Files
clapper/src/revealers.js
Rafał Dzięgiel b02f54a3a6 Do not show mobile controls transition on launch
Start app with the correct controls layout instead of showing the "hide elapsed time"
transition when started on mobile width. It is annoying.

We cannot detect surface width during app widgets assembly, so update the controls
revealers state on first surface update after window is mapped and only if running
on mobile width. Otherwise do not do anything like before which will result in
showing fully revealed controls (default).
2021-04-15 15:27:28 +02:00

417 lines
11 KiB
JavaScript

const { GLib, GObject, Gtk, Pango } = imports.gi;
const { HeaderBar } = imports.src.headerbar;
const Debug = imports.src.debug;
const DBus = imports.src.dbus;
const Misc = imports.src.misc;
const { debug } = Debug;
const { settings } = Misc;
var CustomRevealer = GObject.registerClass(
class ClapperCustomRevealer extends Gtk.Revealer
{
_init(opts)
{
opts = opts || {};
const defaults = {
visible: false,
can_focus: false,
transition_duration: 800,
};
Object.assign(opts, defaults);
super._init(opts);
this.revealerName = '';
this.bind_property('child_revealed', this, 'visible',
GObject.BindingFlags.DEFAULT
);
}
revealChild(isReveal)
{
if(this.reveal_child === isReveal)
return;
if(isReveal)
this.visible = true;
this.reveal_child = isReveal;
}
});
var RevealerTop = GObject.registerClass(
class ClapperRevealerTop extends CustomRevealer
{
_init()
{
super._init({
transition_type: Gtk.RevealerTransitionType.CROSSFADE,
valign: Gtk.Align.START,
});
this.revealerName = 'top';
this._requestedTransition = this.transition_type;
const initTime = GLib.DateTime.new_now_local().format('%X');
this.timeFormat = (initTime.length > 8)
? '%I:%M %p'
: '%H:%M';
this.mediaTitle = new Gtk.Label({
ellipsize: Pango.EllipsizeMode.END,
halign: Gtk.Align.START,
valign: Gtk.Align.CENTER,
margin_start: 10,
margin_end: 10,
visible: false,
});
this.mediaTitle.add_css_class('tvtitle');
this.currentTime = new Gtk.Label({
halign: Gtk.Align.END,
valign: Gtk.Align.CENTER,
margin_start: 10,
margin_end: 10,
});
this.currentTime.add_css_class('tvtime');
this.endTime = new Gtk.Label({
halign: Gtk.Align.END,
valign: Gtk.Align.START,
margin_start: 10,
margin_end: 10,
visible: false,
});
this.endTime.add_css_class('tvendtime');
const revealerBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
});
this.headerBar = new HeaderBar();
revealerBox.append(this.headerBar);
this.revealerGrid = new Gtk.Grid({
column_spacing: 4,
margin_top: 8,
margin_start: 8,
margin_end: 8,
visible: false,
});
this.revealerGrid.add_css_class('revealertopgrid');
const topLeftBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
});
topLeftBox.add_css_class('osd');
topLeftBox.add_css_class('roundedcorners');
topLeftBox.append(this.mediaTitle);
const topSpacerBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
hexpand: true,
});
const topRightBox = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
halign: Gtk.Align.END,
});
topRightBox.add_css_class('osd');
topRightBox.add_css_class('roundedcorners');
topRightBox.append(this.currentTime);
topRightBox.append(this.endTime);
this.revealerGrid.attach(topLeftBox, 0, 0, 1, 1);
this.revealerGrid.attach(topSpacerBox, 1, 0, 1, 1);
this.revealerGrid.attach(topRightBox, 2, 0, 1, 2);
revealerBox.append(this.revealerGrid);
this.set_child(revealerBox);
this.mediaTitle.bind_property('visible', this.endTime, 'visible',
GObject.BindingFlags.DEFAULT
);
this.connect('notify::child-revealed', this._onTopRevealed.bind(this));
}
set title(text)
{
this.mediaTitle.label = text;
}
get title()
{
return this.mediaTitle.label;
}
set showTitle(isShow)
{
this.mediaTitle.visible = isShow;
}
get showTitle()
{
return this.mediaTitle.visible;
}
setTimes(currTime, endTime, isEndKnown)
{
const now = currTime.format(this.timeFormat);
this.currentTime.label = now;
const end = (isEndKnown)
? endTime.format(this.timeFormat)
: 'unknown';
this.endTime.label = `Ends at: ${end}`;
/* Make sure that next timeout is always run after clock changes,
* by delaying it for additional few milliseconds */
const nextUpdate = 60004 - parseInt(currTime.get_seconds() * 1000);
debug(`updated current time: ${now}, ends at: ${end}`);
return nextUpdate;
}
setFullscreenMode(isFullscreen, isMobileMonitor)
{
const isTvMode = (isFullscreen && !isMobileMonitor);
this.headerBar.visible = !isTvMode;
this.revealerGrid.visible = isTvMode;
this.headerBar.extraButtonsBox.visible = !isFullscreen;
this._requestedTransition = (isTvMode)
? Gtk.RevealerTransitionType.SLIDE_DOWN
: Gtk.RevealerTransitionType.CROSSFADE;
const isRevealed = this.child_revealed;
/* FIXME: Changing transition in middle or when not fully
* revealed has dire consequences, seems to be a GTK4 bug */
if(isRevealed && isRevealed === this.reveal_child)
this._checkSwitchTransitionType();
}
_checkSwitchTransitionType()
{
if(this.transition_type !== this._requestedTransition)
this.transition_type = this._requestedTransition;
}
_onTopRevealed()
{
if(this.child_revealed) {
/* TODO: Move before above if statement when GTK4 can handle
* changing transition type while not fully revealed */
this._checkSwitchTransitionType();
const clapperWidget = this.root.child;
if(!clapperWidget) return;
clapperWidget._setHideControlsTimeout();
}
}
});
var RevealerBottom = GObject.registerClass(
class ClapperRevealerBottom extends CustomRevealer
{
_init()
{
super._init({
transition_type: Gtk.RevealerTransitionType.SLIDE_UP,
valign: Gtk.Align.END,
});
this.revealerName = 'bottom';
this.revealerBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
margin_start: 8,
margin_end: 8,
margin_bottom: 8,
visible: false,
});
this.revealerBox.add_css_class('osd');
this.revealerBox.add_css_class('roundedcorners');
this.set_child(this.revealerBox);
const motionController = new Gtk.EventControllerMotion();
motionController.connect('motion', this._onMotion.bind(this));
this.add_controller(motionController);
}
append(widget)
{
this.revealerBox.append(widget);
}
remove(widget)
{
this.revealerBox.remove(widget);
}
setLayoutMargins(layoutWidth)
{
const maxWidth = 1720;
if(layoutWidth <= maxWidth)
return;
const margin = (layoutWidth - maxWidth) / 2;
this.margin_start = margin;
this.margin_end = margin;
}
_onMotion(controller, x, y)
{
const clapperWidget = this.root.child;
clapperWidget._clearTimeout('hideControls');
}
});
var ControlsRevealer = GObject.registerClass(
class ClapperControlsRevealer extends Gtk.Revealer
{
_init()
{
super._init({
transition_duration: 600,
transition_type: Gtk.RevealerTransitionType.SLIDE_DOWN,
reveal_child: true,
});
this.connect('notify::child-revealed', this._onControlsRevealed.bind(this));
}
toggleReveal()
{
/* Prevent interrupting transition */
if(this.reveal_child !== this.child_revealed)
return;
const { widget } = this.root.child.player;
if(this.child_revealed) {
const [width] = this.root.get_default_size();
const height = widget.get_height();
this.add_tick_callback(
this._onUnrevealTick.bind(this, widget, width, height)
);
}
else
this.visible = true;
widget.height_request = widget.get_height();
this.reveal_child ^= true;
const isFloating = !this.reveal_child;
DBus.shellWindowEval('make_above', isFloating);
const isStick = (isFloating && settings.get_boolean('floating-stick'));
DBus.shellWindowEval('stick', isStick);
}
_onControlsRevealed()
{
if(this.child_revealed) {
const clapperWidget = this.root.child;
if(!clapperWidget) return;
const [width, height] = this.root.get_default_size();
clapperWidget.player.widget.height_request = -1;
this.root.set_default_size(width, height);
}
}
_onUnrevealTick(playerWidget, width, height)
{
const isRevealed = this.child_revealed;
if(!isRevealed) {
playerWidget.height_request = -1;
this.visible = false;
}
this.root.set_default_size(width, height);
return isRevealed;
}
});
var ButtonsRevealer = GObject.registerClass(
class ClapperButtonsRevealer extends Gtk.Revealer
{
_init(trType, toggleButton)
{
super._init({
transition_duration: 500,
transition_type: Gtk.RevealerTransitionType[trType],
});
const revealerBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
});
this.set_child(revealerBox);
if(toggleButton) {
toggleButton.connect('clicked', this._onToggleButtonClicked.bind(this));
this.connect('notify::reveal-child', this._onRevealChild.bind(this, toggleButton));
this.connect('notify::child-revealed', this._onChildRevealed.bind(this, toggleButton));
}
}
append(widget)
{
this.get_child().append(widget);
}
revealInstantly(isReveal)
{
if(this.child_revealed === isReveal)
return;
const initialDuration = this.transition_duration;
this.transition_duration = 0;
this.reveal_child = isReveal;
this.transition_duration = initialDuration;
}
_setRotateClass(icon, isAdd)
{
const cssClass = 'halfrotate';
const hasClass = icon.has_css_class(cssClass);
if(!hasClass && isAdd)
icon.add_css_class(cssClass);
else if(hasClass && !isAdd)
icon.remove_css_class(cssClass);
}
_onToggleButtonClicked(button)
{
this.set_reveal_child(!this.reveal_child);
}
_onRevealChild(button)
{
if(this.reveal_child !== this.child_revealed)
this._setRotateClass(button.child, true);
}
_onChildRevealed(button)
{
if(!this.child_revealed)
button.setPrimaryIcon();
else
button.setSecondaryIcon();
this._setRotateClass(button.child, false);
}
});