diff --git a/src/audrey.gresource.xml b/src/audrey.gresource.xml
index 9fd414e..f676199 100644
--- a/src/audrey.gresource.xml
+++ b/src/audrey.gresource.xml
@@ -5,6 +5,7 @@
gtk/help-overlay.ui
ui/play_queue.ui
ui/play_queue_song.ui
+ ui/playbar.ui
ui/setup.ui
ui/window.ui
diff --git a/src/meson.build b/src/meson.build
index 4704f2e..d35c67e 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -6,6 +6,7 @@ audrey_sources = [
'playbin.vala',
'subsonic.vala',
'ui/play_queue.vala',
+ 'ui/playbar.vala',
'ui/setup.vala',
'ui/window.vala',
]
diff --git a/src/ui/meson.build b/src/ui/meson.build
index a0ba2ba..e59fe29 100644
--- a/src/ui/meson.build
+++ b/src/ui/meson.build
@@ -1,7 +1,7 @@
blueprints = custom_target(
'blueprints',
- input: files('play_queue.blp', 'play_queue_song.blp', 'setup.blp', 'window.blp'),
- output: ['play_queue.ui', 'play_queue_song.ui', 'setup.ui', 'window.ui'],
+ input: files('play_queue.blp', 'play_queue_song.blp', 'playbar.blp', 'setup.blp', 'window.blp'),
+ output: ['play_queue.ui', 'play_queue_song.ui', 'playbar.ui', 'setup.ui', 'window.ui'],
command: [
find_program('blueprint-compiler'),
'batch-compile',
diff --git a/src/ui/playbar.blp b/src/ui/playbar.blp
new file mode 100644
index 0000000..6f9e3b5
--- /dev/null
+++ b/src/ui/playbar.blp
@@ -0,0 +1,154 @@
+using Gtk 4.0;
+
+template $UiPlaybar: Box {
+ styles [
+ "toolbar",
+ ]
+
+ CenterBox {
+ [start]
+ Box {
+ orientation: vertical;
+ valign: center;
+
+ Label {
+ styles [ "heading" ]
+ xalign: 0;
+ halign: start;
+ label: bind $song_title (template.song) as ;
+ ellipsize: end;
+ }
+
+ Label {
+ styles [ "caption" ]
+ xalign: 0;
+ label: bind $song_artist (template.song) as ;
+ ellipsize: end;
+ }
+
+ Label {
+ styles [ "caption" ]
+ xalign: 0;
+ label: bind $song_album (template.song) as ;
+ ellipsize: end;
+ }
+ }
+
+ [center]
+ Box {
+ orientation: vertical;
+ halign: center;
+ hexpand: true;
+
+ CenterBox {
+ [start]
+ Label play_position_label {
+ styles [
+ "caption",
+ "numeric",
+ ]
+
+ label: bind $format_timestamp (template.playbin as <$Playbin>.position) as ;
+ }
+
+ [center]
+ Scale play_position {
+ name: "seek-scale";
+ orientation: horizontal;
+ width-request: 400;
+ sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
+
+ adjustment: Adjustment {
+ lower: 0;
+ value: bind template.playbin as <$Playbin>.position;
+ upper: bind template.playbin as <$Playbin>.duration;
+ };
+
+ change-value => $on_play_position_seek ();
+ }
+
+ [end]
+ Label play_duration {
+ styles [
+ "caption",
+ "numeric",
+ ]
+
+ label: bind $format_timestamp (template.playbin as <$Playbin>.duration) as ;
+ }
+ }
+
+ Box {
+ halign: center;
+ orientation: horizontal;
+
+ Button {
+ icon-name: "media-skip-backward";
+ valign: center;
+ sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
+
+ clicked => $on_skip_backward_clicked ();
+ }
+
+ Button {
+ icon-name: "media-seek-backward";
+ valign: center;
+ sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
+
+ clicked => $seek_backward ();
+ }
+
+ Button {
+ icon-name: bind $play_pause_icon_name (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
+ valign: center;
+ sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
+
+ clicked => $on_play_pause_clicked ();
+ }
+
+ Button {
+ icon-name: "media-seek-forward";
+ valign: center;
+ sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
+
+ clicked => $seek_forward ();
+ }
+
+ Button {
+ icon-name: "media-skip-forward";
+ valign: center;
+ sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
+
+ clicked => $on_skip_forward_clicked ();
+ }
+ }
+ }
+
+ [end]
+ Box {
+ Button {
+ icon-name: "non-starred";
+ valign: center;
+ }
+
+ Button {
+ icon-name: bind $mute_button_icon_name (template.playbin as <$Playbin>.mute) as ;
+ valign: center;
+
+ clicked => $on_mute_toggle ();
+ }
+
+ Scale {
+ name: "volume-scale";
+ orientation: horizontal;
+ width-request: 130;
+
+ adjustment: Adjustment {
+ lower: 0;
+ value: bind template.volume bidirectional;
+ upper: 100;
+ };
+ }
+ }
+ }
+}
diff --git a/src/ui/playbar.vala b/src/ui/playbar.vala
new file mode 100644
index 0000000..1f9bd28
--- /dev/null
+++ b/src/ui/playbar.vala
@@ -0,0 +1,80 @@
+[GtkTemplate (ui = "/eu/callcc/audrey/ui/playbar.ui")]
+class Ui.Playbar : Gtk.Box {
+ public Subsonic.Song? song { get; set; }
+ public Playbin playbin { get; set; }
+ public int volume {
+ get { return playbin == null ? 100 : playbin.volume; }
+ set { playbin.volume = value; }
+ }
+
+ [GtkCallback] private string format_timestamp (double s) {
+ return "%02d:%02d".printf (((int) s)/60, ((int) s)%60);
+ }
+
+ [GtkCallback] private bool on_play_position_seek (Gtk.Range range, Gtk.ScrollType scroll_type, double value) {
+ this.playbin.seek ((int64) value);
+ return false;
+ }
+
+ [GtkCallback] private void on_play_pause_clicked () {
+ if (this.playbin.state == PlaybinState.PLAYING) {
+ this.playbin.pause();
+ } else {
+ this.playbin.play();
+ }
+ }
+
+ [GtkCallback] private string play_pause_icon_name (PlaybinState state) {
+ if (state == PlaybinState.PLAYING) {
+ return "media-playback-pause";
+ } else {
+ return "media-playback-start";
+ }
+ }
+
+ [GtkCallback] private bool playbin_active (PlaybinState state) {
+ return state != PlaybinState.STOPPED;
+ }
+
+ [GtkCallback] private string mute_button_icon_name (bool mute) {
+ return mute ? "audio-volume-muted" : "audio-volume-high";
+ }
+
+ [GtkCallback] private void on_mute_toggle () {
+ this.playbin.mute = !this.playbin.mute;
+ }
+
+ [GtkCallback] private void on_skip_forward_clicked () {
+ this.playbin.next_track ();
+ }
+
+ [GtkCallback] private void on_skip_backward_clicked () {
+ this.playbin.prev_track ();
+ }
+
+ [GtkCallback] private void seek_backward () {
+ // 10 seconds
+ double new_position = playbin.position - 10.0;
+ if (new_position < 0.0) new_position = 0.0;
+ this.playbin.seek (new_position);
+ }
+
+ [GtkCallback] private void seek_forward () {
+ // 10 seconds
+ double new_position = playbin.position + 10.0;
+ if (new_position > this.playbin.duration) new_position = this.playbin.duration;
+ this.playbin.seek (new_position);
+ }
+
+ [GtkCallback] private string song_title (Subsonic.Song? song) {
+ return song == null ? "" : song.title;
+ }
+
+ [GtkCallback] private string song_artist (Subsonic.Song? song) {
+ return song == null ? "" : song.artist;
+ }
+
+ [GtkCallback] private string song_album (Subsonic.Song? song) {
+ return song == null ? "" : song.album;
+ }
+}
diff --git a/src/ui/window.blp b/src/ui/window.blp
index e52b9c2..0cc223f 100644
--- a/src/ui/window.blp
+++ b/src/ui/window.blp
@@ -95,156 +95,9 @@ template $UiWindow: Adw.ApplicationWindow {
Box {}
[bottom-bar]
- CenterBox {
- styles [
- "toolbar",
- ]
-
- [start]
- Box {
- orientation: vertical;
- valign: center;
-
- Label {
- styles [ "heading" ]
- xalign: 0;
- halign: start;
- label: bind $song_title (template.song) as ;
- ellipsize: end;
- }
-
- Label {
- styles [ "caption" ]
- xalign: 0;
- label: bind $song_artist (template.song) as ;
- ellipsize: end;
- }
-
- Label {
- styles [ "caption" ]
- xalign: 0;
- label: bind $song_album (template.song) as ;
- ellipsize: end;
- }
- }
-
- [center]
- Box {
- orientation: vertical;
- halign: center;
- hexpand: true;
-
- CenterBox {
- [start]
- Label play_position_label {
- styles [
- "caption",
- "numeric",
- ]
-
- label: bind $format_timestamp (template.playbin as <$Playbin>.position) as ;
- }
-
- [center]
- Scale play_position {
- name: "seek-scale";
- orientation: horizontal;
- width-request: 400;
- sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
-
- adjustment: Adjustment {
- lower: 0;
- value: bind template.playbin as <$Playbin>.position;
- upper: bind template.playbin as <$Playbin>.duration;
- };
-
- change-value => $on_play_position_seek ();
- }
-
- [end]
- Label play_duration {
- styles [
- "caption",
- "numeric",
- ]
-
- label: bind $format_timestamp (template.playbin as <$Playbin>.duration) as ;
- }
- }
-
- Box {
- halign: center;
- orientation: horizontal;
-
- Button {
- icon-name: "media-skip-backward";
- valign: center;
- sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
-
- clicked => $on_skip_backward_clicked ();
- }
-
- Button {
- icon-name: "media-seek-backward";
- valign: center;
- sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
-
- clicked => $seek_backward ();
- }
-
- Button {
- icon-name: bind $play_pause_icon_name (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
- valign: center;
- sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
-
- clicked => $on_play_pause_clicked ();
- }
-
- Button {
- icon-name: "media-seek-forward";
- valign: center;
- sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
-
- clicked => $seek_forward ();
- }
-
- Button {
- icon-name: "media-skip-forward";
- valign: center;
- sensitive: bind $playbin_active (template.playbin as <$Playbin>.state as <$PlaybinState>) as ;
-
- clicked => $on_skip_forward_clicked ();
- }
- }
- }
-
- [end]
- Box {
- Button {
- icon-name: "non-starred";
- valign: center;
- }
-
- Button {
- icon-name: bind $mute_button_icon_name (template.mute) as ;
- valign: center;
-
- clicked => $on_mute_toggle ();
- }
-
- Scale {
- name: "volume-scale";
- orientation: horizontal;
- width-request: 130;
-
- adjustment: Adjustment {
- lower: 0;
- value: bind template.volume bidirectional;
- upper: 100;
- };
- }
- }
+ $UiPlaybar playbar {
+ song: bind template.song;
+ playbin: bind template.playbin;
}
};
}
-
diff --git a/src/ui/window.vala b/src/ui/window.vala
index f499053..87c75cf 100644
--- a/src/ui/window.vala
+++ b/src/ui/window.vala
@@ -5,6 +5,7 @@ class Ui.Window : Adw.ApplicationWindow {
[GtkChild] private unowned Gtk.Stack stack;
[GtkChild] public unowned Ui.PlayQueue play_queue;
+ [GtkChild] public unowned Ui.Playbar playbar;
[GtkChild] public unowned Adw.ButtonRow shuffle_all_tracks;
private Setup setup;
@@ -115,78 +116,7 @@ class Ui.Window : Adw.ApplicationWindow {
}
}
- [GtkCallback] private string format_timestamp (double s) {
- return "%02d:%02d".printf (((int) s)/60, ((int) s)%60);
- }
-
- [GtkCallback] private bool on_play_position_seek (Gtk.Range range, Gtk.ScrollType scroll_type, double value) {
- this.playbin.seek ((int64) value);
- return false;
- }
-
- [GtkCallback] private void on_play_pause_clicked () {
- if (this.playbin.state == PlaybinState.PLAYING) {
- this.playbin.pause();
- } else {
- this.playbin.play();
- }
- }
-
- [GtkCallback] private string play_pause_icon_name (PlaybinState state) {
- if (state == PlaybinState.PLAYING) {
- return "media-playback-pause";
- } else {
- return "media-playback-start";
- }
- }
-
- [GtkCallback] private bool playbin_active (PlaybinState state) {
- return state != PlaybinState.STOPPED;
- }
-
- [GtkCallback] private string mute_button_icon_name (bool mute) {
- return mute ? "audio-volume-muted" : "audio-volume-high";
- }
-
- [GtkCallback] private void on_mute_toggle () {
- this.mute = !this.mute;
- }
-
- [GtkCallback] private void on_skip_forward_clicked () {
- this.playbin.next_track ();
- }
-
- [GtkCallback] private void on_skip_backward_clicked () {
- this.playbin.prev_track ();
- }
-
[GtkCallback] private void show_setup_dialog () {
this.setup.present (this);
}
-
- [GtkCallback] private void seek_backward () {
- // 10 seconds
- double new_position = playbin.position - 10.0;
- if (new_position < 0.0) new_position = 0.0;
- this.playbin.seek (new_position);
- }
-
- [GtkCallback] private void seek_forward () {
- // 10 seconds
- double new_position = playbin.position + 10.0;
- if (new_position > this.playbin.duration) new_position = this.playbin.duration;
- this.playbin.seek (new_position);
- }
-
- [GtkCallback] private string song_title (Subsonic.Song? song) {
- return song == null ? "" : song.title;
- }
-
- [GtkCallback] private string song_artist (Subsonic.Song? song) {
- return song == null ? "" : song.artist;
- }
-
- [GtkCallback] private string song_album (Subsonic.Song? song) {
- return song == null ? "" : song.album;
- }
}