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 # 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"> <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> <b>Windowed Mode</b>
</p> </p>
<p align="center"> <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> <b>Fullscreen Mode</b>
</p> </p>
<p align="center"> <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> <b>Floating Mode</b>
</p> </p>
### WORK IN PROGRESS
This is still early WIP. Many features are not implemented yet and quite a few are still unstable.
### Features: ### Features:
* [Hardware acceleration](https://github.com/Rafostar/clapper/wiki/Hardware-acceleration) * [Hardware acceleration](https://github.com/Rafostar/clapper/wiki/Hardware-acceleration)
* [Floating mode](https://github.com/Rafostar/clapper/wiki/Floating-mode) * [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) * [Playlist from file](https://github.com/Rafostar/clapper/wiki/Playlists)
* Chapters on progress bar * Chapters on progress bar

View File

@@ -5,41 +5,93 @@ scale marks {
radio { radio {
margin-left: -2px; 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 */ /* Adwaita is missing osd ListBox */
.osd list { .osd list {
background: none; background: none;
} }
.osd list row {
-gtk-icon-shadow: none;
}
.gtk402 trough highlight { .gtk402 trough highlight {
border-color: transparent; border-color: transparent;
} }
.gtk402 .osd trough highlight { .gtk402 .osd trough highlight {
border-color: inherit; 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; -gtk-icon-size: 24px;
} }
.playbackicon { .playbackicon {
-gtk-icon-size: 20px; -gtk-icon-size: 20px;
} }
.osd .playbackicon { .tvmode .playbackicon {
-gtk-icon-size: 28px; -gtk-icon-size: 28px;
} }
.labelbutton { .labelbutton {
@@ -51,32 +103,33 @@ radio {
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
font-weight: 600; font-weight: 600;
} }
.osd .labelbutton { .tvmode .labelbutton {
margin-top: 0px; margin-top: 0px;
font-size: 23px; font-size: 23px;
text-shadow: none; text-shadow: none;
} }
.reavealertop {
min-height: 88px; /* Top Revealer */
box-shadow: inset 0px 200px 10px -132px rgba(0,0,0,0.4); .tvmode .revealertopgrid {
font-family: 'Cantarell', sans-serif; font-family: 'Cantarell', sans-serif;
}
.tvmode .tvtitle {
font-size: 28px; font-size: 28px;
font-weight: 500; font-weight: 500;
text-shadow: none; text-shadow: none;
background: transparent;
} }
.osdtime { .tvtime {
margin-top: -2px; margin-top: -2px;
margin-right: -4px; margin-bottom: -2px;
min-width: 4px; min-height: 4px;
font-size: 38px; font-size: 38px;
font-weight: 700; font-weight: 700;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
.osdendtime { .tvendtime {
margin-top: 36px; margin-top: -4px;
margin-right: -4px; margin-bottom: 2px;
min-width: 4px; min-height: 6px;
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
@@ -93,13 +146,16 @@ radio {
margin-top: -2px; margin-top: -2px;
margin-bottom: -2px; margin-bottom: -2px;
} }
.osd .positionscale { .tvmode .positionscale {
margin-top: -1px; margin-top: -1px;
} }
.positionscale trough highlight { .positionscale trough highlight {
min-height: 4px; min-height: 4px;
} }
.osd .positionscale trough slider { .osd .positionscale trough highlight {
min-height: 6px;
}
.tvmode .positionscale trough slider {
color: transparent; color: transparent;
background: transparent; background: transparent;
border-color: transparent; border-color: transparent;
@@ -111,11 +167,11 @@ radio {
.positionscale.fine-tune mark indicator { .positionscale.fine-tune mark indicator {
min-height: 6px; min-height: 6px;
} }
.osd .positionscale mark indicator { .tvmode .positionscale mark indicator {
min-height: 7px; min-height: 7px;
min-width: 2px; min-width: 2px;
} }
.osd .positionscale.fine-tune mark indicator { .tvmode .positionscale.fine-tune mark indicator {
min-height: 7px; min-height: 7px;
min-width: 2px; min-width: 2px;
} }
@@ -127,17 +183,17 @@ radio {
margin-top: 4px; margin-top: 4px;
margin-bottom: -6px; margin-bottom: -6px;
} }
.osd .positionscale marks.top { .tvmode .positionscale marks.top {
margin-bottom: 2px; margin-bottom: 2px;
} }
.osd .positionscale marks.bottom { .tvmode .positionscale marks.bottom {
margin-top: 2px; margin-top: 2px;
} }
.osd .positionscale trough highlight { .tvmode .positionscale trough highlight {
border-radius: 3px; border-radius: 3px;
min-height: 20px; min-height: 20px;
} }
.osd .positionscale.fine-tune trough highlight { .tvmode .positionscale.fine-tune trough highlight {
border-radius: 3px; border-radius: 3px;
min-height: 20px; min-height: 20px;
} }
@@ -149,7 +205,7 @@ radio {
margin-right: -6px; margin-right: -6px;
min-height: 180px; min-height: 180px;
} }
.osd .volumescale { .tvmode .volumescale {
margin: 2px; margin: 2px;
margin-left: -6px; margin-left: -6px;
margin-right: -4px; margin-right: -4px;
@@ -160,7 +216,7 @@ radio {
margin-top: -4px; margin-top: -4px;
margin-bottom: -6px; margin-bottom: -6px;
} }
.osd .volumescale trough highlight { .tvmode .volumescale trough highlight {
min-width: 6px; min-width: 6px;
} }
.overamp trough highlight { .overamp trough highlight {
@@ -168,57 +224,19 @@ radio {
} }
/* Elapsed Popover */ /* Elapsed Popover */
.osd list row {
-gtk-icon-shadow: none;
}
.elapsedpopoverbox { .elapsedpopoverbox {
min-width: 260px; min-width: 260px;
} }
.elapsedpopoverbox box separator { .elapsedpopoverbox box separator {
background: @insensitive_fg_color; background: @insensitive_fg_color;
} }
.osd .elapsedpopoverbox { .tvmode .elapsedpopoverbox {
min-width: 360px; min-width: 360px;
} }
.osd .speedscale trough highlight { .tvmode .speedscale trough highlight {
min-height: 6px; 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 { .narrowbutton {
min-width: 8px; min-width: 8px;
} }
@@ -238,17 +256,12 @@ radio {
.chapterlabel { .chapterlabel {
min-width: 32px; min-width: 32px;
} }
.osd .chapterlabel { .tvmode .chapterlabel {
min-width: 40px; min-width: 40px;
text-shadow: none; text-shadow: none;
font-size: 21px; font-size: 21px;
font-weight: 500; font-weight: 500;
} }
.osd.floatingcontrols .chapterlabel {
font: inherit;
font-size: 100%;
min-width: 32px;
}
/* Preferences */ /* Preferences */
.prefsnotebook grid { .prefsnotebook grid {

View File

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

View File

@@ -4,21 +4,23 @@
<metadata_license>CC0-1.0</metadata_license> <metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0-or-later</project_license> <project_license>GPL-3.0-or-later</project_license>
<name>Clapper</name> <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> <translation type="gettext">com.github.rafostar.Clapper</translation>
<launchable type="desktop-id">com.github.rafostar.Clapper.desktop</launchable> <launchable type="desktop-id">com.github.rafostar.Clapper.desktop</launchable>
<description> <description>
<p> <p>
Clapper is a GNOME media player build using GJS with GTK4 toolkit. Clapper is a GNOME media player build using GJS with GTK4 toolkit.
The media player is using GStreamer GstPlayer API as a media backend The media player is using GStreamer as a media backend and renders
and renders everything via OpenGL. Player works natively on both everything via OpenGL. Player works natively on both Xorg and Wayland.
Xorg and Wayland. It also supports VA-API on AMD/Intel GPUs. It also supports VA-API on AMD/Intel GPUs.
</p> </p>
<p> <p>
The media player has an adaptive GUI. When viewing videos in "Windowed Mode", 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. 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 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> </p>
</description> </description>
<developer_name>Rafał Dzięgiel</developer_name> <developer_name>Rafał Dzięgiel</developer_name>
@@ -32,13 +34,16 @@
</categories> </categories>
<screenshots> <screenshots>
<screenshot type="default"> <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>
<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>
<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> </screenshot>
</screenshots> </screenshots>
<releases> <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", "--share=network",
"--device=all", "--device=all",
"--filesystem=xdg-videos", "--filesystem=xdg-videos",
"--talk-name=org.gnome.Shell",
"--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0", "--env=GST_PLUGIN_SYSTEM_PATH=/app/lib/gstreamer-1.0",
"--env=GST_VAAPI_ALL_DRIVERS=1" "--env=GST_VAAPI_ALL_DRIVERS=1"
], ],

View File

@@ -18,7 +18,8 @@
{ {
"type": "git", "type": "git",
"url": "https://gitlab.gnome.org/GNOME/gtk.git", "url": "https://gitlab.gnome.org/GNOME/gtk.git",
"commit": "ec94ec0286041c16d6df7b496b0760a0ae0885ba" "tag": "4.1.0",
"commit": "65c38111f958bac66d578e3f81964ca3857105c1"
}, },
{ {
"type": "patch", "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 { AppBase } = imports.src.appBase;
const { HeaderBar } = imports.src.headerbar;
const { Widget } = imports.src.widget; const { Widget } = imports.src.widget;
const Debug = imports.src.debug; const Debug = imports.src.debug;
@@ -23,14 +22,20 @@ class ClapperApp extends AppBase
{ {
super.vfunc_startup(); super.vfunc_startup();
this.active_window.isClapperApp = true; const window = this.active_window;
this.active_window.add_css_class('nobackground');
window.isClapperApp = true;
window.add_css_class('nobackground');
const clapperWidget = new Widget(); const clapperWidget = new Widget();
this.active_window.set_child(clapperWidget); window.set_child(clapperWidget);
const headerBar = new HeaderBar(this.active_window); const dummyHeaderbar = new Gtk.Box({
this.active_window.set_titlebar(headerBar); can_focus: false,
focusable: false,
visible: false,
});
window.set_titlebar(dummyHeaderbar);
} }
vfunc_open(files, hint) vfunc_open(files, hint)

View File

@@ -95,6 +95,12 @@ class ClapperAppBase extends Gtk.Application
const theme = gtkSettings.gtk_theme_name; const theme = gtkSettings.gtk_theme_name;
debug(`user selected theme: ${theme}`); 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')) if(!theme.endsWith('-dark'))
return; return;

View File

@@ -18,11 +18,7 @@ class ClapperCustomButton extends Gtk.Button
super._init(opts); super._init(opts);
this.floatUnaffected = false;
this.wantedVisible = true;
this.isFullscreen = false; this.isFullscreen = false;
this.isFloating = false;
this.add_css_class('flat'); this.add_css_class('flat');
} }
@@ -43,60 +39,24 @@ class ClapperCustomButton extends Gtk.Button
this.isFullscreen = isFullscreen; 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() vfunc_clicked()
{ {
if(!this.isFullscreen) if(!this.isFullscreen)
return; return;
const { player } = this.get_ancestor(Gtk.Grid); const clapperWidget = this.get_ancestor(Gtk.Grid);
player._setHideControlsTimeout(); clapperWidget.revealControls();
}
});
var IconButton = GObject.registerClass(
class ClapperIconButton extends CustomButton
{
_init(icon)
{
super._init({
icon_name: icon,
});
this.floatUnaffected = true;
} }
}); });
var IconToggleButton = GObject.registerClass( var IconToggleButton = GObject.registerClass(
class ClapperIconToggleButton extends IconButton class ClapperIconToggleButton extends CustomButton
{ {
_init(primaryIcon, secondaryIcon) _init(primaryIcon, secondaryIcon)
{ {
super._init(primaryIcon); super._init({
icon_name: primaryIcon,
});
this.primaryIcon = primaryIcon; this.primaryIcon = primaryIcon;
this.secondaryIcon = secondaryIcon; this.secondaryIcon = secondaryIcon;
@@ -114,11 +74,20 @@ class ClapperIconToggleButton extends IconButton
}); });
var PopoverButtonBase = GObject.registerClass( var PopoverButtonBase = GObject.registerClass(
class ClapperPopoverButtonBase extends CustomButton class ClapperPopoverButtonBase extends Gtk.ToggleButton
{ {
_init() _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({ this.popover = new Gtk.Popover({
position: Gtk.PositionType.TOP, position: Gtk.PositionType.TOP,
@@ -142,7 +111,16 @@ class ClapperPopoverButtonBase extends CustomButton
if(this.isFullscreen === isFullscreen) if(this.isFullscreen === isFullscreen)
return; 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); this.popover.set_offset(0, -this.margin_top);
@@ -154,20 +132,33 @@ class ClapperPopoverButtonBase extends CustomButton
this.popover[action + '_css_class'](cssClass); 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(); this.popover.popup();
} }
_onClosed() _onClosed()
{ {
const { player } = this.get_ancestor(Gtk.Grid); const clapperWidget = this.get_ancestor(Gtk.Grid);
player.widget.grab_focus();
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() _onCloseRequest()

63
src/controls.js vendored
View File

@@ -28,8 +28,6 @@ class ClapperControls extends Gtk.Box
this.currentPosition = 0; this.currentPosition = 0;
this.currentDuration = 0; this.currentDuration = 0;
this.isPositionDragging = false; this.isPositionDragging = false;
this.isMobileMonitor = false;
this.isMobile = false; this.isMobile = false;
this.showHours = false; this.showHours = false;
@@ -49,7 +47,6 @@ class ClapperControls extends Gtk.Box
'go-previous-symbolic', 'go-previous-symbolic',
'go-next-symbolic' 'go-next-symbolic'
); );
revealTracksButton.floatUnaffected = false;
revealTracksButton.add_css_class('narrowbutton'); revealTracksButton.add_css_class('narrowbutton');
this.buttonsArr.push(revealTracksButton); this.buttonsArr.push(revealTracksButton);
const tracksRevealer = new Revealers.ButtonsRevealer( const tracksRevealer = new Revealers.ButtonsRevealer(
@@ -92,12 +89,6 @@ class ClapperControls extends Gtk.Box
this.unfullscreenButton.connect('clicked', this._onUnfullscreenClicked.bind(this)); this.unfullscreenButton.connect('clicked', this._onUnfullscreenClicked.bind(this));
this.unfullscreenButton.set_visible(false); 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(); const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', this._onControlsKeyPressed.bind(this)); keyController.connect('key-pressed', this._onControlsKeyPressed.bind(this));
keyController.connect('key-released', this._onControlsKeyReleased.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) for(let button of this.buttonsArr)
button.setFullscreenMode(isFullscreen); button.setFullscreenMode(isFullscreen);
this.unfullscreenButton.set_visible(isFullscreen); this.unfullscreenButton.visible = isFullscreen;
this.set_can_focus(isFullscreen); this.can_focus = isFullscreen;
}
setFloatingMode(isFloating)
{
this.isMobile = null;
for(let button of this.buttonsArr)
button.setFloatingMode(isFloating);
} }
setLiveMode(isLive, isSeekable) setLiveMode(isLive, isSeekable)
@@ -149,7 +132,7 @@ class ClapperControls extends Gtk.Box
{ {
const button = (buttonIcon instanceof Gtk.Button) const button = (buttonIcon instanceof Gtk.Button)
? buttonIcon ? buttonIcon
: new Buttons.IconButton(buttonIcon); : new Buttons.CustomButton({ icon_name: buttonIcon });
if(!revealer) if(!revealer)
this.append(button); this.append(button);
@@ -495,13 +478,13 @@ class ClapperControls extends Gtk.Box
this.disconnect(this.realizeSignal); this.disconnect(this.realizeSignal);
this.realizeSignal = null; this.realizeSignal = null;
const { player } = this.get_ancestor(Gtk.Grid); const clapperWidget = this.get_ancestor(Gtk.Grid);
const scrollController = new Gtk.EventControllerScroll(); const scrollController = new Gtk.EventControllerScroll();
scrollController.set_flags( scrollController.set_flags(
Gtk.EventControllerScrollFlags.VERTICAL Gtk.EventControllerScrollFlags.VERTICAL
| Gtk.EventControllerScrollFlags.DISCRETE | Gtk.EventControllerScrollFlags.DISCRETE
); );
scrollController.connect('scroll', player._onScroll.bind(player)); scrollController.connect('scroll', clapperWidget._onScroll.bind(clapperWidget));
this.volumeButton.add_controller(scrollController); this.volumeButton.add_controller(scrollController);
const initialVolume = (settings.get_string('volume-initial') === 'custom') const initialVolume = (settings.get_string('volume-initial') === 'custom')
@@ -530,12 +513,6 @@ class ClapperControls extends Gtk.Box
root.unfullscreen(); root.unfullscreen();
} }
_onUnfloatClicked(button)
{
const clapperWidget = this.get_ancestor(Gtk.Grid);
clapperWidget.setFloatingMode(false);
}
_onCheckButtonToggled(checkButton) _onCheckButtonToggled(checkButton)
{ {
if(!checkButton.get_active()) if(!checkButton.get_active())
@@ -564,8 +541,8 @@ class ClapperControls extends Gtk.Box
_onPositionScaleScroll(controller, dx, dy) _onPositionScaleScroll(controller, dx, dy)
{ {
const { player } = this.get_ancestor(Gtk.Grid); const clapperWidget = this.get_ancestor(Gtk.Grid);
player._onScroll(controller, dx || dy, 0); clapperWidget._onScroll(controller, dx || dy, 0);
} }
_onPositionScaleValueChanged(scale) _onPositionScaleValueChanged(scale)
@@ -616,32 +593,40 @@ class ClapperControls extends Gtk.Box
{ {
const isPositionDragging = scale.has_css_class('dragging'); const isPositionDragging = scale.has_css_class('dragging');
if((this.isPositionDragging = isPositionDragging)) if(this.isPositionDragging === isPositionDragging)
return; return;
const isChapterSeek = this.chapterPopover.visible;
if(!isPositionDragging)
this._setChapterVisible(false);
const clapperWidget = this.get_ancestor(Gtk.Grid); const clapperWidget = this.get_ancestor(Gtk.Grid);
if(!clapperWidget) return; if(!clapperWidget) return;
if(clapperWidget.isFullscreenMode) {
clapperWidget.revealControls();
if(isPositionDragging)
clapperWidget._clearTimeout('hideControls');
}
if((this.isPositionDragging = isPositionDragging))
return;
const scaleValue = scale.get_value(); const scaleValue = scale.get_value();
const isChapterSeek = this.chapterPopover.visible;
if(!isChapterSeek) { if(!isChapterSeek) {
const positionSeconds = Math.round(scaleValue); const positionSeconds = Math.round(scaleValue);
clapperWidget.player.seek_seconds(positionSeconds); clapperWidget.player.seek_seconds(positionSeconds);
} }
else else {
clapperWidget.player.seek_chapter(scaleValue); clapperWidget.player.seek_chapter(scaleValue);
this._setChapterVisible(false);
}
} }
/* Only happens when navigating through controls panel */ /* Only happens when navigating through controls panel */
_onControlsKeyPressed(controller, keyval, keycode, state) _onControlsKeyPressed(controller, keyval, keycode, state)
{ {
const { player } = this.get_ancestor(Gtk.Grid); const clapperWidget = this.get_ancestor(Gtk.Grid);
player._setHideControlsTimeout(); clapperWidget._setHideControlsTimeout();
} }
_onControlsKeyReleased(controller, keyval, keycode, state) _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; const { HeaderBarBase } = imports.src.headerbarBase;
var HeaderBar = GObject.registerClass( var HeaderBar = GObject.registerClass(
class ClapperHeaderBar extends HeaderBarBase 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(); _onWindowButtonActivate(action)
clapperWidget.controls.unfloatButton.bind_property('visible', this, 'visible', {
GObject.BindingFlags.INVERT_BOOLEAN this.activate_action(action, null);
);
} }
_onFloatButtonClicked() _onFloatButtonClicked()
{ {
const clapperWidget = this.get_prev_sibling(); const clapperWidget = this.root.child;
clapperWidget.setFloatingMode(true);
clapperWidget.controlsRevealer.toggleReveal();
/* Reset timer to not disappear during click */
clapperWidget._setHideControlsTimeout();
} }
_onFullscreenButtonClicked() _onFullscreenButtonClicked()
{ {
const window = this.get_parent(); this.root.fullscreen();
window.fullscreen();
} }
}); });

View File

@@ -1,90 +1,202 @@
const { GObject, Gtk, Pango } = imports.gi; const { GObject, Gtk } = imports.gi;
const Misc = imports.src.misc; const Misc = imports.src.misc;
var HeaderBarBase = GObject.registerClass( var HeaderBarBase = GObject.registerClass(
class ClapperHeaderBarBase extends Gtk.HeaderBar class ClapperHeaderBarBase extends Gtk.Box
{ {
_init(window) _init()
{ {
super._init({ super._init({
can_focus: false, 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 clapperPath = Misc.getClapperPath();
const uiBuilder = Gtk.Builder.new_from_file( const uiBuilder = Gtk.Builder.new_from_file(
`${clapperPath}/ui/clapper.ui` `${clapperPath}/ui/clapper.ui`
); );
this.add_css_class('noborder'); this.menuWidget = new Gtk.Box({
this.set_title_widget(this._createWidgetForWindow(window)); 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', icon_name: 'open-menu-symbolic',
valign: Gtk.Align.CENTER,
}); });
const mainMenuModel = uiBuilder.get_object('mainMenu'); const mainMenuModel = uiBuilder.get_object('mainMenu');
const mainMenuPopover = new HeaderBarPopover(mainMenuModel); const mainMenuPopover = new HeaderBarPopover(mainMenuModel);
mainMenuButton.set_popover(mainMenuPopover); mainMenuPopover.add_css_class('menupopover');
this.pack_start(mainMenuButton); 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, orientation: Gtk.Orientation.HORIZONTAL,
valign: Gtk.Align.CENTER,
}); });
buttonsBox.add_css_class('linked'); this.extraButtonsBox.add_css_class('linked');
const floatButton = new Gtk.Button({ const floatButton = new Gtk.Button({
icon_name: 'preferences-desktop-remote-desktop-symbolic', icon_name: 'go-bottom-symbolic',
}); });
floatButton.connect('clicked', this._onFloatButtonClicked.bind(this)); floatButton.add_css_class('circular');
buttonsBox.append(floatButton); 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({ const fullscreenButton = new Gtk.Button({
icon_name: 'view-fullscreen-symbolic', 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.spacerWidget = new Gtk.Box({
this.pack_start(buttonsBox); 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; if(this.isMenuOnLeft === isOnLeft)
this.subtitleLabel.visible = (subtitle !== null); return;
if(subtitle) if(isOnLeft) {
this.subtitleLabel.label = subtitle; 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({ if(this.isMaximized === isMaximized)
orientation: Gtk.Orientation.VERTICAL, 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, valign: Gtk.Align.CENTER,
}); });
button.add_css_class('circular');
this.titleLabel = new Gtk.Label({ const action = (name === 'maximize')
halign: Gtk.Align.CENTER, ? 'window.toggle-maximized'
single_line_mode: true, : 'window.' + name;
ellipsize: Pango.EllipsizeMode.END,
width_chars: 5,
});
this.titleLabel.add_css_class('title');
this.titleLabel.set_parent(box);
window.bind_property('title', this.titleLabel, 'label', button.connect('clicked',
GObject.BindingFlags.SYNC_CREATE this._onWindowButtonActivate.bind(this, action)
); );
this.subtitleLabel = new Gtk.Label({ return button;
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 box; _onWindowButtonActivate(action)
{
} }
_onFloatButtonClicked() _onFloatButtonClicked()
@@ -105,12 +217,13 @@ class ClapperHeaderBarPopover extends Gtk.PopoverMenu
menu_model: model, menu_model: model,
}); });
this.connect('map', this._onMap.bind(this));
this.connect('closed', this._onClosed.bind(this)); this.connect('closed', this._onClosed.bind(this));
} }
_onClosed() _onMap()
{ {
const { child } = this.get_root(); const { child } = this.root;
if( if(
!child !child
@@ -119,6 +232,24 @@ class ClapperHeaderBarPopover extends Gtk.PopoverMenu
) )
return; 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(); child.player.widget.grab_focus();
} }
}); });

View File

@@ -14,10 +14,7 @@ class ClapperPlayer extends PlayerBase
{ {
super._init(); super._init();
this.cursorInPlayer = false;
this.seek_done = true; this.seek_done = true;
this.dragAllowed = false;
this.isWidgetDragging = false;
this.doneStartup = false; this.doneStartup = false;
this.needsFastSeekRestore = false; this.needsFastSeekRestore = false;
@@ -25,55 +22,15 @@ class ClapperPlayer extends PlayerBase
this.quitOnStop = false; this.quitOnStop = false;
this.needsTocUpdate = true; this.needsTocUpdate = true;
this.posX = 0;
this.posY = 0;
this.keyPressCount = 0; this.keyPressCount = 0;
this._maxVolume = Misc.getLinearValue(Misc.maxVolume); 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(); const keyController = new Gtk.EventControllerKey();
keyController.connect('key-pressed', this._onWidgetKeyPressed.bind(this)); keyController.connect('key-pressed', this._onWidgetKeyPressed.bind(this));
keyController.connect('key-released', this._onWidgetKeyReleased.bind(this)); keyController.connect('key-released', this._onWidgetKeyReleased.bind(this));
this.widget.add_controller(keyController); 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('state-changed', this._onStateChanged.bind(this));
this.connect('uri-loaded', this._onUriLoaded.bind(this)); this.connect('uri-loaded', this._onUriLoaded.bind(this));
this.connect('end-of-stream', this._onStreamEnded.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) _performCloseCleanup(window)
{ {
window.disconnect(this.closeRequestSignal); window.disconnect(this.closeRequestSignal);
this.closeRequestSignal = null; this.closeRequestSignal = null;
const clapperWidget = this.widget.get_ancestor(Gtk.Grid); const clapperWidget = this.widget.get_ancestor(Gtk.Grid);
if(!clapperWidget.fullscreenMode) {
if(!clapperWidget.isFullscreenMode && clapperWidget.controlsRevealer.child_revealed) {
const size = window.get_default_size(); const size = window.get_default_size();
if(size[0] > 0 && size[1] > 0) if(size[0] > 0 && size[1] > 0) {
clapperWidget._saveWindowSize(size); 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 "quitOnStop" is set here it means that we are in middle of autoclosing */
if(this.state !== GstClapper.ClapperState.STOPPED && !this.quitOnStop) { if(this.state !== GstClapper.ClapperState.STOPPED && !this.quitOnStop) {
@@ -495,7 +372,7 @@ class ClapperPlayer extends PlayerBase
if(settings.get_boolean('fullscreen-auto')) { if(settings.get_boolean('fullscreen-auto')) {
const root = player.widget.get_root(); const root = player.widget.get_root();
const clapperWidget = root.get_child(); const clapperWidget = root.get_child();
if(!clapperWidget.fullscreenMode) { if(!clapperWidget.isFullscreenMode) {
this.playOnFullscreen = true; this.playOnFullscreen = true;
root.fullscreen(); root.fullscreen();
@@ -557,11 +434,8 @@ class ClapperPlayer extends PlayerBase
bool = true; bool = true;
case Gdk.KEY_Left: case Gdk.KEY_Left:
this.adjust_position(bool); this.adjust_position(bool);
this._clearTimeout('hideControls'); if(this.keyPressCount > 1)
if(this.keyPressCount > 1) { clapperWidget.revealControls();
clapperWidget.revealerBottom.set_can_focus(false);
clapperWidget.revealerBottom.revealChild(true);
}
break; break;
default: default:
break; break;
@@ -581,10 +455,8 @@ class ClapperPlayer extends PlayerBase
this.toggle_play(); this.toggle_play();
break; break;
case Gdk.KEY_Return: case Gdk.KEY_Return:
if(clapperWidget.fullscreenMode) { if(clapperWidget.isFullscreenMode)
clapperWidget.revealControls(true); clapperWidget.revealControls(true);
this._setHideControlsTimeout();
}
break; break;
case Gdk.KEY_Right: case Gdk.KEY_Right:
case Gdk.KEY_Left: case Gdk.KEY_Left:
@@ -592,7 +464,7 @@ class ClapperPlayer extends PlayerBase
clapperWidget.controls.positionScale.get_value() clapperWidget.controls.positionScale.get_value()
); );
this.seek_seconds(value); this.seek_seconds(value);
this._setHideControlsTimeout(); clapperWidget._setHideControlsTimeout();
break; break;
case Gdk.KEY_F11: case Gdk.KEY_F11:
case Gdk.KEY_f: case Gdk.KEY_f:
@@ -600,7 +472,7 @@ class ClapperPlayer extends PlayerBase
clapperWidget.toggleFullscreen(); clapperWidget.toggleFullscreen();
break; break;
case Gdk.KEY_Escape: case Gdk.KEY_Escape:
if(clapperWidget.fullscreenMode) { if(clapperWidget.isFullscreenMode) {
root = this.widget.get_root(); root = this.widget.get_root();
root.unfullscreen(); 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) _onCloseRequest(window)
{ {
this._performCloseCleanup(window); this._performCloseCleanup(window);

View File

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

View File

@@ -73,6 +73,9 @@ class ClapperBehaviourPage extends PrefsBase.Grid
this.addTitle('Resume'); this.addTitle('Resume');
this.addCheckButton('Ask to resume last unfinished video', 'resume-enabled'); 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 { GLib, GObject, Gtk, Pango } = imports.gi;
const { HeaderBar } = imports.src.headerbar;
const Debug = imports.src.debug; const Debug = imports.src.debug;
const DBus = imports.src.dbus;
const REVEAL_TIME = 800; const Misc = imports.src.misc;
const { debug } = Debug; const { debug } = Debug;
const { settings } = Misc;
var CustomRevealer = GObject.registerClass( var CustomRevealer = GObject.registerClass(
class ClapperCustomRevealer extends Gtk.Revealer class ClapperCustomRevealer extends Gtk.Revealer
@@ -15,74 +17,24 @@ class ClapperCustomRevealer extends Gtk.Revealer
const defaults = { const defaults = {
visible: false, visible: false,
can_focus: false, can_focus: false,
transition_duration: 800,
}; };
Object.assign(opts, defaults); Object.assign(opts, defaults);
super._init(opts); super._init(opts);
this.revealerName = ''; this.revealerName = '';
this.bind_property('child_revealed', this, 'visible',
GObject.BindingFlags.DEFAULT
);
} }
revealChild(isReveal) revealChild(isReveal)
{ {
if(isReveal) { if(isReveal)
this._clearTimeout(); this.visible = true;
this.set_visible(isReveal);
}
else
this._setHideTimeout();
/* Restore focusability after we are done */ this.reveal_child = isReveal;
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;
} }
}); });
@@ -92,55 +44,108 @@ class ClapperRevealerTop extends CustomRevealer
_init() _init()
{ {
super._init({ super._init({
transition_duration: REVEAL_TIME,
transition_type: Gtk.RevealerTransitionType.CROSSFADE, transition_type: Gtk.RevealerTransitionType.CROSSFADE,
valign: Gtk.Align.START, valign: Gtk.Align.START,
}); });
this.revealerName = 'top'; this.revealerName = 'top';
this._requestedTransition = this.transition_type;
const initTime = GLib.DateTime.new_now_local().format('%X'); const initTime = GLib.DateTime.new_now_local().format('%X');
this.timeFormat = (initTime.length > 8) this.timeFormat = (initTime.length > 8)
? '%I:%M %p' ? '%I:%M %p'
: '%H:%M'; : '%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({ this.mediaTitle = new Gtk.Label({
ellipsize: Pango.EllipsizeMode.END, 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, hexpand: true,
margin_top: 14,
margin_start: 12,
xalign: 0,
yalign: 0,
}); });
const timeLabelOpts = { const topRightBox = new Gtk.Box({
margin_end: 10, orientation: Gtk.Orientation.VERTICAL,
xalign: 1, halign: Gtk.Align.END,
yalign: 0, });
}; topRightBox.add_css_class('osd');
this.currentTime = new Gtk.Label(timeLabelOpts); topRightBox.add_css_class('roundedcorners');
this.currentTime.add_css_class('osdtime'); topRightBox.append(this.currentTime);
topRightBox.append(this.endTime);
timeLabelOpts.visible = false; this.revealerGrid.attach(topLeftBox, 0, 0, 1, 1);
this.endTime = new Gtk.Label(timeLabelOpts); this.revealerGrid.attach(topSpacerBox, 1, 0, 1, 1);
this.endTime.add_css_class('osdendtime'); this.revealerGrid.attach(topRightBox, 2, 0, 1, 2);
revealerBox.append(this.revealerGrid);
this.revealerGrid.attach(this.mediaTitle, 0, 0, 1, 1); this.set_child(revealerBox);
this.revealerGrid.attach(this.currentTime, 1, 0, 1, 1);
this.revealerGrid.attach(this.endTime, 1, 0, 1, 1);
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) setTimes(currTime, endTime)
@@ -159,6 +164,31 @@ class ClapperRevealerTop extends CustomRevealer
return nextUpdate; 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( var RevealerBottom = GObject.registerClass(
@@ -167,16 +197,25 @@ class ClapperRevealerBottom extends CustomRevealer
_init() _init()
{ {
super._init({ super._init({
transition_duration: REVEAL_TIME,
transition_type: Gtk.RevealerTransitionType.SLIDE_UP, transition_type: Gtk.RevealerTransitionType.SLIDE_UP,
valign: Gtk.Align.END, valign: Gtk.Align.END,
}); });
this.revealerName = 'bottom'; 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('osd');
this.revealerBox.add_css_class('roundedcorners');
this.set_child(this.revealerBox); this.set_child(this.revealerBox);
const motionController = new Gtk.EventControllerMotion();
motionController.connect('motion', this._onMotion.bind(this));
this.add_controller(motionController);
} }
append(widget) append(widget)
@@ -189,44 +228,79 @@ class ClapperRevealerBottom extends CustomRevealer
this.revealerBox.remove(widget); this.revealerBox.remove(widget);
} }
setFloatingClass(isFloating) _onMotion(controller, x, y)
{ {
if(isFloating === this.revealerBox.has_css_class('floatingcontrols')) const clapperWidget = this.root.child;
return; clapperWidget._clearTimeout('hideControls');
}
});
const action = (isFloating) ? 'add' : 'remove'; var ControlsRevealer = GObject.registerClass(
this.revealerBox[`${action}_css_class`]('floatingcontrols'); 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); /* Prevent interrupting transition */
if(!isChange || !this.can_focus) return; if(this.reveal_child !== this.child_revealed)
return;
const parent = this.get_parent(); const { widget } = this.root.child.player;
const playerWidget = parent.get_first_child();
if(!playerWidget) return;
if(isVisible) { if(this.child_revealed) {
const box = this.get_first_child(); const [width] = this.root.get_default_size();
if(!box) return; const height = widget.get_height();
const controls = box.get_first_child(); this.add_tick_callback(
if(!controls) return; this._onUnrevealTick.bind(this, widget, width, height)
);
const togglePlayButton = controls.get_first_child();
if(togglePlayButton) {
togglePlayButton.grab_focus();
debug('focus moved to toggle play button');
}
playerWidget.set_can_focus(false);
} }
else { else
playerWidget.set_can_focus(true); this.visible = true;
playerWidget.grab_focus();
debug('focus moved to player widget'); 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( 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) append(widget)
{ {
this.get_child().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 { Controls } = imports.src.controls;
const Debug = imports.src.debug; const Debug = imports.src.debug;
const Dialogs = imports.src.dialogs; const Dialogs = imports.src.dialogs;
@@ -20,15 +20,27 @@ class ClapperWidget extends Gtk.Grid
* separately as a pre-made GTK widget */ * separately as a pre-made GTK widget */
Misc.loadCustomCss(); Misc.loadCustomCss();
this.posX = 0;
this.posY = 0;
this.windowSize = JSON.parse(settings.get_string('window-size')); this.windowSize = JSON.parse(settings.get_string('window-size'));
this.floatSize = JSON.parse(settings.get_string('float-size'));
this.layoutWidth = 0; this.layoutWidth = 0;
this.fullscreenMode = false; this.isFullscreenMode = false;
this.floatingMode = false;
this.isSeekable = 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.needsTracksUpdate = true;
this.needsCursorRestore = false;
this.overlay = new Gtk.Overlay(); this.overlay = new Gtk.Overlay();
this.revealerTop = new Revealers.RevealerTop(); this.revealerTop = new Revealers.RevealerTop();
@@ -41,12 +53,17 @@ class ClapperWidget extends Gtk.Grid
this.controlsBox.add_css_class('controlsbox'); this.controlsBox.add_css_class('controlsbox');
this.controlsBox.append(this.controls); 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.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.mapSignal = this.connect('map', this._onMap.bind(this));
this.player = new Player(); this.player = new Player();
const playerWidget = this.player.widget;
this.controls.elapsedButton.scrolledWindow.set_child(this.player.playlistWidget); this.controls.elapsedButton.scrolledWindow.set_child(this.player.playlistWidget);
this.controls.speedAdjustment.bind_property( this.controls.speedAdjustment.bind_property(
'value', this.player, 'rate', GObject.BindingFlags.BIDIRECTIONAL '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 */ /* FIXME: re-enable once ported to new GstPlayer API with messages bus */
//this.player.connect('volume-changed', this._onPlayerVolumeChanged.bind(this)); //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.revealerTop);
this.overlay.add_overlay(this.revealerBottom); this.overlay.add_overlay(this.revealerBottom);
const motionController = new Gtk.EventControllerMotion(); const clickGesture = this._getClickGesture();
motionController.connect('leave', this._onLeave.bind(this)); playerWidget.add_controller(clickGesture);
this.add_controller(motionController); const clickGestureTop = this._getClickGesture();
this.revealerTop.add_controller(clickGestureTop);
const topClickGesture = new Gtk.GestureClick(); const dragGesture = this._getDragGesture();
topClickGesture.set_button(0); playerWidget.add_controller(dragGesture);
topClickGesture.connect('pressed', this.player._onWidgetPressed.bind(this.player)); const dragGestureTop = this._getDragGesture();
this.revealerTop.add_controller(topClickGesture); this.revealerTop.add_controller(dragGestureTop);
const topMotionController = new Gtk.EventControllerMotion(); const swipeGesture = this._getSwipeGesture();
topMotionController.connect('motion', this.player._onWidgetMotion.bind(this.player)); playerWidget.add_controller(swipeGesture);
this.revealerTop.add_controller(topMotionController); const swipeGestureTop = this._getSwipeGesture();
this.revealerTop.add_controller(swipeGestureTop);
const topScrollController = new Gtk.EventControllerScroll(); const scrollController = this._getScrollController();
topScrollController.set_flags(Gtk.EventControllerScrollFlags.BOTH_AXES); playerWidget.add_controller(scrollController);
topScrollController.connect('scroll', this.player._onScroll.bind(this.player)); const scrollControllerTop = this._getScrollController();
this.revealerTop.add_controller(topScrollController); 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._checkSetUpdateTimeInterval();
this[`revealer${pos}`].revealChild(isReveal);
}
showControls(isShow) this.revealerTop.revealChild(true);
{ this.revealerBottom.revealChild(true);
for(let pos of ['Top', 'Bottom'])
this[`revealer${pos}`].showChild(isShow); if(isAllowInput)
this.setControlsCanFocus(true);
this._setHideControlsTimeout();
} }
toggleFullscreen() toggleFullscreen()
@@ -98,98 +128,55 @@ class ClapperWidget extends Gtk.Grid
const root = this.get_root(); const root = this.get_root();
if(!root) return; if(!root) return;
const un = (this.fullscreenMode) ? 'un' : ''; const un = (this.isFullscreenMode) ? 'un' : '';
root[`${un}fullscreen`](); root[`${un}fullscreen`]();
} }
setFullscreenMode(isFullscreen) setFullscreenMode(isFullscreen)
{ {
if(this.fullscreenMode === isFullscreen) if(this.isFullscreenMode === isFullscreen)
return; return;
this.fullscreenMode = isFullscreen; debug('changing fullscreen mode');
this.isFullscreenMode = isFullscreen;
const root = this.get_root(); const root = this.get_root();
const action = (isFullscreen) ? 'add' : 'remove'; const action = (isFullscreen) ? 'add' : 'remove';
root[action + '_css_class']('gpufriendlyfs'); root[action + '_css_class']('gpufriendlyfs');
if(!this.floatingMode) if(!this.isMobileMonitor)
this._changeControlsPlacement(isFullscreen); root[action + '_css_class']('tvmode');
else {
this._setWindowFloating(!isFullscreen);
this.revealerBottom.setFloatingClass(!isFullscreen);
this.controls.setFloatingMode(!isFullscreen);
this.controls.unfloatButton.set_visible(!isFullscreen);
}
if(!isFullscreen)
this._clearTimeout('updateTime');
this._changeControlsPlacement(isFullscreen);
this.controls.setFullscreenMode(isFullscreen); this.controls.setFullscreenMode(isFullscreen);
this.showControls(isFullscreen); this.revealerTop.setFullscreenMode(isFullscreen, this.isMobileMonitor);
this.player.widget.grab_focus();
if(this.revealerTop.child_revealed)
this._checkSetUpdateTimeInterval();
this.setControlsCanFocus(false);
if(this.player.playOnFullscreen && isFullscreen) { if(this.player.playOnFullscreen && isFullscreen) {
this.player.playOnFullscreen = false; this.player.playOnFullscreen = false;
this.player.play(); this.player.play();
} }
debug(`interface in fullscreen mode: ${isFullscreen}`);
} }
setFloatingMode(isFloating) setControlsCanFocus(isControlsFocus)
{ {
if(this.floatingMode === isFloating) this.revealerBottom.can_focus = isControlsFocus;
return; this.player.widget.can_focus = !isControlsFocus;
const root = this.get_root(); const focusWidget = (isControlsFocus)
const size = root.get_default_size(); ? this.controls.togglePlayButton
: this.player.widget;
this._saveWindowSize(size); focusWidget.grab_focus();
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]}`);
} }
_changeControlsPlacement(isOnTop) _changeControlsPlacement(isOnTop)
@@ -212,8 +199,8 @@ class ClapperWidget extends Gtk.Grid
if(!mediaInfo) if(!mediaInfo)
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
/* Set titlebar media title and path */ /* Set titlebar media title */
this.updateTitles(mediaInfo); this.updateTitle(mediaInfo);
/* Show/hide position scale on LIVE */ /* Show/hide position scale on LIVE */
const isLive = mediaInfo.is_live(); const isLive = mediaInfo.is_live();
@@ -322,26 +309,21 @@ class ClapperWidget extends Gtk.Grid
return GLib.SOURCE_REMOVE; return GLib.SOURCE_REMOVE;
} }
updateTitles(mediaInfo) updateTitle(mediaInfo)
{ {
let title = mediaInfo.get_title(); let title = mediaInfo.get_title();
let subtitle = this.player.playlistWidget.getActiveFilename();
if(!title) { if(!title) {
const subtitle = this.player.playlistWidget.getActiveFilename();
title = (subtitle.includes('.')) title = (subtitle.includes('.'))
? subtitle.split('.').slice(0, -1).join('.') ? subtitle.split('.').slice(0, -1).join('.')
: subtitle; : subtitle;
subtitle = null;
} }
const root = this.get_root(); this.root.title = title;
const headerbar = root.get_titlebar(); this.revealerTop.title = title;
this.revealerTop.showTitle = true;
if(headerbar && headerbar.updateHeaderBar)
headerbar.updateHeaderBar(title, subtitle);
this.revealerTop.setMediaTitle(title);
} }
updateTime() updateTime()
@@ -564,15 +546,16 @@ class ClapperWidget extends Gtk.Grid
_onStateNotify(toplevel) _onStateNotify(toplevel)
{ {
const isMaximized = Boolean(
toplevel.state & Gdk.ToplevelState.MAXIMIZED
);
const isFullscreen = Boolean( const isFullscreen = Boolean(
toplevel.state & Gdk.ToplevelState.FULLSCREEN toplevel.state & Gdk.ToplevelState.FULLSCREEN
); );
const headerBar = this.revealerTop.headerBar;
if(this.fullscreenMode === isFullscreen) headerBar.setMaximized(isMaximized);
return;
this.setFullscreenMode(isFullscreen); this.setFullscreenMode(isFullscreen);
debug(`interface in fullscreen mode: ${isFullscreen}`);
} }
_onLayoutUpdate(surface, width, height) _onLayoutUpdate(surface, width, height)
@@ -584,18 +567,6 @@ class ClapperWidget extends Gtk.Grid
this.controls._onPlayerResize(width, height); this.controls._onPlayerResize(width, height);
} }
_onLeave(controller)
{
if(
this.fullscreenMode
|| !this.floatingMode
|| this.player.isWidgetDragging
)
return;
this.revealerBottom.revealChild(false);
}
_onMap() _onMap()
{ {
this.disconnect(this.mapSignal); this.disconnect(this.mapSignal);
@@ -616,11 +587,327 @@ class ClapperWidget extends Gtk.Grid
const monitorWidth = Math.max(geometry.width, geometry.height); const monitorWidth = Math.max(geometry.width, geometry.height);
if(monitorWidth < 1280) { if(monitorWidth < 1280) {
this.controls.isMobileMonitor = true; this.isMobileMonitor = true;
debug('mobile monitor detected'); debug('mobile monitor detected');
} }
surface.connect('notify::state', this._onStateNotify.bind(this)); surface.connect('notify::state', this._onStateNotify.bind(this));
surface.connect('layout', this._onLayoutUpdate.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;
}
}); });