mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 08:21:59 +02:00
14
README.md
14
README.md
@@ -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
|
||||||
|
|
||||||
|
187
css/styles.css
187
css/styles.css
@@ -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 {
|
||||||
|
@@ -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>
|
||||||
|
@@ -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 |
@@ -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"
|
||||||
],
|
],
|
||||||
|
@@ -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",
|
||||||
|
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 { 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)
|
||||||
|
@@ -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;
|
||||||
|
|
||||||
|
101
src/buttons.js
101
src/buttons.js
@@ -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
63
src/controls.js
vendored
@@ -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
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;
|
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();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
339
src/player.js
339
src/player.js
@@ -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);
|
||||||
|
@@ -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;
|
||||||
|
@@ -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');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
318
src/revealers.js
318
src/revealers.js
@@ -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);
|
||||||
|
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 { 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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user