diff --git a/data/style.css b/data/style.css index 78c080be..5f2da442 100644 --- a/data/style.css +++ b/data/style.css @@ -1,3 +1,7 @@ +:root { + --dimmed-color: rgb(from currentColor r g b / calc(alpha * 0.55)); +} + .thumbnail { border-radius: 12px; } @@ -200,7 +204,7 @@ label.bold-link link { } .playlist-column-view listview>row, -.playlist-list-view >row { +.playlist-list-view>row { border-radius: 9px; } @@ -330,12 +334,12 @@ button.large { font-weight: 500; } -.main-toast-overlay > toast { +.main-toast-overlay>toast { margin-bottom: calc(var(--toast-margin, 0px) + 24px); transition: 0.3s margin-bottom; } -.full-player-view > revealer > box { +.full-player-view>revealer>box { padding-left: 9px; padding-right: 9px; } @@ -343,3 +347,20 @@ button.large { .counterpart-button { background-color: rgb(from var(--view-bg-color) r g b / calc(alpha * 0.80)); } + +np-volume>scale { + color: var(--dimmed-color); +} + +np-volume:hover>scale { + color: inherit; +} + +np-volume>scale trough highlight { + min-height: 12px; + min-width: 12px; +} + +np-volume>scale trough slider { + opacity: 0; +} diff --git a/data/ui/components/player/now-playing/cover.blp b/data/ui/components/player/now-playing/cover.blp index 5017aa73..ebc87233 100644 --- a/data/ui/components/player/now-playing/cover.blp +++ b/data/ui/components/player/now-playing/cover.blp @@ -229,6 +229,8 @@ template $MuzikaNPCover : Adw.Bin { ] } } + + $MuzikaNPVolumeControl {} } } diff --git a/data/ui/components/player/now-playing/volume-control.blp b/data/ui/components/player/now-playing/volume-control.blp new file mode 100644 index 00000000..fab886cb --- /dev/null +++ b/data/ui/components/player/now-playing/volume-control.blp @@ -0,0 +1,39 @@ +using Gtk 4.0; + +template $MuzikaNPVolumeControl: Widget { + Button volume_low_button { + icon-name: 'audio-volume-low-symbolic'; + action-name: 'volume.toggle-mute'; + valign: center; + + styles [ + "flat", + "circular", + ] + } + + Scale volume_scale { + hexpand: true; + margin-end: 6; + adjustment: Adjustment volume_adjustment { + lower: 0; + upper: 1; + step-increment: 0.05; + value: 1; + + notify::value => $adjustment_value_changed_cb(); + }; + + EventControllerScroll { + name: "volume-scroll"; + flags: vertical; + + scroll => $volume_scale_scrolled_cb(); + } + } + + Image volume_high_image { + icon-name: 'audio-volume-high-symbolic'; + margin-end: 10; + } +} diff --git a/data/ui/meson.build b/data/ui/meson.build index 125e3c7e..e0bb9950 100644 --- a/data/ui/meson.build +++ b/data/ui/meson.build @@ -21,6 +21,7 @@ blueprint_files = [ 'components/player/now-playing/counterpart-switcher.blp', 'components/player/now-playing/cover.blp', 'components/player/now-playing/sheet.blp', + 'components/player/now-playing/volume-control.blp', 'components/player/video/controls.blp', 'components/player/video/view.blp', 'components/player/video/volume-controls.blp', diff --git a/src/components/player/now-playing/cover.ts b/src/components/player/now-playing/cover.ts index 2f8d9843..d9e7142b 100644 --- a/src/components/player/now-playing/cover.ts +++ b/src/components/player/now-playing/cover.ts @@ -12,6 +12,7 @@ import { SignalListeners } from "src/util/signal-listener"; import { load_thumbnails } from "src/components/webimage"; import { micro_to_string } from "src/util/time"; import { FixedRatioThumbnail } from "src/components/fixed-ratio-thumbnail"; +import { MuzikaNPVolumeControl } from "./volume-control"; import { bind_play_icon, bind_repeat_button, @@ -20,6 +21,8 @@ import { } from "src/player/helpers"; import { get_button_props } from "src/util/menu/like"; +GObject.type_ensure(MuzikaNPVolumeControl.$gtype); + export class MuzikaNPCover extends Adw.Bin { static { GObject.registerClass( diff --git a/src/components/player/now-playing/volume-control.ts b/src/components/player/now-playing/volume-control.ts new file mode 100644 index 00000000..d2657084 --- /dev/null +++ b/src/components/player/now-playing/volume-control.ts @@ -0,0 +1,130 @@ +// from: https://gitlab.gnome.org/World/amberol/-/blob/main/src/volume_control.rs + +import GObject from "gi://GObject"; +import Gtk from "gi://Gtk?version=4.0"; + +import { get_player } from "src/application"; +import { SignalListeners } from "src/util/signal-listener"; +import { get_volume_icon_name } from "src/util/volume"; + +export class MuzikaNPVolumeControl extends Gtk.Widget { + static { + GObject.registerClass( + { + GTypeName: "MuzikaNPVolumeControl", + Template: + "resource:///com/vixalien/muzika/ui/components/player/now-playing/volume-control.ui", + CssName: "np-volume", + Properties: { + volume: GObject.param_spec_double( + "volume", + "volume", + "Volume", + 0.0, + 1.0, + 1.0, + GObject.ParamFlags.READWRITE, + ), + muted: GObject.param_spec_boolean( + "muted", + "muted", + "muted", + false, + GObject.ParamFlags.READWRITE, + ), + }, + InternalChildren: ["volume_adjustment", "volume_low_button"], + }, + this, + ); + + this.set_layout_manager_type(Gtk.BoxLayout.$gtype); + this.set_accessible_role(Gtk.AccessibleRole.GROUP); + + this.install_property_action("volume.toggle-mute", "muted"); + } + + private _volume_adjustment!: Gtk.Adjustment; + private _volume_low_button!: Gtk.Button; + + get volume() { + return this._volume_adjustment.value; + } + + set volume(value: number) { + this._volume_adjustment.value = value; + } + + private prev_volume = 0; + + private _muted = false; + + get muted() { + return this._muted; + } + + set muted(value: boolean) { + if (value === this._muted) return; + + if (value) { + this.prev_volume = this._volume_adjustment.value; + this._volume_adjustment.value = 0; + } else { + this._volume_adjustment.value = this.prev_volume; + } + this._muted = value; + } + + private adjustment_value_changed_cb(adjustment: Gtk.Adjustment) { + this._volume_low_button.icon_name = get_volume_icon_name( + false, + adjustment.value, + ); + + this.notify("volume"); + } + + private volume_scale_scrolled_cb( + _: Gtk.EventControllerScroll, + __: number, + dy: number, + ) { + const delta = dy * this._volume_adjustment.step_increment; + const d = Math.max( + Math.min( + this._volume_adjustment.value - delta, + this._volume_adjustment.upper, + ), + 0, + ); + this._volume_adjustment.value = d; + + return true; + } + + private listeners = new SignalListeners(); + + vfunc_map() { + super.vfunc_map(); + + this.listeners.add_bindings( + get_player().bind_property( + "cubic-volume", + this, + "volume", + GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL, + ), + get_player().bind_property( + "muted", + this, + "muted", + GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL, + ), + ); + } + + vfunc_unmap(): void { + this.listeners.clear(); + super.vfunc_unmap(); + } +} diff --git a/src/components/player/video/volume-controls.ts b/src/components/player/video/volume-controls.ts index b1e61146..7a065b43 100644 --- a/src/components/player/video/volume-controls.ts +++ b/src/components/player/video/volume-controls.ts @@ -3,6 +3,7 @@ import GObject from "gi://GObject"; import { SignalListeners } from "src/util/signal-listener"; import { get_player } from "src/application"; +import { get_volume_icon_name } from "src/util/volume"; export class VolumeControls extends Gtk.Box { static { @@ -24,23 +25,7 @@ export class VolumeControls extends Gtk.Box { listeners = new SignalListeners(); update_icon(muted: boolean, volume: number) { - let icon_name: string; - - if (muted) { - icon_name = "audio-volume-muted-symbolic"; - } else { - if (volume === 0) { - icon_name = "audio-volume-muted-symbolic"; - } else if (volume < 0.33) { - icon_name = "audio-volume-low-symbolic"; - } else if (volume < 0.66) { - icon_name = "audio-volume-medium-symbolic"; - } else { - icon_name = "audio-volume-high-symbolic"; - } - } - - this._button.set_icon_name(icon_name); + this._button.set_icon_name(get_volume_icon_name(muted, volume)); } private update_values() { diff --git a/src/util/volume.ts b/src/util/volume.ts new file mode 100644 index 00000000..d3cc2e33 --- /dev/null +++ b/src/util/volume.ts @@ -0,0 +1,19 @@ +export function get_volume_icon_name(muted: boolean, volume: number) { + let icon_name: string; + + if (muted) { + icon_name = "audio-volume-muted-symbolic"; + } else { + if (volume === 0) { + icon_name = "audio-volume-muted-symbolic"; + } else if (volume < 0.33) { + icon_name = "audio-volume-low-symbolic"; + } else if (volume < 0.66) { + icon_name = "audio-volume-medium-symbolic"; + } else { + icon_name = "audio-volume-high-symbolic"; + } + } + + return icon_name; +}