mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-29 23:32:04 +02:00
A simple borderless window floating on desktop. Window can be resized and moved by dragging. It also has some minimalistic controls showing on top of the video when cursor is hovering over it.\n\n This was a feature originally requested by @zahid1905.
232 lines
5.8 KiB
JavaScript
232 lines
5.8 KiB
JavaScript
const { GLib, GObject, Gtk, Pango } = imports.gi;
|
|
const Debug = imports.clapper_src.debug;
|
|
|
|
const REVEAL_TIME = 800;
|
|
|
|
let { debug } = Debug;
|
|
|
|
var CustomRevealer = GObject.registerClass(
|
|
class ClapperCustomRevealer extends Gtk.Revealer
|
|
{
|
|
_init(opts)
|
|
{
|
|
opts = opts || {};
|
|
|
|
let defaults = {
|
|
visible: false,
|
|
can_focus: false,
|
|
};
|
|
Object.assign(opts, defaults);
|
|
|
|
super._init(opts);
|
|
|
|
this.revealerName = '';
|
|
}
|
|
|
|
revealChild(isReveal)
|
|
{
|
|
if(isReveal) {
|
|
this._clearTimeout();
|
|
this.set_visible(isReveal);
|
|
}
|
|
else
|
|
this._setHideTimeout();
|
|
|
|
/* Restore focusability after we are done */
|
|
if(!isReveal) this.set_can_focus(true);
|
|
|
|
this._timedReveal(isReveal, REVEAL_TIME);
|
|
}
|
|
|
|
showChild(isReveal)
|
|
{
|
|
this._clearTimeout();
|
|
this.set_visible(isReveal);
|
|
this._timedReveal(isReveal, 0);
|
|
}
|
|
|
|
set_visible(isVisible)
|
|
{
|
|
if(this.visible === isVisible)
|
|
return false;
|
|
|
|
super.set_visible(isVisible);
|
|
debug(`${this.revealerName} revealer visible: ${isVisible}`);
|
|
|
|
return true;
|
|
}
|
|
|
|
_timedReveal(isReveal, time)
|
|
{
|
|
this.set_transition_duration(time);
|
|
this.set_reveal_child(isReveal);
|
|
}
|
|
|
|
/* Drawing revealers on top of video frames
|
|
* increases CPU usage, so we hide them */
|
|
_setHideTimeout()
|
|
{
|
|
this._clearTimeout();
|
|
|
|
this._revealerTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, REVEAL_TIME + 20, () => {
|
|
this._revealerTimeout = null;
|
|
this.set_visible(false);
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
}
|
|
|
|
_clearTimeout()
|
|
{
|
|
if(!this._revealerTimeout)
|
|
return;
|
|
|
|
GLib.source_remove(this._revealerTimeout);
|
|
this._revealerTimeout = null;
|
|
}
|
|
});
|
|
|
|
var RevealerTop = GObject.registerClass(
|
|
class ClapperRevealerTop extends CustomRevealer
|
|
{
|
|
_init()
|
|
{
|
|
super._init({
|
|
transition_duration: REVEAL_TIME,
|
|
transition_type: Gtk.RevealerTransitionType.CROSSFADE,
|
|
valign: Gtk.Align.START,
|
|
});
|
|
|
|
this.revealerName = 'top';
|
|
let initTime = GLib.DateTime.new_now_local().format('%X');
|
|
this.timeFormat = (initTime.length > 8)
|
|
? '%I:%M %p'
|
|
: '%H:%M';
|
|
|
|
this.revealerGrid = new Gtk.Grid({
|
|
column_spacing: 8
|
|
});
|
|
this.revealerGrid.add_css_class('osd');
|
|
this.revealerGrid.add_css_class('reavealertop');
|
|
|
|
this.mediaTitle = new Gtk.Label({
|
|
ellipsize: Pango.EllipsizeMode.END,
|
|
vexpand: true,
|
|
hexpand: true,
|
|
margin_top: 14,
|
|
margin_start: 12,
|
|
xalign: 0,
|
|
yalign: 0,
|
|
});
|
|
|
|
let timeLabelOpts = {
|
|
margin_end: 10,
|
|
xalign: 1,
|
|
yalign: 0,
|
|
};
|
|
this.currentTime = new Gtk.Label(timeLabelOpts);
|
|
this.currentTime.add_css_class('osdtime');
|
|
|
|
this.endTime = new Gtk.Label(
|
|
Object.assign(timeLabelOpts, { visible: false })
|
|
);
|
|
this.endTime.add_css_class('osdendtime');
|
|
|
|
this.revealerGrid.attach(this.mediaTitle, 0, 0, 1, 1);
|
|
this.revealerGrid.attach(this.currentTime, 1, 0, 1, 1);
|
|
this.revealerGrid.attach(this.endTime, 1, 0, 1, 1);
|
|
|
|
this.set_child(this.revealerGrid);
|
|
}
|
|
|
|
setMediaTitle(title)
|
|
{
|
|
this.mediaTitle.label = title;
|
|
}
|
|
|
|
setTimes(currTime, endTime)
|
|
{
|
|
let now = currTime.format(this.timeFormat);
|
|
let end = endTime.format(this.timeFormat);
|
|
let endText = `Ends at: ${end}`;
|
|
|
|
this.currentTime.set_label(now);
|
|
this.endTime.set_label(endText);
|
|
|
|
/* Make sure that next timeout is always run after clock changes,
|
|
* by delaying it for additional few milliseconds */
|
|
let nextUpdate = 60002 - parseInt(currTime.get_seconds() * 1000);
|
|
debug(`updated current time: ${now}, ends at: ${end}`);
|
|
|
|
return nextUpdate;
|
|
}
|
|
});
|
|
|
|
var RevealerBottom = GObject.registerClass(
|
|
class ClapperRevealerBottom extends CustomRevealer
|
|
{
|
|
_init()
|
|
{
|
|
super._init({
|
|
transition_duration: REVEAL_TIME,
|
|
transition_type: Gtk.RevealerTransitionType.SLIDE_UP,
|
|
valign: Gtk.Align.END,
|
|
});
|
|
|
|
this.revealerName = 'bottom';
|
|
this.revealerBox = new Gtk.Box();
|
|
this.revealerBox.add_css_class('osd');
|
|
|
|
this.set_child(this.revealerBox);
|
|
}
|
|
|
|
append(widget)
|
|
{
|
|
this.revealerBox.append(widget);
|
|
}
|
|
|
|
remove(widget)
|
|
{
|
|
this.revealerBox.remove(widget);
|
|
}
|
|
|
|
setFloatingClass(isFloating)
|
|
{
|
|
if(isFloating === this.revealerBox.has_css_class('floatingcontrols'))
|
|
return;
|
|
|
|
let action = (isFloating) ? 'add' : 'remove';
|
|
this.revealerBox[`${action}_css_class`]('floatingcontrols');
|
|
}
|
|
|
|
set_visible(isVisible)
|
|
{
|
|
let isChange = super.set_visible(isVisible);
|
|
if(!isChange || !this.can_focus) return;
|
|
|
|
let parent = this.get_parent();
|
|
let playerWidget = parent.get_first_child();
|
|
if(!playerWidget) return;
|
|
|
|
if(isVisible) {
|
|
let box = this.get_first_child();
|
|
if(!box) return;
|
|
|
|
let controls = box.get_first_child();
|
|
if(!controls) return;
|
|
|
|
let togglePlayButton = controls.get_first_child();
|
|
if(togglePlayButton) {
|
|
togglePlayButton.grab_focus();
|
|
debug('focus moved to toggle play button');
|
|
}
|
|
playerWidget.set_can_focus(false);
|
|
}
|
|
else {
|
|
playerWidget.set_can_focus(true);
|
|
playerWidget.grab_focus();
|
|
debug('focus moved to player widget');
|
|
}
|
|
}
|
|
});
|