Merge pull request #43 from Rafostar/gnome4000

GNOME 40 design changes
This commit is contained in:
Rafał Dzięgiel
2021-02-22 11:39:41 +01:00
committed by GitHub
22 changed files with 1081 additions and 848 deletions

View File

@@ -1,28 +1,26 @@
# Clapper
A GNOME media player build using [GJS](https://gitlab.gnome.org/GNOME/gjs) with [GTK4](https://www.gtk.org) toolkit. The media player is using [GStreamer](https://gstreamer.freedesktop.org/) as a media backend and renders everything via [OpenGL](https://www.opengl.org).
A GNOME media player build using [GJS](https://gitlab.gnome.org/GNOME/gjs) with [GTK4](https://www.gtk.org) toolkit.
The media player is using [GStreamer](https://gstreamer.freedesktop.org/) as a media backend and renders everything via [OpenGL](https://www.opengl.org).
<p align="center">
<img src="https://github.com/Rafostar/clapper/raw/master/media/screenshot-windowed-mode.png"><br>
<img src="https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot-windowed.png"><br>
<b>Windowed Mode</b>
</p>
<p align="center">
<img src="https://github.com/Rafostar/clapper/raw/master/media/screenshot-fullscreen-mode.png"><br>
<img src="https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot-fullscreen.png"><br>
<b>Fullscreen Mode</b>
</p>
<p align="center">
<img src="https://github.com/Rafostar/clapper/raw/master/media/screenshot-floating-mode.png"><br>
<img src="https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot-floating.png"><br>
<b>Floating Mode</b>
</p>
### WORK IN PROGRESS
This is still early WIP. Many features are not implemented yet and quite a few are still unstable.
### Features:
* [Hardware acceleration](https://github.com/Rafostar/clapper/wiki/Hardware-acceleration)
* [Floating mode](https://github.com/Rafostar/clapper/wiki/Floating-mode)
* [Adaptive UI](https://raw.githubusercontent.com/Rafostar/clapper/master/media/screencast-mobile-ui.webm)
* [Adaptive UI](https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot-mobile.png)
* [Playlist from file](https://github.com/Rafostar/clapper/wiki/Playlists)
* Chapters on progress bar

View File

@@ -5,41 +5,93 @@ scale marks {
radio {
margin-left: -2px;
}
.osd popover box {
text-shadow: none;
font-size: 21px;
font-weight: 500;
}
.osd button {
min-width: 32px;
min-height: 32px;
-gtk-icon-shadow: none;
}
.osd radio {
margin-left: 0px;
margin-right: 4px;
border: 2px solid;
min-width: 17px;
min-height: 17px;
}
/* Adwaita is missing osd ListBox */
.osd list {
background: none;
}
.osd list row {
-gtk-icon-shadow: none;
}
.gtk402 trough highlight {
border-color: transparent;
}
.gtk402 .osd trough highlight {
border-color: inherit;
}
.osdheaderbar {
background: transparent;
}
.osdheaderbar button {
border: transparent;
}
.linkseparator {
background: alpha(@borders, 0.75);
min-width: 1px;
}
.linkedleft image {
margin-left: 2px;
}
.linkedright image {
margin-right: 2px;
}
/* Non-osd style for popover menu */
.menupopover label {
color: @theme_text_color;
}
.menupopover arrow {
background: @theme_base_color;
border-color: @insensitive_base_color;
}
.menupopover contents {
background: @theme_base_color;
border-color: @insensitive_base_color;
}
.adwrounded.csd {
border-radius: 8px;
}
.adwrounded.fullscreen,
.adwrounded.maximized,
.adwrounded.tiled,
.adwrounded.tiled-top,
.adwrounded.tiled-left,
.adwrounded.tiled-right,
.adwrounded.tiled-bottom {
border-radius: 0px;
}
.roundedcorners {
border-radius: 8px;
}
.osd .playercontrols {
.videowidget {
min-width: 336px;
min-height: 189px;
}
.tvmode popover box {
text-shadow: none;
font-size: 21px;
font-weight: 500;
}
.tvmode button {
min-width: 32px;
min-height: 32px;
-gtk-icon-shadow: none;
}
.tvmode radio {
margin-left: 0px;
margin-right: 4px;
border: 2px solid;
min-width: 17px;
min-height: 17px;
}
.tvmode .playercontrols {
-gtk-icon-size: 24px;
}
.playbackicon {
-gtk-icon-size: 20px;
}
.osd .playbackicon {
.tvmode .playbackicon {
-gtk-icon-size: 28px;
}
.labelbutton {
@@ -51,32 +103,33 @@ radio {
font-variant-numeric: tabular-nums;
font-weight: 600;
}
.osd .labelbutton {
.tvmode .labelbutton {
margin-top: 0px;
font-size: 23px;
text-shadow: none;
}
.reavealertop {
min-height: 88px;
box-shadow: inset 0px 200px 10px -132px rgba(0,0,0,0.4);
/* Top Revealer */
.tvmode .revealertopgrid {
font-family: 'Cantarell', sans-serif;
}
.tvmode .tvtitle {
font-size: 28px;
font-weight: 500;
text-shadow: none;
background: transparent;
}
.osdtime {
.tvtime {
margin-top: -2px;
margin-right: -4px;
min-width: 4px;
margin-bottom: -2px;
min-height: 4px;
font-size: 38px;
font-weight: 700;
font-variant-numeric: tabular-nums;
}
.osdendtime {
margin-top: 36px;
margin-right: -4px;
min-width: 4px;
.tvendtime {
margin-top: -4px;
margin-bottom: 2px;
min-height: 6px;
font-size: 24px;
font-weight: 600;
font-variant-numeric: tabular-nums;
@@ -93,13 +146,16 @@ radio {
margin-top: -2px;
margin-bottom: -2px;
}
.osd .positionscale {
.tvmode .positionscale {
margin-top: -1px;
}
.positionscale trough highlight {
min-height: 4px;
}
.osd .positionscale trough slider {
.osd .positionscale trough highlight {
min-height: 6px;
}
.tvmode .positionscale trough slider {
color: transparent;
background: transparent;
border-color: transparent;
@@ -111,11 +167,11 @@ radio {
.positionscale.fine-tune mark indicator {
min-height: 6px;
}
.osd .positionscale mark indicator {
.tvmode .positionscale mark indicator {
min-height: 7px;
min-width: 2px;
}
.osd .positionscale.fine-tune mark indicator {
.tvmode .positionscale.fine-tune mark indicator {
min-height: 7px;
min-width: 2px;
}
@@ -127,17 +183,17 @@ radio {
margin-top: 4px;
margin-bottom: -6px;
}
.osd .positionscale marks.top {
.tvmode .positionscale marks.top {
margin-bottom: 2px;
}
.osd .positionscale marks.bottom {
.tvmode .positionscale marks.bottom {
margin-top: 2px;
}
.osd .positionscale trough highlight {
.tvmode .positionscale trough highlight {
border-radius: 3px;
min-height: 20px;
}
.osd .positionscale.fine-tune trough highlight {
.tvmode .positionscale.fine-tune trough highlight {
border-radius: 3px;
min-height: 20px;
}
@@ -149,7 +205,7 @@ radio {
margin-right: -6px;
min-height: 180px;
}
.osd .volumescale {
.tvmode .volumescale {
margin: 2px;
margin-left: -6px;
margin-right: -4px;
@@ -160,7 +216,7 @@ radio {
margin-top: -4px;
margin-bottom: -6px;
}
.osd .volumescale trough highlight {
.tvmode .volumescale trough highlight {
min-width: 6px;
}
.overamp trough highlight {
@@ -168,57 +224,19 @@ radio {
}
/* Elapsed Popover */
.osd list row {
-gtk-icon-shadow: none;
}
.elapsedpopoverbox {
min-width: 260px;
}
.elapsedpopoverbox box separator {
background: @insensitive_fg_color;
}
.osd .elapsedpopoverbox {
.tvmode .elapsedpopoverbox {
min-width: 360px;
}
.osd .speedscale trough highlight {
.tvmode .speedscale trough highlight {
min-height: 6px;
}
/* Floating Mode */
.floatingwindow {
border-radius: 8px;
}
.osd.floatingcontrols .playercontrols {
-gtk-icon-size: 16px;
}
.osd.floatingcontrols .playbackicon {
-gtk-icon-size: 20px;
}
.osd.floatingcontrols button {
border-radius: 10px;
min-width: 24px;
min-height: 24px;
}
.osd.floatingcontrols .positionscale {
margin-top: -2px;
}
.osd.floatingcontrols .positionscale trough highlight {
border-radius: 3px;
min-height: 12px;
}
.osd.floatingcontrols .positionscale.fine-tune trough highlight {
border-radius: 3px;
min-height: 12px;
}
.osd.floatingcontrols .positionscale mark indicator {
min-height: 5px;
min-width: 1px;
}
.osd.floatingcontrols .positionscale.fine-tune mark indicator {
min-height: 5px;
min-width: 1px;
}
.narrowbutton {
min-width: 8px;
}
@@ -238,17 +256,12 @@ radio {
.chapterlabel {
min-width: 32px;
}
.osd .chapterlabel {
.tvmode .chapterlabel {
min-width: 40px;
text-shadow: none;
font-size: 21px;
font-weight: 500;
}
.osd.floatingcontrols .chapterlabel {
font: inherit;
font-size: 100%;
min-width: 32px;
}
/* Preferences */
.prefsnotebook grid {

View File

@@ -40,6 +40,10 @@
<default>'[]'</default>
<summary>Data storing unfinished videos resume info</summary>
</key>
<key name="floating-stick" type="b">
<default>false</default>
<summary>Auto stick floating window to all workspaces</summary>
</key>
<!-- Audio -->
<key name="audio-offset" type="d">
@@ -104,10 +108,6 @@
<default>'[960, 583]'</default>
<summary>Stores window size to restore on next launch</summary>
</key>
<key name="float-size" type="s">
<default>'[480, 270]'</default>
<summary>Stores floating window size to restore on next launch</summary>
</key>
<key name="volume-last" type="d">
<default>1</default>
<summary>Stores last linear volume value to apply on startup</summary>

View File

@@ -4,21 +4,23 @@
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-or-later</project_license>
<name>Clapper</name>
<summary>Play videos and music</summary>
<summary>Simple and modern GNOME media player</summary>
<translation type="gettext">com.github.rafostar.Clapper</translation>
<launchable type="desktop-id">com.github.rafostar.Clapper.desktop</launchable>
<description>
<p>
Clapper is a GNOME media player build using GJS with GTK4 toolkit.
The media player is using GStreamer GstPlayer API as a media backend
and renders everything via OpenGL. Player works natively on both
Xorg and Wayland. It also supports VA-API on AMD/Intel GPUs.
The media player is using GStreamer as a media backend and renders
everything via OpenGL. Player works natively on both Xorg and Wayland.
It also supports VA-API on AMD/Intel GPUs.
</p>
<p>
The media player has an adaptive GUI. When viewing videos in "Windowed Mode",
Clapper will use mostly unmodified GTK widgets to match your OS look nicely.
When player enters "Fullscreen Mode" all GUI elements will become darker, bigger
and semi-transparent for your viewing comfort. It also has a "Floating Mode".
and semi-transparent for your viewing comfort. It also has a "Floating Mode" which
displays video only on top of all other windows for a PiP-like viewing experience.
Mobile friendly transitions are also supported.
</p>
</description>
<developer_name>Rafał Dzięgiel</developer_name>
@@ -32,13 +34,16 @@
</categories>
<screenshots>
<screenshot type="default">
<image type="source">https://raw.githubusercontent.com/Rafostar/clapper/master/media/screenshot-windowed-mode.png</image>
<image type="source">https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot-windowed.png</image>
</screenshot>
<screenshot>
<image type="source">https://raw.githubusercontent.com/Rafostar/clapper/master/media/screenshot-fullscreen-mode.png</image>
<image type="source">https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot-fullscreen.png</image>
</screenshot>
<screenshot>
<image type="source">https://raw.githubusercontent.com/Rafostar/clapper/master/media/screenshot-floating-mode.png</image>
<image type="source">https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot-floating.png</image>
</screenshot>
<screenshot>
<image type="source">https://raw.githubusercontent.com/wiki/Rafostar/clapper/media/screenshot-mobile.png</image>
</screenshot>
</screenshots>
<releases>

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 MiB

View File

@@ -13,6 +13,7 @@
"--share=network",
"--device=all",
"--filesystem=xdg-videos",
"--talk-name=org.gnome.Shell",
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0",
"--env=GST_VAAPI_ALL_DRIVERS=1"
],

View File

@@ -18,7 +18,8 @@
{
"type": "git",
"url": "https://gitlab.gnome.org/GNOME/gtk.git",
"commit": "ec94ec0286041c16d6df7b496b0760a0ae0885ba"
"tag": "4.1.0",
"commit": "65c38111f958bac66d578e3f81964ca3857105c1"
},
{
"type": "patch",

View File

@@ -1,6 +1,5 @@
const { Gio, GObject } = imports.gi;
const { Gio, GObject, Gtk } = imports.gi;
const { AppBase } = imports.src.appBase;
const { HeaderBar } = imports.src.headerbar;
const { Widget } = imports.src.widget;
const Debug = imports.src.debug;
@@ -23,14 +22,20 @@ class ClapperApp extends AppBase
{
super.vfunc_startup();
this.active_window.isClapperApp = true;
this.active_window.add_css_class('nobackground');
const window = this.active_window;
window.isClapperApp = true;
window.add_css_class('nobackground');
const clapperWidget = new Widget();
this.active_window.set_child(clapperWidget);
window.set_child(clapperWidget);
const headerBar = new HeaderBar(this.active_window);
this.active_window.set_titlebar(headerBar);
const dummyHeaderbar = new Gtk.Box({
can_focus: false,
focusable: false,
visible: false,
});
window.set_titlebar(dummyHeaderbar);
}
vfunc_open(files, hint)

View File

@@ -95,6 +95,12 @@ class ClapperAppBase extends Gtk.Application
const theme = gtkSettings.gtk_theme_name;
debug(`user selected theme: ${theme}`);
if(
theme.startsWith('Adwaita')
&& !this.active_window.has_css_class('adwrounded')
)
this.active_window.add_css_class('adwrounded');
if(!theme.endsWith('-dark'))
return;

View File

@@ -18,11 +18,7 @@ class ClapperCustomButton extends Gtk.Button
super._init(opts);
this.floatUnaffected = false;
this.wantedVisible = true;
this.isFullscreen = false;
this.isFloating = false;
this.add_css_class('flat');
}
@@ -43,60 +39,24 @@ class ClapperCustomButton extends Gtk.Button
this.isFullscreen = isFullscreen;
}
setFloatingMode(isFloating)
{
if(this.isFloating === isFloating)
return;
this.isFloating = isFloating;
if(this.floatUnaffected)
return;
if(isFloating)
super.set_visible(false);
else
super.set_visible(this.wantedVisible);
}
set_visible(isVisible)
{
this.wantedVisible = isVisible;
if(this.isFloating && !this.floatUnaffected)
super.set_visible(false);
else
super.set_visible(isVisible);
}
vfunc_clicked()
{
if(!this.isFullscreen)
return;
const { player } = this.get_ancestor(Gtk.Grid);
player._setHideControlsTimeout();
}
});
var IconButton = GObject.registerClass(
class ClapperIconButton extends CustomButton
{
_init(icon)
{
super._init({
icon_name: icon,
});
this.floatUnaffected = true;
const clapperWidget = this.get_ancestor(Gtk.Grid);
clapperWidget.revealControls();
}
});
var IconToggleButton = GObject.registerClass(
class ClapperIconToggleButton extends IconButton
class ClapperIconToggleButton extends CustomButton
{
_init(primaryIcon, secondaryIcon)
{
super._init(primaryIcon);
super._init({
icon_name: primaryIcon,
});
this.primaryIcon = primaryIcon;
this.secondaryIcon = secondaryIcon;
@@ -114,11 +74,20 @@ class ClapperIconToggleButton extends IconButton
});
var PopoverButtonBase = GObject.registerClass(
class ClapperPopoverButtonBase extends CustomButton
class ClapperPopoverButtonBase extends Gtk.ToggleButton
{
_init()
{
super._init();
super._init({
margin_top: 4,
margin_bottom: 4,
margin_start: 2,
margin_end: 2,
can_focus: false,
});
this.isFullscreen = false;
this.add_css_class('flat');
this.popover = new Gtk.Popover({
position: Gtk.PositionType.TOP,
@@ -142,7 +111,16 @@ class ClapperPopoverButtonBase extends CustomButton
if(this.isFullscreen === isFullscreen)
return;
super.setFullscreenMode(isFullscreen);
this.margin_top = (isFullscreen) ? 5 : 4;
this.margin_start = (isFullscreen) ? 3 : 2;
this.margin_end = (isFullscreen) ? 3 : 2;
this.can_focus = isFullscreen;
/* Redraw icon after style class change */
if(this.icon_name)
this.set_icon_name(this.icon_name);
this.isFullscreen = isFullscreen;
this.popover.set_offset(0, -this.margin_top);
@@ -154,20 +132,33 @@ class ClapperPopoverButtonBase extends CustomButton
this.popover[action + '_css_class'](cssClass);
}
vfunc_clicked()
vfunc_toggled()
{
super.vfunc_clicked();
if(!this.active)
return;
const clapperWidget = this.get_ancestor(Gtk.Grid);
if(this.isFullscreen) {
clapperWidget.revealControls();
clapperWidget.isPopoverOpen = true;
}
this.set_state_flags(Gtk.StateFlags.CHECKED, false);
this.popover.popup();
}
_onClosed()
{
const { player } = this.get_ancestor(Gtk.Grid);
player.widget.grab_focus();
const clapperWidget = this.get_ancestor(Gtk.Grid);
this.unset_state_flags(Gtk.StateFlags.CHECKED);
clapperWidget.player.widget.grab_focus();
/* Set again timeout as popover is now closed */
if(clapperWidget.isFullscreenMode)
clapperWidget.revealControls();
clapperWidget.isPopoverOpen = false;
this.active = false;
}
_onCloseRequest()

63
src/controls.js vendored
View File

@@ -28,8 +28,6 @@ class ClapperControls extends Gtk.Box
this.currentPosition = 0;
this.currentDuration = 0;
this.isPositionDragging = false;
this.isMobileMonitor = false;
this.isMobile = false;
this.showHours = false;
@@ -49,7 +47,6 @@ class ClapperControls extends Gtk.Box
'go-previous-symbolic',
'go-next-symbolic'
);
revealTracksButton.floatUnaffected = false;
revealTracksButton.add_css_class('narrowbutton');
this.buttonsArr.push(revealTracksButton);
const tracksRevealer = new Revealers.ButtonsRevealer(
@@ -92,12 +89,6 @@ class ClapperControls extends Gtk.Box
this.unfullscreenButton.connect('clicked', this._onUnfullscreenClicked.bind(this));
this.unfullscreenButton.set_visible(false);
this.unfloatButton = this.addButton(
'preferences-desktop-remote-desktop-symbolic'
);
this.unfloatButton.connect('clicked', this._onUnfloatClicked.bind(this));
this.unfloatButton.set_visible(false);
const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', this._onControlsKeyPressed.bind(this));
keyController.connect('key-released', this._onControlsKeyReleased.bind(this));
@@ -115,16 +106,8 @@ class ClapperControls extends Gtk.Box
for(let button of this.buttonsArr)
button.setFullscreenMode(isFullscreen);
this.unfullscreenButton.set_visible(isFullscreen);
this.set_can_focus(isFullscreen);
}
setFloatingMode(isFloating)
{
this.isMobile = null;
for(let button of this.buttonsArr)
button.setFloatingMode(isFloating);
this.unfullscreenButton.visible = isFullscreen;
this.can_focus = isFullscreen;
}
setLiveMode(isLive, isSeekable)
@@ -149,7 +132,7 @@ class ClapperControls extends Gtk.Box
{
const button = (buttonIcon instanceof Gtk.Button)
? buttonIcon
: new Buttons.IconButton(buttonIcon);
: new Buttons.CustomButton({ icon_name: buttonIcon });
if(!revealer)
this.append(button);
@@ -495,13 +478,13 @@ class ClapperControls extends Gtk.Box
this.disconnect(this.realizeSignal);
this.realizeSignal = null;
const { player } = this.get_ancestor(Gtk.Grid);
const clapperWidget = this.get_ancestor(Gtk.Grid);
const scrollController = new Gtk.EventControllerScroll();
scrollController.set_flags(
Gtk.EventControllerScrollFlags.VERTICAL
| Gtk.EventControllerScrollFlags.DISCRETE
);
scrollController.connect('scroll', player._onScroll.bind(player));
scrollController.connect('scroll', clapperWidget._onScroll.bind(clapperWidget));
this.volumeButton.add_controller(scrollController);
const initialVolume = (settings.get_string('volume-initial') === 'custom')
@@ -530,12 +513,6 @@ class ClapperControls extends Gtk.Box
root.unfullscreen();
}
_onUnfloatClicked(button)
{
const clapperWidget = this.get_ancestor(Gtk.Grid);
clapperWidget.setFloatingMode(false);
}
_onCheckButtonToggled(checkButton)
{
if(!checkButton.get_active())
@@ -564,8 +541,8 @@ class ClapperControls extends Gtk.Box
_onPositionScaleScroll(controller, dx, dy)
{
const { player } = this.get_ancestor(Gtk.Grid);
player._onScroll(controller, dx || dy, 0);
const clapperWidget = this.get_ancestor(Gtk.Grid);
clapperWidget._onScroll(controller, dx || dy, 0);
}
_onPositionScaleValueChanged(scale)
@@ -616,32 +593,40 @@ class ClapperControls extends Gtk.Box
{
const isPositionDragging = scale.has_css_class('dragging');
if((this.isPositionDragging = isPositionDragging))
if(this.isPositionDragging === isPositionDragging)
return;
const isChapterSeek = this.chapterPopover.visible;
if(!isPositionDragging)
this._setChapterVisible(false);
const clapperWidget = this.get_ancestor(Gtk.Grid);
if(!clapperWidget) return;
if(clapperWidget.isFullscreenMode) {
clapperWidget.revealControls();
if(isPositionDragging)
clapperWidget._clearTimeout('hideControls');
}
if((this.isPositionDragging = isPositionDragging))
return;
const scaleValue = scale.get_value();
const isChapterSeek = this.chapterPopover.visible;
if(!isChapterSeek) {
const positionSeconds = Math.round(scaleValue);
clapperWidget.player.seek_seconds(positionSeconds);
}
else
else {
clapperWidget.player.seek_chapter(scaleValue);
this._setChapterVisible(false);
}
}
/* Only happens when navigating through controls panel */
_onControlsKeyPressed(controller, keyval, keycode, state)
{
const { player } = this.get_ancestor(Gtk.Grid);
player._setHideControlsTimeout();
const clapperWidget = this.get_ancestor(Gtk.Grid);
clapperWidget._setHideControlsTimeout();
}
_onControlsKeyReleased(controller, keyval, keycode, state)

56
src/dbus.js Normal file
View File

@@ -0,0 +1,56 @@
const { Gio } = imports.gi;
const Debug = imports.src.debug;
const { debug } = Debug;
const ShellProxyWrapper = Gio.DBusProxy.makeProxyWrapper(`
<node>
<interface name="org.gnome.Shell">
<method name="Eval">
<arg type="s" direction="in" name="script"/>
<arg type="b" direction="out" name="success"/>
<arg type="s" direction="out" name="result"/>
</method>
</interface>
</node>`
);
let shellProxy = null;
new ShellProxyWrapper(
Gio.DBus.session,
'org.gnome.Shell',
'/org/gnome/Shell',
(proxy, err) => {
if(err) {
debug(err);
return;
}
shellProxy = proxy;
debug('GNOME Shell DBus proxy is ready');
},
null,
Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION
| Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS
);
function shellWindowEval(fn, isEnabled)
{
if(!shellProxy)
return;
const un = (isEnabled) ? '' : 'un';
debug(`changing ${fn}`);
shellProxy.EvalRemote(
`global.display.focus_window.${un}${fn}()`,
(out) => {
const debugMsg = (out[0])
? `window ${fn}: ${isEnabled}`
: new Error(out[1]);
debug(debugMsg);
}
);
}

View File

@@ -1,28 +1,33 @@
const { GObject } = imports.gi;
const { GObject, Gtk } = imports.gi;
const { HeaderBarBase } = imports.src.headerbarBase;
var HeaderBar = GObject.registerClass(
class ClapperHeaderBar extends HeaderBarBase
{
_init(window)
_init()
{
super._init(window);
super._init();
this.add_css_class('osd');
this.add_css_class('osdheaderbar');
}
const clapperWidget = window.get_child();
clapperWidget.controls.unfloatButton.bind_property('visible', this, 'visible',
GObject.BindingFlags.INVERT_BOOLEAN
);
_onWindowButtonActivate(action)
{
this.activate_action(action, null);
}
_onFloatButtonClicked()
{
const clapperWidget = this.get_prev_sibling();
clapperWidget.setFloatingMode(true);
const clapperWidget = this.root.child;
clapperWidget.controlsRevealer.toggleReveal();
/* Reset timer to not disappear during click */
clapperWidget._setHideControlsTimeout();
}
_onFullscreenButtonClicked()
{
const window = this.get_parent();
window.fullscreen();
this.root.fullscreen();
}
});

View File

@@ -1,90 +1,202 @@
const { GObject, Gtk, Pango } = imports.gi;
const { GObject, Gtk } = imports.gi;
const Misc = imports.src.misc;
var HeaderBarBase = GObject.registerClass(
class ClapperHeaderBarBase extends Gtk.HeaderBar
class ClapperHeaderBarBase extends Gtk.Box
{
_init(window)
_init()
{
super._init({
can_focus: false,
orientation: Gtk.Orientation.HORIZONTAL,
spacing: 6,
margin_top: 6,
margin_start: 6,
margin_end: 6,
});
this.isMaximized = false;
this.isMenuOnLeft = true;
const clapperPath = Misc.getClapperPath();
const uiBuilder = Gtk.Builder.new_from_file(
`${clapperPath}/ui/clapper.ui`
);
this.add_css_class('noborder');
this.set_title_widget(this._createWidgetForWindow(window));
this.menuWidget = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
valign: Gtk.Align.CENTER,
spacing: 6,
});
const mainMenuButton = new Gtk.MenuButton({
this.menuButton = new Gtk.MenuButton({
icon_name: 'open-menu-symbolic',
valign: Gtk.Align.CENTER,
});
const mainMenuModel = uiBuilder.get_object('mainMenu');
const mainMenuPopover = new HeaderBarPopover(mainMenuModel);
mainMenuButton.set_popover(mainMenuPopover);
this.pack_start(mainMenuButton);
mainMenuPopover.add_css_class('menupopover');
this.menuButton.set_popover(mainMenuPopover);
this.menuButton.add_css_class('circular');
this.menuWidget.append(this.menuButton);
const buttonsBox = new Gtk.Box({
this.extraButtonsBox = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
valign: Gtk.Align.CENTER,
});
buttonsBox.add_css_class('linked');
this.extraButtonsBox.add_css_class('linked');
const floatButton = new Gtk.Button({
icon_name: 'preferences-desktop-remote-desktop-symbolic',
icon_name: 'go-bottom-symbolic',
});
floatButton.connect('clicked', this._onFloatButtonClicked.bind(this));
buttonsBox.append(floatButton);
floatButton.add_css_class('circular');
floatButton.add_css_class('linkedleft');
floatButton.connect('clicked',
this._onFloatButtonClicked.bind(this)
);
this.extraButtonsBox.append(floatButton);
const separator = new Gtk.Separator({
orientation: Gtk.Orientation.VERTICAL,
});
separator.add_css_class('linkseparator');
this.extraButtonsBox.append(separator);
const fullscreenButton = new Gtk.Button({
icon_name: 'view-fullscreen-symbolic',
});
fullscreenButton.connect('clicked', this._onFullscreenButtonClicked.bind(this));
fullscreenButton.add_css_class('circular');
fullscreenButton.add_css_class('linkedright');
fullscreenButton.connect('clicked',
this._onFullscreenButtonClicked.bind(this)
);
this.extraButtonsBox.append(fullscreenButton);
this.menuWidget.append(this.extraButtonsBox);
buttonsBox.append(fullscreenButton);
this.pack_start(buttonsBox);
this.spacerWidget = new Gtk.Box({
hexpand: true,
});
this.minimizeWidget = this._getWindowButton('minimize');
this.maximizeWidget = this._getWindowButton('maximize');
this.closeWidget = this._getWindowButton('close');
const gtkSettings = Gtk.Settings.get_default();
this._onLayoutUpdate(gtkSettings);
gtkSettings.connect(
'notify::gtk-decoration-layout',
this._onLayoutUpdate.bind(this)
);
}
updateHeaderBar(title, subtitle)
setMenuOnLeft(isOnLeft)
{
this.titleLabel.label = title;
this.subtitleLabel.visible = (subtitle !== null);
if(this.isMenuOnLeft === isOnLeft)
return;
if(subtitle)
this.subtitleLabel.label = subtitle;
if(isOnLeft) {
this.menuWidget.reorder_child_after(
this.extraButtonsBox, this.menuButton
);
}
else {
this.menuWidget.reorder_child_after(
this.menuButton, this.extraButtonsBox
);
}
this.isMenuOnLeft = isOnLeft;
}
_createWidgetForWindow(window)
setMaximized(isMaximized)
{
const box = new Gtk.Box({
orientation: Gtk.Orientation.VERTICAL,
if(this.isMaximized === isMaximized)
return;
this.maximizeWidget.icon_name = (isMaximized)
? 'window-restore-symbolic'
: 'window-maximize-symbolic';
this.isMaximized = isMaximized;
}
_onLayoutUpdate(gtkSettings)
{
const gtkLayout = gtkSettings.gtk_decoration_layout;
this._replaceButtons(gtkLayout);
}
_replaceButtons(gtkLayout)
{
const modLayout = gtkLayout.replace(':', ',spacer,');
const layoutArr = modLayout.split(',');
let lastWidget = null;
let showMinimize = false;
let showMaximize = false;
let showClose = false;
let spacerAdded = false;
for(let name of layoutArr) {
const widget = this[`${name}Widget`];
if(!widget) continue;
if(!widget.parent)
this.append(widget);
else
this.reorder_child_after(widget, lastWidget);
switch(name) {
case 'spacer':
spacerAdded = true;
break;
case 'minimize':
showMinimize = true;
break;
case 'maximize':
showMaximize = true;
break;
case 'close':
showClose = true;
break;
case 'menu':
this.setMenuOnLeft(!spacerAdded);
break;
default:
break;
}
lastWidget = widget;
}
this.minimizeWidget.visible = showMinimize;
this.maximizeWidget.visible = showMaximize;
this.closeWidget.visible = showClose;
}
_getWindowButton(name)
{
const button = new Gtk.Button({
icon_name: `window-${name}-symbolic`,
valign: Gtk.Align.CENTER,
});
button.add_css_class('circular');
this.titleLabel = new Gtk.Label({
halign: Gtk.Align.CENTER,
single_line_mode: true,
ellipsize: Pango.EllipsizeMode.END,
width_chars: 5,
});
this.titleLabel.add_css_class('title');
this.titleLabel.set_parent(box);
const action = (name === 'maximize')
? 'window.toggle-maximized'
: 'window.' + name;
window.bind_property('title', this.titleLabel, 'label',
GObject.BindingFlags.SYNC_CREATE
button.connect('clicked',
this._onWindowButtonActivate.bind(this, action)
);
this.subtitleLabel = new Gtk.Label({
halign: Gtk.Align.CENTER,
single_line_mode: true,
ellipsize: Pango.EllipsizeMode.END,
});
this.subtitleLabel.add_css_class('subtitle');
this.subtitleLabel.set_parent(box);
this.subtitleLabel.visible = false;
return button;
}
return box;
_onWindowButtonActivate(action)
{
}
_onFloatButtonClicked()
@@ -105,12 +217,13 @@ class ClapperHeaderBarPopover extends Gtk.PopoverMenu
menu_model: model,
});
this.connect('map', this._onMap.bind(this));
this.connect('closed', this._onClosed.bind(this));
}
_onClosed()
_onMap()
{
const { child } = this.get_root();
const { child } = this.root;
if(
!child
@@ -119,6 +232,24 @@ class ClapperHeaderBarPopover extends Gtk.PopoverMenu
)
return;
child.revealControls();
child.isPopoverOpen = true;
}
_onClosed()
{
const { child } = this.root;
if(
!child
|| !child.player
|| !child.player.widget
)
return;
child.revealControls();
child.isPopoverOpen = false;
child.player.widget.grab_focus();
}
});

View File

@@ -14,10 +14,7 @@ class ClapperPlayer extends PlayerBase
{
super._init();
this.cursorInPlayer = false;
this.seek_done = true;
this.dragAllowed = false;
this.isWidgetDragging = false;
this.doneStartup = false;
this.needsFastSeekRestore = false;
@@ -25,55 +22,15 @@ class ClapperPlayer extends PlayerBase
this.quitOnStop = false;
this.needsTocUpdate = true;
this.posX = 0;
this.posY = 0;
this.keyPressCount = 0;
this._maxVolume = Misc.getLinearValue(Misc.maxVolume);
this._hideCursorTimeout = null;
this._hideControlsTimeout = null;
this._updateTimeTimeout = null;
const clickGesture = new Gtk.GestureClick();
clickGesture.set_button(0);
clickGesture.connect('pressed', this._onWidgetPressed.bind(this));
this.widget.add_controller(clickGesture);
const dragGesture = new Gtk.GestureDrag();
dragGesture.connect('drag-update', this._onWidgetDragUpdate.bind(this));
this.widget.add_controller(dragGesture);
const swipeGesture = new Gtk.GestureSwipe({
touch_only: true,
});
swipeGesture.connect('swipe', this._onWidgetSwipe.bind(this));
swipeGesture.connect('update', this._onWidgetSwipeUpdate.bind(this));
this.widget.add_controller(swipeGesture);
const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', this._onWidgetKeyPressed.bind(this));
keyController.connect('key-released', this._onWidgetKeyReleased.bind(this));
this.widget.add_controller(keyController);
const scrollController = new Gtk.EventControllerScroll();
scrollController.set_flags(Gtk.EventControllerScrollFlags.BOTH_AXES);
scrollController.connect('scroll', this._onScroll.bind(this));
this.widget.add_controller(scrollController);
const motionController = new Gtk.EventControllerMotion();
motionController.connect('enter', this._onWidgetEnter.bind(this));
motionController.connect('leave', this._onWidgetLeave.bind(this));
motionController.connect('motion', this._onWidgetMotion.bind(this));
this.widget.add_controller(motionController);
const dropTarget = new Gtk.DropTarget({
actions: Gdk.DragAction.COPY,
});
dropTarget.set_gtypes([GObject.TYPE_STRING]);
dropTarget.connect('drop', this._onDataDrop.bind(this));
this.widget.add_controller(dropTarget);
this.connect('state-changed', this._onStateChanged.bind(this));
this.connect('uri-loaded', this._onUriLoaded.bind(this));
this.connect('end-of-stream', this._onStreamEnded.bind(this));
@@ -301,100 +258,20 @@ class ClapperPlayer extends PlayerBase
}
}
getIsSwipeOk(velocity, otherVelocity)
{
if(!velocity)
return false;
const absVel = Math.abs(velocity);
if(absVel < 20 || Math.abs(otherVelocity) * 1.5 >= absVel)
return false;
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
return clapperWidget.fullscreenMode;
}
_setHideCursorTimeout()
{
this._clearTimeout('hideCursor');
this._hideCursorTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => {
this._hideCursorTimeout = null;
if(this.cursorInPlayer) {
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
const blankCursor = Gdk.Cursor.new_from_name('none', null);
this.widget.set_cursor(blankCursor);
clapperWidget.revealerTop.set_cursor(blankCursor);
}
return GLib.SOURCE_REMOVE;
});
}
_setHideControlsTimeout()
{
this._clearTimeout('hideControls');
this._hideControlsTimeout = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 3, () => {
this._hideControlsTimeout = null;
if(this.cursorInPlayer) {
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
if(clapperWidget.fullscreenMode || clapperWidget.floatingMode) {
this._clearTimeout('updateTime');
clapperWidget.revealControls(false);
}
}
return GLib.SOURCE_REMOVE;
});
}
_setUpdateTimeInterval()
{
this._clearTimeout('updateTime');
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
const nextUpdate = clapperWidget.updateTime();
if(nextUpdate === null)
return;
this._updateTimeTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, nextUpdate, () => {
this._updateTimeTimeout = null;
if(clapperWidget.fullscreenMode)
this._setUpdateTimeInterval();
return GLib.SOURCE_REMOVE;
});
}
_clearTimeout(name)
{
if(!this[`_${name}Timeout`])
return;
GLib.source_remove(this[`_${name}Timeout`]);
this[`_${name}Timeout`] = null;
if(name === 'updateTime')
debug('cleared update time interval');
}
_performCloseCleanup(window)
{
window.disconnect(this.closeRequestSignal);
this.closeRequestSignal = null;
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
if(!clapperWidget.fullscreenMode) {
if(!clapperWidget.isFullscreenMode && clapperWidget.controlsRevealer.child_revealed) {
const size = window.get_default_size();
if(size[0] > 0 && size[1] > 0)
clapperWidget._saveWindowSize(size);
if(size[0] > 0 && size[1] > 0) {
settings.set_string('window-size', JSON.stringify(size));
debug(`saved window size: ${size[0]}x${size[1]}`);
}
}
/* If "quitOnStop" is set here it means that we are in middle of autoclosing */
if(this.state !== GstClapper.ClapperState.STOPPED && !this.quitOnStop) {
@@ -495,7 +372,7 @@ class ClapperPlayer extends PlayerBase
if(settings.get_boolean('fullscreen-auto')) {
const root = player.widget.get_root();
const clapperWidget = root.get_child();
if(!clapperWidget.fullscreenMode) {
if(!clapperWidget.isFullscreenMode) {
this.playOnFullscreen = true;
root.fullscreen();
@@ -557,11 +434,8 @@ class ClapperPlayer extends PlayerBase
bool = true;
case Gdk.KEY_Left:
this.adjust_position(bool);
this._clearTimeout('hideControls');
if(this.keyPressCount > 1) {
clapperWidget.revealerBottom.set_can_focus(false);
clapperWidget.revealerBottom.revealChild(true);
}
if(this.keyPressCount > 1)
clapperWidget.revealControls();
break;
default:
break;
@@ -581,10 +455,8 @@ class ClapperPlayer extends PlayerBase
this.toggle_play();
break;
case Gdk.KEY_Return:
if(clapperWidget.fullscreenMode) {
if(clapperWidget.isFullscreenMode)
clapperWidget.revealControls(true);
this._setHideControlsTimeout();
}
break;
case Gdk.KEY_Right:
case Gdk.KEY_Left:
@@ -592,7 +464,7 @@ class ClapperPlayer extends PlayerBase
clapperWidget.controls.positionScale.get_value()
);
this.seek_seconds(value);
this._setHideControlsTimeout();
clapperWidget._setHideControlsTimeout();
break;
case Gdk.KEY_F11:
case Gdk.KEY_f:
@@ -600,7 +472,7 @@ class ClapperPlayer extends PlayerBase
clapperWidget.toggleFullscreen();
break;
case Gdk.KEY_Escape:
if(clapperWidget.fullscreenMode) {
if(clapperWidget.isFullscreenMode) {
root = this.widget.get_root();
root.unfullscreen();
}
@@ -615,193 +487,6 @@ class ClapperPlayer extends PlayerBase
}
}
_onWidgetPressed(gesture, nPress, x, y)
{
const button = gesture.get_current_button();
const isDouble = (nPress % 2 == 0);
this.dragAllowed = !isDouble;
switch(button) {
case Gdk.BUTTON_PRIMARY:
if(isDouble) {
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
clapperWidget.toggleFullscreen();
}
break;
case Gdk.BUTTON_SECONDARY:
this.toggle_play();
break;
default:
break;
}
}
_onWidgetEnter(controller, x, y)
{
this.cursorInPlayer = true;
this.isWidgetDragging = false;
this._setHideCursorTimeout();
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
if(clapperWidget.fullscreenMode || clapperWidget.floatingMode)
this._setHideControlsTimeout();
}
_onWidgetLeave(controller)
{
this.cursorInPlayer = false;
this._clearTimeout('hideCursor');
this._clearTimeout('hideControls');
}
_onWidgetMotion(controller, posX, posY)
{
this.cursorInPlayer = true;
/* GTK4 sometimes generates motions with same coords */
if(this.posX === posX && this.posY === posY)
return;
/* Do not show cursor on small movements */
if(
Math.abs(this.posX - posX) >= 0.5
|| Math.abs(this.posY - posY) >= 0.5
) {
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
const defaultCursor = Gdk.Cursor.new_from_name('default', null);
this.widget.set_cursor(defaultCursor);
clapperWidget.revealerTop.set_cursor(defaultCursor);
this._setHideCursorTimeout();
if(clapperWidget.floatingMode && !clapperWidget.fullscreenMode) {
clapperWidget.revealerBottom.set_can_focus(false);
clapperWidget.revealerBottom.revealChild(true);
this._setHideControlsTimeout();
}
else if(clapperWidget.fullscreenMode) {
if(!this._updateTimeTimeout)
this._setUpdateTimeInterval();
if(!clapperWidget.revealerTop.get_reveal_child()) {
/* Do not grab controls key focus on mouse movement */
clapperWidget.revealerBottom.set_can_focus(false);
clapperWidget.revealControls(true);
}
this._setHideControlsTimeout();
}
else {
if(this._hideControlsTimeout)
this._clearTimeout('hideControls');
if(this._updateTimeTimeout)
this._clearTimeout('updateTime');
}
}
this.posX = posX;
this.posY = posY;
}
_onWidgetDragUpdate(gesture, offsetX, offsetY)
{
if(!this.dragAllowed)
return;
const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
if(clapperWidget.fullscreenMode)
return;
const { gtk_double_click_distance } = this.widget.get_settings();
if (
Math.abs(offsetX) > gtk_double_click_distance
|| Math.abs(offsetY) > gtk_double_click_distance
) {
const [isActive, startX, startY] = gesture.get_start_point();
if(!isActive) return;
const native = this.widget.get_native();
if(!native) return;
let [isShared, winX, winY] = this.widget.translate_coordinates(
native, startX, startY
);
if(!isShared) return;
const [nativeX, nativeY] = native.get_surface_transform();
winX += nativeX;
winY += nativeY;
this.isWidgetDragging = true;
native.get_surface().begin_move(
gesture.get_device(),
gesture.get_current_button(),
winX,
winY,
gesture.get_current_event_time()
);
gesture.reset();
}
}
_onWidgetSwipe(gesture, velocityX, velocityY)
{
if(!this.getIsSwipeOk(velocityX, velocityY))
return;
this._onScroll(gesture, -velocityX, 0);
}
_onWidgetSwipeUpdate(gesture, sequence)
{
const [isCalc, velocityX, velocityY] = gesture.get_velocity();
if(!isCalc) return;
if(!this.getIsSwipeOk(velocityY, velocityX))
return;
const isIncrease = velocityY < 0;
this.adjust_volume(isIncrease, 0.01);
}
_onScroll(controller, dx, dy)
{
const isHorizontal = (Math.abs(dx) >= Math.abs(dy));
const isIncrease = (isHorizontal) ? dx < 0 : dy < 0;
if(isHorizontal) {
this.adjust_position(isIncrease);
const { controls } = this.widget.get_ancestor(Gtk.Grid);
const value = Math.round(controls.positionScale.get_value());
this.seek_seconds(value);
}
else
this.adjust_volume(isIncrease);
return true;
}
_onDataDrop(dropTarget, value, x, y)
{
const playlist = value.split(/\r?\n/).filter(uri => {
return Gst.uri_is_valid(uri);
});
if(!playlist.length)
return false;
this.set_playlist(playlist);
const { application } = this.widget.get_root();
application.activate();
return true;
}
_onCloseRequest(window)
{
this._performCloseCleanup(window);

View File

@@ -29,8 +29,7 @@ class ClapperPlayerBase extends GstClapper.Clapper
});
this.widget = gtk4plugin.video_sink.widget;
this.widget.vexpand = true;
this.widget.hexpand = true;
this.widget.add_css_class('videowidget');
this.state = GstClapper.ClapperState.STOPPED;
this.visualization_enabled = false;

View File

@@ -73,6 +73,9 @@ class ClapperBehaviourPage extends PrefsBase.Grid
this.addTitle('Resume');
this.addCheckButton('Ask to resume last unfinished video', 'resume-enabled');
this.addTitle('Floating Mode');
this.addCheckButton('Show on all workspaces', 'floating-stick');
}
});

View File

@@ -1,9 +1,11 @@
const { GLib, GObject, Gtk, Pango } = imports.gi;
const { HeaderBar } = imports.src.headerbar;
const Debug = imports.src.debug;
const REVEAL_TIME = 800;
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
@@ -15,74 +17,24 @@ class ClapperCustomRevealer extends Gtk.Revealer
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(isReveal) {
this._clearTimeout();
this.set_visible(isReveal);
}
else
this._setHideTimeout();
if(isReveal)
this.visible = true;
/* 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;
this.reveal_child = isReveal;
}
});
@@ -92,55 +44,108 @@ class ClapperRevealerTop extends CustomRevealer
_init()
{
super._init({
transition_duration: REVEAL_TIME,
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.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,
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,
margin_top: 14,
margin_start: 12,
xalign: 0,
yalign: 0,
});
const timeLabelOpts = {
margin_end: 10,
xalign: 1,
yalign: 0,
};
this.currentTime = new Gtk.Label(timeLabelOpts);
this.currentTime.add_css_class('osdtime');
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);
timeLabelOpts.visible = false;
this.endTime = new Gtk.Label(timeLabelOpts);
this.endTime.add_css_class('osdendtime');
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.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(revealerBox);
this.set_child(this.revealerGrid);
this.connect('notify::child-revealed', this._onTopRevealed.bind(this));
}
setMediaTitle(title)
set title(text)
{
this.mediaTitle.label = title;
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)
@@ -159,6 +164,31 @@ class ClapperRevealerTop extends CustomRevealer
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;
/* Changing transition in middle can have dire consequences,
so change only when not in transition */
if(this.reveal_child === this.child_revealed)
this.transition_type = this._requestedTransition;
}
_onTopRevealed()
{
if(this.transition_type !== this._requestedTransition)
this.transition_type = this._requestedTransition;
}
});
var RevealerBottom = GObject.registerClass(
@@ -167,16 +197,25 @@ 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 = new Gtk.Box({
orientation: Gtk.Orientation.HORIZONTAL,
margin_start: 8,
margin_end: 8,
margin_bottom: 8,
});
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)
@@ -189,44 +228,79 @@ class ClapperRevealerBottom extends CustomRevealer
this.revealerBox.remove(widget);
}
setFloatingClass(isFloating)
_onMotion(controller, x, y)
{
if(isFloating === this.revealerBox.has_css_class('floatingcontrols'))
return;
const clapperWidget = this.root.child;
clapperWidget._clearTimeout('hideControls');
}
});
const action = (isFloating) ? 'add' : 'remove';
this.revealerBox[`${action}_css_class`]('floatingcontrols');
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));
}
set_visible(isVisible)
toggleReveal()
{
const isChange = super.set_visible(isVisible);
if(!isChange || !this.can_focus) return;
/* Prevent interrupting transition */
if(this.reveal_child !== this.child_revealed)
return;
const parent = this.get_parent();
const playerWidget = parent.get_first_child();
if(!playerWidget) return;
const { widget } = this.root.child.player;
if(isVisible) {
const box = this.get_first_child();
if(!box) return;
if(this.child_revealed) {
const [width] = this.root.get_default_size();
const height = widget.get_height();
const controls = box.get_first_child();
if(!controls) return;
const togglePlayButton = controls.get_first_child();
if(togglePlayButton) {
togglePlayButton.grab_focus();
debug('focus moved to toggle play button');
}
playerWidget.set_can_focus(false);
this.add_tick_callback(
this._onUnrevealTick.bind(this, widget, width, height)
);
}
else {
playerWidget.set_can_focus(true);
playerWidget.grab_focus();
debug('focus moved to player widget');
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;
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(
@@ -251,18 +325,6 @@ class ClapperButtonsRevealer extends Gtk.Revealer
}
}
set_reveal_child(isReveal)
{
if(this.reveal_child === isReveal)
return;
const grandson = this.child.get_first_child();
if(grandson && grandson.isFloating && !grandson.isFullscreen)
return;
super.set_reveal_child(isReveal);
}
append(widget)
{
this.get_child().append(widget);

View File

@@ -1,4 +1,4 @@
const { Gdk, GLib, GObject, GstClapper, Gtk } = imports.gi;
const { Gdk, GLib, GObject, Gst, GstClapper, Gtk } = imports.gi;
const { Controls } = imports.src.controls;
const Debug = imports.src.debug;
const Dialogs = imports.src.dialogs;
@@ -20,15 +20,27 @@ class ClapperWidget extends Gtk.Grid
* separately as a pre-made GTK widget */
Misc.loadCustomCss();
this.posX = 0;
this.posY = 0;
this.windowSize = JSON.parse(settings.get_string('window-size'));
this.floatSize = JSON.parse(settings.get_string('float-size'));
this.layoutWidth = 0;
this.fullscreenMode = false;
this.floatingMode = false;
this.isFullscreenMode = false;
this.isSeekable = false;
this.isMobileMonitor = false;
this.isDragAllowed = false;
this.isSwipePerformed = false;
this.isCursorInPlayer = false;
this.isPopoverOpen = false;
this._hideControlsTimeout = null;
this._updateTimeTimeout = null;
this.needsTracksUpdate = true;
this.needsCursorRestore = false;
this.overlay = new Gtk.Overlay();
this.revealerTop = new Revealers.RevealerTop();
@@ -41,12 +53,17 @@ class ClapperWidget extends Gtk.Grid
this.controlsBox.add_css_class('controlsbox');
this.controlsBox.append(this.controls);
this.controlsRevealer = new Revealers.ControlsRevealer();
this.controlsRevealer.set_child(this.controlsBox);
this.attach(this.overlay, 0, 0, 1, 1);
this.attach(this.controlsBox, 0, 1, 1, 1);
this.attach(this.controlsRevealer, 0, 1, 1, 1);
this.mapSignal = this.connect('map', this._onMap.bind(this));
this.player = new Player();
const playerWidget = this.player.widget;
this.controls.elapsedButton.scrolledWindow.set_child(this.player.playlistWidget);
this.controls.speedAdjustment.bind_property(
'value', this.player, 'rate', GObject.BindingFlags.BIDIRECTIONAL
@@ -58,39 +75,52 @@ class ClapperWidget extends Gtk.Grid
/* FIXME: re-enable once ported to new GstPlayer API with messages bus */
//this.player.connect('volume-changed', this._onPlayerVolumeChanged.bind(this));
this.overlay.set_child(this.player.widget);
this.overlay.set_child(playerWidget);
this.overlay.add_overlay(this.revealerTop);
this.overlay.add_overlay(this.revealerBottom);
const motionController = new Gtk.EventControllerMotion();
motionController.connect('leave', this._onLeave.bind(this));
this.add_controller(motionController);
const clickGesture = this._getClickGesture();
playerWidget.add_controller(clickGesture);
const clickGestureTop = this._getClickGesture();
this.revealerTop.add_controller(clickGestureTop);
const topClickGesture = new Gtk.GestureClick();
topClickGesture.set_button(0);
topClickGesture.connect('pressed', this.player._onWidgetPressed.bind(this.player));
this.revealerTop.add_controller(topClickGesture);
const dragGesture = this._getDragGesture();
playerWidget.add_controller(dragGesture);
const dragGestureTop = this._getDragGesture();
this.revealerTop.add_controller(dragGestureTop);
const topMotionController = new Gtk.EventControllerMotion();
topMotionController.connect('motion', this.player._onWidgetMotion.bind(this.player));
this.revealerTop.add_controller(topMotionController);
const swipeGesture = this._getSwipeGesture();
playerWidget.add_controller(swipeGesture);
const swipeGestureTop = this._getSwipeGesture();
this.revealerTop.add_controller(swipeGestureTop);
const topScrollController = new Gtk.EventControllerScroll();
topScrollController.set_flags(Gtk.EventControllerScrollFlags.BOTH_AXES);
topScrollController.connect('scroll', this.player._onScroll.bind(this.player));
this.revealerTop.add_controller(topScrollController);
const scrollController = this._getScrollController();
playerWidget.add_controller(scrollController);
const scrollControllerTop = this._getScrollController();
this.revealerTop.add_controller(scrollControllerTop);
const motionController = this._getMotionController();
playerWidget.add_controller(motionController);
const motionControllerTop = this._getMotionController();
this.revealerTop.add_controller(motionControllerTop);
const dropTarget = this._getDropTarget();
playerWidget.add_controller(dropTarget);
const dropTargetTop = this._getDropTarget();
this.revealerTop.add_controller(dropTargetTop);
}
revealControls(isReveal)
revealControls(isAllowInput)
{
for(let pos of ['Top', 'Bottom'])
this[`revealer${pos}`].revealChild(isReveal);
}
this._checkSetUpdateTimeInterval();
showControls(isShow)
{
for(let pos of ['Top', 'Bottom'])
this[`revealer${pos}`].showChild(isShow);
this.revealerTop.revealChild(true);
this.revealerBottom.revealChild(true);
if(isAllowInput)
this.setControlsCanFocus(true);
this._setHideControlsTimeout();
}
toggleFullscreen()
@@ -98,98 +128,55 @@ class ClapperWidget extends Gtk.Grid
const root = this.get_root();
if(!root) return;
const un = (this.fullscreenMode) ? 'un' : '';
const un = (this.isFullscreenMode) ? 'un' : '';
root[`${un}fullscreen`]();
}
setFullscreenMode(isFullscreen)
{
if(this.fullscreenMode === isFullscreen)
if(this.isFullscreenMode === isFullscreen)
return;
this.fullscreenMode = isFullscreen;
debug('changing fullscreen mode');
this.isFullscreenMode = isFullscreen;
const root = this.get_root();
const action = (isFullscreen) ? 'add' : 'remove';
root[action + '_css_class']('gpufriendlyfs');
if(!this.floatingMode)
this._changeControlsPlacement(isFullscreen);
else {
this._setWindowFloating(!isFullscreen);
this.revealerBottom.setFloatingClass(!isFullscreen);
this.controls.setFloatingMode(!isFullscreen);
this.controls.unfloatButton.set_visible(!isFullscreen);
}
if(!this.isMobileMonitor)
root[action + '_css_class']('tvmode');
if(!isFullscreen)
this._clearTimeout('updateTime');
this._changeControlsPlacement(isFullscreen);
this.controls.setFullscreenMode(isFullscreen);
this.showControls(isFullscreen);
this.player.widget.grab_focus();
this.revealerTop.setFullscreenMode(isFullscreen, this.isMobileMonitor);
if(this.revealerTop.child_revealed)
this._checkSetUpdateTimeInterval();
this.setControlsCanFocus(false);
if(this.player.playOnFullscreen && isFullscreen) {
this.player.playOnFullscreen = false;
this.player.play();
}
debug(`interface in fullscreen mode: ${isFullscreen}`);
}
setFloatingMode(isFloating)
setControlsCanFocus(isControlsFocus)
{
if(this.floatingMode === isFloating)
return;
this.revealerBottom.can_focus = isControlsFocus;
this.player.widget.can_focus = !isControlsFocus;
const root = this.get_root();
const size = root.get_default_size();
const focusWidget = (isControlsFocus)
? this.controls.togglePlayButton
: this.player.widget;
this._saveWindowSize(size);
if(isFloating) {
this.windowSize = size;
this.player.widget.set_size_request(192, 108);
}
else {
this.floatSize = size;
this.player.widget.set_size_request(-1, -1);
}
this.floatingMode = isFloating;
this.revealerBottom.setFloatingClass(isFloating);
this._changeControlsPlacement(isFloating);
this.controls.setFloatingMode(isFloating);
this.controls.unfloatButton.set_visible(isFloating);
this._setWindowFloating(isFloating);
const resize = (isFloating)
? this.floatSize
: this.windowSize;
root.set_default_size(resize[0], resize[1]);
debug(`resized window: ${resize[0]}x${resize[1]}`);
this.revealerBottom.showChild(false);
this.player.widget.grab_focus();
}
_setWindowFloating(isFloating)
{
const root = this.get_root();
const cssClass = 'floatingwindow';
if(isFloating === root.has_css_class(cssClass))
return;
const action = (isFloating) ? 'add' : 'remove';
root[action + '_css_class'](cssClass);
}
_saveWindowSize(size)
{
const rootName = (this.floatingMode)
? 'float'
: 'window';
settings.set_string(`${rootName}-size`, JSON.stringify(size));
debug(`saved ${rootName} size: ${size[0]}x${size[1]}`);
focusWidget.grab_focus();
}
_changeControlsPlacement(isOnTop)
@@ -212,8 +199,8 @@ class ClapperWidget extends Gtk.Grid
if(!mediaInfo)
return GLib.SOURCE_REMOVE;
/* Set titlebar media title and path */
this.updateTitles(mediaInfo);
/* Set titlebar media title */
this.updateTitle(mediaInfo);
/* Show/hide position scale on LIVE */
const isLive = mediaInfo.is_live();
@@ -322,26 +309,21 @@ class ClapperWidget extends Gtk.Grid
return GLib.SOURCE_REMOVE;
}
updateTitles(mediaInfo)
updateTitle(mediaInfo)
{
let title = mediaInfo.get_title();
let subtitle = this.player.playlistWidget.getActiveFilename();
if(!title) {
const subtitle = this.player.playlistWidget.getActiveFilename();
title = (subtitle.includes('.'))
? subtitle.split('.').slice(0, -1).join('.')
: subtitle;
subtitle = null;
}
const root = this.get_root();
const headerbar = root.get_titlebar();
if(headerbar && headerbar.updateHeaderBar)
headerbar.updateHeaderBar(title, subtitle);
this.revealerTop.setMediaTitle(title);
this.root.title = title;
this.revealerTop.title = title;
this.revealerTop.showTitle = true;
}
updateTime()
@@ -564,15 +546,16 @@ class ClapperWidget extends Gtk.Grid
_onStateNotify(toplevel)
{
const isMaximized = Boolean(
toplevel.state & Gdk.ToplevelState.MAXIMIZED
);
const isFullscreen = Boolean(
toplevel.state & Gdk.ToplevelState.FULLSCREEN
);
const headerBar = this.revealerTop.headerBar;
if(this.fullscreenMode === isFullscreen)
return;
headerBar.setMaximized(isMaximized);
this.setFullscreenMode(isFullscreen);
debug(`interface in fullscreen mode: ${isFullscreen}`);
}
_onLayoutUpdate(surface, width, height)
@@ -584,18 +567,6 @@ class ClapperWidget extends Gtk.Grid
this.controls._onPlayerResize(width, height);
}
_onLeave(controller)
{
if(
this.fullscreenMode
|| !this.floatingMode
|| this.player.isWidgetDragging
)
return;
this.revealerBottom.revealChild(false);
}
_onMap()
{
this.disconnect(this.mapSignal);
@@ -616,11 +587,327 @@ class ClapperWidget extends Gtk.Grid
const monitorWidth = Math.max(geometry.width, geometry.height);
if(monitorWidth < 1280) {
this.controls.isMobileMonitor = true;
this.isMobileMonitor = true;
debug('mobile monitor detected');
}
surface.connect('notify::state', this._onStateNotify.bind(this));
surface.connect('layout', this._onLayoutUpdate.bind(this));
}
_clearTimeout(name)
{
if(!this[`_${name}Timeout`])
return;
GLib.source_remove(this[`_${name}Timeout`]);
this[`_${name}Timeout`] = null;
if(name === 'updateTime')
debug('cleared update time interval');
}
_setHideControlsTimeout()
{
this._clearTimeout('hideControls');
let time = 2500;
if(this.isFullscreenMode && !this.isMobileMonitor)
time += 1500;
this._hideControlsTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, time, () => {
this._hideControlsTimeout = null;
if(this.isCursorInPlayer) {
const blankCursor = Gdk.Cursor.new_from_name('none', null);
this.player.widget.set_cursor(blankCursor);
this.revealerTop.set_cursor(blankCursor);
this.needsCursorRestore = true;
}
if(!this.isPopoverOpen) {
this._clearTimeout('updateTime');
this.revealerTop.revealChild(false);
this.revealerBottom.revealChild(false);
}
this.setControlsCanFocus(false);
return GLib.SOURCE_REMOVE;
});
}
_checkSetUpdateTimeInterval()
{
if(
this.isFullscreenMode
&& !this.isMobileMonitor
&& !this._updateTimeTimeout
) {
this._setUpdateTimeInterval();
}
}
_setUpdateTimeInterval()
{
this._clearTimeout('updateTime');
const nextUpdate = this.updateTime();
if(nextUpdate === null)
return;
this._updateTimeTimeout = GLib.timeout_add(GLib.PRIORITY_DEFAULT, nextUpdate, () => {
this._updateTimeTimeout = null;
if(this.isFullscreenMode)
this._setUpdateTimeInterval();
return GLib.SOURCE_REMOVE;
});
}
_getClickGesture()
{
const clickGesture = new Gtk.GestureClick();
clickGesture.set_button(0);
clickGesture.connect('pressed', this._onPressed.bind(this));
clickGesture.connect('released', this._onReleased.bind(this));
return clickGesture;
}
_getDragGesture()
{
const dragGesture = new Gtk.GestureDrag();
dragGesture.connect('drag-update', this._onDragUpdate.bind(this));
return dragGesture;
}
_getSwipeGesture()
{
const swipeGesture = new Gtk.GestureSwipe({
touch_only: true,
});
swipeGesture.connect('swipe', this._onSwipe.bind(this));
swipeGesture.connect('update', this._onSwipeUpdate.bind(this));
return swipeGesture;
}
_getScrollController()
{
const scrollController = new Gtk.EventControllerScroll();
scrollController.set_flags(Gtk.EventControllerScrollFlags.BOTH_AXES);
scrollController.connect('scroll', this._onScroll.bind(this));
return scrollController;
}
_getMotionController()
{
const motionController = new Gtk.EventControllerMotion();
motionController.connect('enter', this._onEnter.bind(this));
motionController.connect('leave', this._onLeave.bind(this));
motionController.connect('motion', this._onMotion.bind(this));
return motionController;
}
_getDropTarget()
{
const dropTarget = new Gtk.DropTarget({
actions: Gdk.DragAction.COPY,
});
dropTarget.set_gtypes([GObject.TYPE_STRING]);
dropTarget.connect('drop', this._onDataDrop.bind(this));
return dropTarget;
}
_getIsSwipeOk(velocity, otherVelocity)
{
if(!velocity)
return false;
const absVel = Math.abs(velocity);
if(absVel < 20 || Math.abs(otherVelocity) * 1.5 >= absVel)
return false;
return this.isFullscreenMode;
}
_onPressed(gesture, nPress, x, y)
{
const button = gesture.get_current_button();
const isDouble = (nPress % 2 == 0);
this.isDragAllowed = !isDouble;
this.isSwipePerformed = false;
switch(button) {
case Gdk.BUTTON_PRIMARY:
if(isDouble)
this.toggleFullscreen();
break;
case Gdk.BUTTON_SECONDARY:
this.player.toggle_play();
break;
default:
break;
}
}
_onReleased(gesture, nPress, x, y)
{
/* Reveal if touch was not a swipe or was already revealed */
if(!this.isSwipePerformed || this.revealerBottom.child_revealed) {
const { source } = gesture.get_device();
switch(source) {
case Gdk.InputSource.PEN:
case Gdk.InputSource.TOUCHSCREEN:
this.revealControls();
break;
default:
break;
}
}
}
_onDragUpdate(gesture, offsetX, offsetY)
{
if(!this.isDragAllowed || this.isFullscreenMode)
return;
const { gtk_double_click_distance } = this.get_settings();
if (
Math.abs(offsetX) > gtk_double_click_distance
|| Math.abs(offsetY) > gtk_double_click_distance
) {
const [isActive, startX, startY] = gesture.get_start_point();
if(!isActive) return;
const playerWidget = this.player.widget;
const native = playerWidget.get_native();
if(!native) return;
let [isShared, winX, winY] = playerWidget.translate_coordinates(
native, startX, startY
);
if(!isShared) return;
const [nativeX, nativeY] = native.get_surface_transform();
winX += nativeX;
winY += nativeY;
native.get_surface().begin_move(
gesture.get_device(),
gesture.get_current_button(),
winX,
winY,
gesture.get_current_event_time()
);
gesture.reset();
}
}
_onSwipe(gesture, velocityX, velocityY)
{
if(!this._getIsSwipeOk(velocityX, velocityY))
return;
this._onScroll(gesture, -velocityX, 0);
this.isSwipePerformed = true;
}
_onSwipeUpdate(gesture, sequence)
{
const [isCalc, velocityX, velocityY] = gesture.get_velocity();
if(!isCalc) return;
if(!this._getIsSwipeOk(velocityY, velocityX))
return;
const isIncrease = velocityY < 0;
this.player.adjust_volume(isIncrease, 0.01);
this.isSwipePerformed = true;
}
_onScroll(controller, dx, dy)
{
const isHorizontal = (Math.abs(dx) >= Math.abs(dy));
const isIncrease = (isHorizontal) ? dx < 0 : dy < 0;
if(isHorizontal) {
this.player.adjust_position(isIncrease);
const value = Math.round(this.controls.positionScale.get_value());
this.player.seek_seconds(value);
}
else
this.player.adjust_volume(isIncrease);
return true;
}
_onEnter(controller, x, y)
{
this.isCursorInPlayer = true;
}
_onLeave(controller)
{
if(this.isFullscreenMode)
return;
this.isCursorInPlayer = false;
}
_onMotion(controller, posX, posY)
{
this.isCursorInPlayer = true;
/* GTK4 sometimes generates motions with same coords */
if(this.posX === posX && this.posY === posY)
return;
/* Do not show cursor on small movements */
if(
Math.abs(this.posX - posX) >= 0.5
|| Math.abs(this.posY - posY) >= 0.5
) {
if(this.needsCursorRestore) {
const defaultCursor = Gdk.Cursor.new_from_name('default', null);
this.player.widget.set_cursor(defaultCursor);
this.revealerTop.set_cursor(defaultCursor);
this.needsCursorRestore = false;
}
this.revealControls();
}
this.posX = posX;
this.posY = posY;
}
_onDataDrop(dropTarget, value, x, y)
{
const playlist = value.split(/\r?\n/).filter(uri => {
return Gst.uri_is_valid(uri);
});
if(!playlist.length)
return false;
this.player.set_playlist(playlist);
this.root.application.activate();
return true;
}
});