mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-30 07:42:23 +02:00
14
README.md
14
README.md
@@ -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
|
||||
|
||||
|
187
css/styles.css
187
css/styles.css
@@ -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 {
|
||||
|
@@ -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>
|
||||
|
@@ -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 |
@@ -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"
|
||||
],
|
||||
|
@@ -18,7 +18,8 @@
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://gitlab.gnome.org/GNOME/gtk.git",
|
||||
"commit": "ec94ec0286041c16d6df7b496b0760a0ae0885ba"
|
||||
"tag": "4.1.0",
|
||||
"commit": "65c38111f958bac66d578e3f81964ca3857105c1"
|
||||
},
|
||||
{
|
||||
"type": "patch",
|
||||
|
19
src/app.js
19
src/app.js
@@ -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)
|
||||
|
@@ -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;
|
||||
|
||||
|
101
src/buttons.js
101
src/buttons.js
@@ -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
63
src/controls.js
vendored
@@ -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
56
src/dbus.js
Normal 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);
|
||||
}
|
||||
);
|
||||
}
|
@@ -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();
|
||||
}
|
||||
});
|
||||
|
@@ -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();
|
||||
}
|
||||
});
|
||||
|
339
src/player.js
339
src/player.js
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
|
318
src/revealers.js
318
src/revealers.js
@@ -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);
|
||||
|
539
src/widget.js
539
src/widget.js
@@ -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;
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user