brand new play queue selection logic
This commit is contained in:
parent
7b66f3b18c
commit
1584ebc161
2 changed files with 137 additions and 91 deletions
|
@ -15,18 +15,11 @@ template $UiPlayQueue: Adw.NavigationPage {
|
|||
}
|
||||
|
||||
ScrolledWindow {
|
||||
ColumnView {
|
||||
ColumnView view {
|
||||
styles [ "data-table" ]
|
||||
|
||||
model: SingleSelection selection {
|
||||
model: bind template.songs;
|
||||
selected: bind template.selected_index;
|
||||
selection-changed => $on_song_selected ();
|
||||
};
|
||||
|
||||
ColumnViewColumn {
|
||||
factory: SignalListItemFactory {
|
||||
setup => $delete_cell_setup ();
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,113 +1,166 @@
|
|||
class Ui.PlayQueueStore : Object, ListModel, Gtk.SelectionModel {
|
||||
public ListStore inner = new ListStore (typeof (Song));
|
||||
public uint playing_index { get; private set; default = 0; }
|
||||
|
||||
private bool starting_stream = false;
|
||||
|
||||
public signal void stop_stream ();
|
||||
public signal void start_stream (Song song);
|
||||
// public void on_song_start ();
|
||||
public signal void now_playing (Song song);
|
||||
public signal void prepare_next_song (Song? song);
|
||||
// public void on_stream_end ();
|
||||
|
||||
construct {
|
||||
this.inner.items_changed.connect ((position, removed, added) => {
|
||||
if (this.playing_index >= position) {
|
||||
if (this.playing_index < position+removed) {
|
||||
this.playing_index = position;
|
||||
if (this.playing_index < this.inner.get_n_items ()) {
|
||||
this.starting_stream = true;
|
||||
this.now_playing ((Song) this.inner.get_item (this.playing_index));
|
||||
this.start_stream((Song) this.inner.get_item (this.playing_index));
|
||||
} else {
|
||||
this.stop_stream ();
|
||||
}
|
||||
} else {
|
||||
this.playing_index += added;
|
||||
this.playing_index -= removed;
|
||||
}
|
||||
} else {
|
||||
this.prepare_next_song ((Song?) this.inner.get_item (this.playing_index+1));
|
||||
}
|
||||
|
||||
this.items_changed (position, removed, added);
|
||||
if (this.playing_index < this.inner.get_n_items ()) {
|
||||
this.selection_changed (this.playing_index, 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void on_stream_start () {
|
||||
if (!starting_stream) {
|
||||
this.playing_index += 1;
|
||||
this.selection_changed (this.playing_index-1, 2);
|
||||
this.now_playing ((Song) this.inner.get_item (this.playing_index));
|
||||
}
|
||||
|
||||
this.prepare_next_song ((Song?) this.inner.get_item (this.playing_index+1));
|
||||
starting_stream = false;
|
||||
}
|
||||
|
||||
Gtk.Bitset get_selection_in_range (uint position, uint n_items) {
|
||||
var bitset = new Gtk.Bitset.empty ();
|
||||
if (this.playing_index < this.inner.get_n_items ()) {
|
||||
bitset.add (playing_index);
|
||||
}
|
||||
return bitset;
|
||||
}
|
||||
|
||||
bool is_selected (uint position) {
|
||||
return position == this.playing_index;
|
||||
}
|
||||
|
||||
bool select_all () {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool select_item (uint position, bool unselect_rest) {
|
||||
if (!unselect_rest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var previous = this.playing_index;
|
||||
this.playing_index = position;
|
||||
|
||||
if (previous < this.inner.get_n_items ()) {
|
||||
this.selection_changed (previous, 1);
|
||||
}
|
||||
this.selection_changed (position, 1);
|
||||
|
||||
this.starting_stream = true;
|
||||
this.now_playing ((Song) this.inner.get_item (this.playing_index));
|
||||
this.start_stream ((Song) this.inner.get_item (this.playing_index));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool select_range (uint position, uint n_items, bool unselect_rest) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool set_selection (Gtk.Bitset selected, Gtk.Bitset mask) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool unselect_all () {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool unselect_item (uint position) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool unselect_range (uint position, uint n_items) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Object? get_item (uint position) {
|
||||
return this.inner.get_item (position);
|
||||
}
|
||||
|
||||
Type get_item_type () {
|
||||
return this.inner.get_item_type ();
|
||||
}
|
||||
|
||||
uint get_n_items () {
|
||||
return this.inner.get_n_items ();
|
||||
}
|
||||
}
|
||||
|
||||
[GtkTemplate (ui = "/eu/callcc/audrey/ui/play_queue.ui")]
|
||||
public class Ui.PlayQueue : Adw.NavigationPage {
|
||||
public ListStore songs { get; private set; }
|
||||
[GtkChild] private unowned Gtk.ColumnView view;
|
||||
PlayQueueStore store = new PlayQueueStore ();
|
||||
|
||||
public uint selected_index { get; set; }
|
||||
// this is the index of the song that will play on next on_stream_start
|
||||
private uint next_stream_index;
|
||||
|
||||
public signal void play_next (Song? song);
|
||||
public signal void play_now (Song song);
|
||||
|
||||
public signal void play_next (Song? song);
|
||||
public signal void now_playing (Song song);
|
||||
|
||||
private bool ignore_selection = false;
|
||||
|
||||
[GtkChild] private unowned Gtk.SingleSelection selection;
|
||||
|
||||
public bool can_clear_all { get; private set; default = false; }
|
||||
|
||||
construct {
|
||||
this.songs = new ListStore (typeof (Song));
|
||||
this.next_stream_index = 0;
|
||||
this.view.model = this.store;
|
||||
}
|
||||
|
||||
[GtkCallback] public void clear () {
|
||||
this.songs.remove_all ();
|
||||
this.store.inner.remove_all ();
|
||||
this.can_clear_all = false;
|
||||
|
||||
this.store.start_stream.connect ((song) => this.play_now(song));
|
||||
this.store.now_playing.connect ((song) => this.now_playing(song));
|
||||
this.store.prepare_next_song.connect ((song) => this.play_next(song));
|
||||
}
|
||||
|
||||
public void queue (Song song) {
|
||||
uint new_index = this.songs.get_n_items ();
|
||||
this.songs.append (song);
|
||||
if (new_index == next_stream_index) {
|
||||
this.play_next (song);
|
||||
}
|
||||
this.can_clear_all = true;
|
||||
this.store.inner.append (song);
|
||||
}
|
||||
|
||||
[GtkCallback] private void on_song_selected () {
|
||||
this.selected_index = this.selection.selected; // manual bidi binding
|
||||
if (this.ignore_selection) return;
|
||||
this.pick_song (this.selected_index);
|
||||
}
|
||||
|
||||
private void pick_song (uint index) {
|
||||
Song? song = (Song?) this.songs.get_item (index);
|
||||
if (song != null) {
|
||||
this.ignore_selection = true;
|
||||
this.selected_index = index;
|
||||
this.ignore_selection = false;
|
||||
|
||||
this.next_stream_index = index;
|
||||
this.play_next (song);
|
||||
this.play_now (song);
|
||||
}
|
||||
}
|
||||
|
||||
internal void on_stream_start (Playbin playbin, string uri) {
|
||||
Song song = (Song) this.songs.get_item (this.next_stream_index);
|
||||
if (public_api.stream_uri (song.id) != uri) {
|
||||
// prerolled track wasnt actually next one!
|
||||
// this can happen if it was deleted from the play queue after about-to-finish was signalled
|
||||
// gapless playback is ruined anyway, go wild
|
||||
this.pick_song (this.next_stream_index);
|
||||
return;
|
||||
}
|
||||
this.now_playing (song);
|
||||
|
||||
this.ignore_selection = true;
|
||||
this.selected_index = this.next_stream_index;
|
||||
this.ignore_selection = false;
|
||||
|
||||
// prepare for next song ahead of time (gapless)
|
||||
this.next_stream_index += 1;
|
||||
Song? next_song = (Song?) this.songs.get_item (this.next_stream_index);
|
||||
this.play_next (next_song);
|
||||
internal void on_stream_start () {
|
||||
this.store.on_stream_start ();
|
||||
}
|
||||
|
||||
internal void restart () {
|
||||
this.pick_song (0);
|
||||
this.store.select_item (0, true);
|
||||
}
|
||||
|
||||
public void skip_forward () {
|
||||
this.pick_song (this.selected_index + 1);
|
||||
this.store.select_item (this.store.playing_index+1, true);
|
||||
}
|
||||
|
||||
public void skip_backward () {
|
||||
if (this.selected_index >= 1) {
|
||||
this.pick_song (this.selected_index - 1);
|
||||
if (this.store.playing_index >= 1) {
|
||||
this.store.select_item (this.store.playing_index-1, true);
|
||||
}
|
||||
}
|
||||
|
||||
[GtkCallback] private void delete_cell_setup (Object object) {
|
||||
Gtk.ColumnViewCell cell = (Gtk.ColumnViewCell) object;
|
||||
Gtk.Button button = new Gtk.Button.from_icon_name ("edit-delete");
|
||||
button.add_css_class ("flat");
|
||||
cell.child = button;
|
||||
button.clicked.connect (() => {
|
||||
this.songs.remove (cell.position);
|
||||
this.can_clear_all = this.songs.get_n_items() > 0;
|
||||
|
||||
if (cell.position == this.next_stream_index) {
|
||||
// we just deleted the track that was to be prerolled next
|
||||
// replace it
|
||||
this.play_next ((Song?) this.songs.get_item (cell.position));
|
||||
} else if (cell.position+1 == this.next_stream_index) {
|
||||
// conversely, we just deleted the currently playing track
|
||||
// redo
|
||||
this.pick_song (cell.position);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue