mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 07:42:23 +02:00
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).
417 lines
11 KiB
JavaScript
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);
|
|
}
|
|
});
|