Compare commits
2 commits
930430d5ec
...
dae2882e0d
Author | SHA1 | Date | |
---|---|---|---|
dae2882e0d | |||
b5fadce142 |
8 changed files with 99 additions and 50 deletions
|
@ -3,10 +3,10 @@ using Gtk 4.0;
|
||||||
template $AudreyUiPlayQueueSong: Box {
|
template $AudreyUiPlayQueueSong: Box {
|
||||||
height-request: 48;
|
height-request: 48;
|
||||||
spacing: 12;
|
spacing: 12;
|
||||||
margin-start: 6;
|
|
||||||
margin-end: 6;
|
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
|
margin-start: 6;
|
||||||
|
|
||||||
width-request: 36;
|
width-request: 36;
|
||||||
focusable: false;
|
focusable: false;
|
||||||
homogeneous: true;
|
homogeneous: true;
|
||||||
|
@ -39,7 +39,7 @@ template $AudreyUiPlayQueueSong: Box {
|
||||||
margin-top: 1;
|
margin-top: 1;
|
||||||
margin-bottom: 1;
|
margin-bottom: 1;
|
||||||
pixel-size: 50;
|
pixel-size: 50;
|
||||||
// paintable: bind template.song as <$AudreyPlaybinSong>.thumbnail;
|
// paintable: bind template.song as <$AudreyModelSong>.thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
Box title_box {
|
Box title_box {
|
||||||
|
@ -70,7 +70,7 @@ template $AudreyUiPlayQueueSong: Box {
|
||||||
ellipsize: end;
|
ellipsize: end;
|
||||||
max-width-chars: 90;
|
max-width-chars: 90;
|
||||||
justify: fill;
|
justify: fill;
|
||||||
label: bind template.song as <$AudreyPlaybinSong>.title;
|
label: bind template.song as <$AudreyModelSong>.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -85,7 +85,7 @@ template $AudreyUiPlayQueueSong: Box {
|
||||||
ellipsize: end;
|
ellipsize: end;
|
||||||
max-width-chars: 90;
|
max-width-chars: 90;
|
||||||
justify: fill;
|
justify: fill;
|
||||||
label: bind template.song as <$AudreyPlaybinSong>.artist;
|
label: bind template.song as <$AudreyModelSong>.artist;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,12 +101,12 @@ template $AudreyUiPlayQueueSong: Box {
|
||||||
"dim-label"
|
"dim-label"
|
||||||
]
|
]
|
||||||
|
|
||||||
label: bind $format_duration(template.song as <$AudreyPlaybinSong>.duration) as <string>;
|
label: bind $format_duration(template.song as <$AudreyModelSong>.duration) as <string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
focusable: true;
|
focusable: true;
|
||||||
// TODO icon-name: bind $star_button_icon_name (template.song as <$AudreyPlaybinSong>.starred) as <string>;
|
// TODO icon-name: bind $star_button_icon_name (template.song as <$AudreyModelSong>.starred) as <string>;
|
||||||
icon-name: bind $star_button_icon_name() as <string>;
|
icon-name: bind $star_button_icon_name() as <string>;
|
||||||
|
|
||||||
styles [
|
styles [
|
||||||
|
@ -116,20 +116,24 @@ template $AudreyUiPlayQueueSong: Box {
|
||||||
valign: center;
|
valign: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
MenuButton {
|
Box {
|
||||||
// visible: false;
|
MenuButton {
|
||||||
focusable: true;
|
// visible: false;
|
||||||
icon-name: "view-more";
|
focusable: true;
|
||||||
|
icon-name: "view-more";
|
||||||
|
|
||||||
styles [
|
styles [
|
||||||
"flat"
|
"flat"
|
||||||
]
|
]
|
||||||
|
|
||||||
valign: center;
|
valign: center;
|
||||||
|
|
||||||
popover: PopoverMenu {
|
popover: PopoverMenu {
|
||||||
menu-model: song-menu;
|
menu-model: song-menu;
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-end: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
DragSource {
|
DragSource {
|
||||||
|
|
|
@ -19,6 +19,11 @@
|
||||||
min-height: 15px;
|
min-height: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* make drag and drop indicator take up entire perimeter */
|
||||||
|
#play-queue listview row {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
#play-queue .playing label.title {
|
#play-queue .playing label.title {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,7 @@ pub mod ui;
|
||||||
pub mod mpris;
|
pub mod mpris;
|
||||||
pub use mpris::Mpris;
|
pub use mpris::Mpris;
|
||||||
|
|
||||||
pub mod playbin_song;
|
pub mod model;
|
||||||
pub use playbin_song::Song as PlaybinSong;
|
|
||||||
|
|
||||||
pub mod subsonic;
|
pub mod subsonic;
|
||||||
|
|
||||||
|
|
2
src/model.rs
Normal file
2
src/model.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
mod song;
|
||||||
|
pub use song::Song;
|
|
@ -35,7 +35,7 @@ mod imp {
|
||||||
|
|
||||||
#[glib::object_subclass]
|
#[glib::object_subclass]
|
||||||
impl ObjectSubclass for Song {
|
impl ObjectSubclass for Song {
|
||||||
const NAME: &'static str = "AudreyPlaybinSong";
|
const NAME: &'static str = "AudreyModelSong";
|
||||||
type Type = super::Song;
|
type Type = super::Song;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod imp {
|
mod imp {
|
||||||
use crate::PlaybinSong;
|
use crate::model::Song as ModelSong;
|
||||||
use glib::subclass::InitializingObject;
|
use glib::subclass::InitializingObject;
|
||||||
use gtk::{gdk, gio, glib, prelude::*, subclass::prelude::*};
|
use gtk::{gdk, gio, glib, prelude::*, subclass::prelude::*};
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
|
@ -28,7 +28,7 @@ mod imp {
|
||||||
#[property(set, get)]
|
#[property(set, get)]
|
||||||
displayed_position: Cell<u32>,
|
displayed_position: Cell<u32>,
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
song: RefCell<Option<PlaybinSong>>,
|
song: RefCell<Option<ModelSong>>,
|
||||||
|
|
||||||
drag_pos: Cell<(i32, i32)>,
|
drag_pos: Cell<(i32, i32)>,
|
||||||
drag_widget: Cell<Option<gtk::ListBox>>,
|
drag_widget: Cell<Option<gtk::ListBox>>,
|
||||||
|
@ -112,8 +112,7 @@ mod imp {
|
||||||
fn on_drag_prepare(&self, x: f64, y: f64) -> Option<gdk::ContentProvider> {
|
fn on_drag_prepare(&self, x: f64, y: f64) -> Option<gdk::ContentProvider> {
|
||||||
if self.draggable.get() {
|
if self.draggable.get() {
|
||||||
self.drag_pos.replace((x as i32, y as i32));
|
self.drag_pos.replace((x as i32, y as i32));
|
||||||
let value = self.obj().to_value();
|
Some(gdk::ContentProvider::for_value(&self.obj().to_value()))
|
||||||
Some(gdk::ContentProvider::for_value(&value))
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -122,6 +121,7 @@ mod imp {
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn on_drag_begin(&self, drag: &gdk::Drag) {
|
fn on_drag_begin(&self, drag: &gdk::Drag) {
|
||||||
let drag_widget = gtk::ListBox::new();
|
let drag_widget = gtk::ListBox::new();
|
||||||
|
drag_widget.add_css_class("boxed-list");
|
||||||
|
|
||||||
let drag_row: super::Song = glib::Object::new();
|
let drag_row: super::Song = glib::Object::new();
|
||||||
drag_row.set_draggable(false);
|
drag_row.set_draggable(false);
|
||||||
|
@ -148,19 +148,23 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn on_drop(&self, _value: glib::Value, _x: f64, _y: f64) -> bool {
|
// why BoxedValue? see https://discourse.gnome.org/t/gtk-rs-passing-widget-as-gvalue-through-drag-and-drop-with-composite-template/16449/2
|
||||||
/* FIXME: WrongValueType(ValueTypeMismatchError { actual: GValue, requested: AudreyUiPlayQueueSong })
|
fn on_drop(&self, value: glib::BoxedValue, _x: f64, _y: f64) -> bool {
|
||||||
let source: super::Song = value.get().unwrap();
|
let source: super::Song = value.get().unwrap();
|
||||||
|
|
||||||
source.imp().drag_widget.set(None);
|
source.imp().drag_widget.set(None);
|
||||||
|
|
||||||
self.obj().playbin().move_track(
|
let from = source.position();
|
||||||
source.displayed_position() - 1,
|
let to = self.obj().position();
|
||||||
self.obj().displayed_position() - 1,
|
|
||||||
);
|
|
||||||
|
|
||||||
true */
|
// see playlist_move for justification
|
||||||
false
|
if from < to {
|
||||||
|
self.obj().window().playlist_move(from, to + 1);
|
||||||
|
} else {
|
||||||
|
self.obj().window().playlist_move(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_playlist_pos(&self, playlist_pos: i32) {
|
fn set_playlist_pos(&self, playlist_pos: i32) {
|
||||||
|
@ -170,7 +174,7 @@ mod imp {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::PlaybinSong;
|
use crate::model::Song as ModelSong;
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use gtk::glib;
|
use gtk::glib;
|
||||||
|
|
||||||
|
@ -186,7 +190,7 @@ impl Song {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bind(&self, position: u32, window: &crate::ui::Window) {
|
pub fn bind(&self, position: u32, window: &crate::ui::Window) {
|
||||||
let song: PlaybinSong = window
|
let song: ModelSong = window
|
||||||
.playlist_model()
|
.playlist_model()
|
||||||
.item(position)
|
.item(position)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
mod imp {
|
mod imp {
|
||||||
use crate::PlaybinSong;
|
use crate::model::Song;
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use glib::subclass::InitializingObject;
|
use glib::subclass::InitializingObject;
|
||||||
|
@ -16,7 +16,7 @@ mod imp {
|
||||||
pulse_bar: TemplateChild<gtk::ProgressBar>,
|
pulse_bar: TemplateChild<gtk::ProgressBar>,
|
||||||
|
|
||||||
#[property(get, set, nullable)]
|
#[property(get, set, nullable)]
|
||||||
song: RefCell<Option<PlaybinSong>>,
|
song: RefCell<Option<Song>>,
|
||||||
#[property(get, set)]
|
#[property(get, set)]
|
||||||
playing_cover_art: RefCell<Option<gdk::Paintable>>,
|
playing_cover_art: RefCell<Option<gdk::Paintable>>,
|
||||||
#[property(get, set, default = true)]
|
#[property(get, set, default = true)]
|
||||||
|
@ -168,18 +168,18 @@ mod imp {
|
||||||
// these are nedeed because with regular bindings, if song becomes None, the labes are not
|
// these are nedeed because with regular bindings, if song becomes None, the labes are not
|
||||||
// updated
|
// updated
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn song_title(&self, song: Option<&PlaybinSong>) -> String {
|
fn song_title(&self, song: Option<&Song>) -> String {
|
||||||
song.map(PlaybinSong::title).unwrap_or("".to_string())
|
song.map(Song::title).unwrap_or("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn song_artist(&self, song: Option<&PlaybinSong>) -> String {
|
fn song_artist(&self, song: Option<&Song>) -> String {
|
||||||
song.map(PlaybinSong::artist).unwrap_or("".to_string())
|
song.map(Song::artist).unwrap_or("".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[template_callback]
|
#[template_callback]
|
||||||
fn song_album(&self, song: Option<&PlaybinSong>) -> String {
|
fn song_album(&self, song: Option<&Song>) -> String {
|
||||||
song.map(PlaybinSong::album).unwrap_or("".to_string())
|
song.map(Song::album).unwrap_or("".to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
mod imp {
|
mod imp {
|
||||||
|
use crate::model::Song;
|
||||||
use crate::mpv;
|
use crate::mpv;
|
||||||
use crate::PlaybinSong;
|
|
||||||
use adw::prelude::*;
|
use adw::prelude::*;
|
||||||
use adw::subclass::prelude::*;
|
use adw::subclass::prelude::*;
|
||||||
use glib::subclass::InitializingObject;
|
use glib::subclass::InitializingObject;
|
||||||
|
@ -26,7 +26,7 @@ mod imp {
|
||||||
#[property(get, set, nullable)]
|
#[property(get, set, nullable)]
|
||||||
playing_cover_art: RefCell<Option<gdk::Paintable>>,
|
playing_cover_art: RefCell<Option<gdk::Paintable>>,
|
||||||
|
|
||||||
#[property(type = Option<PlaybinSong>, get = Self::song, nullable)]
|
#[property(type = Option<Song>, get = Self::song, nullable)]
|
||||||
_song: (),
|
_song: (),
|
||||||
|
|
||||||
pub(super) setup: crate::ui::Setup,
|
pub(super) setup: crate::ui::Setup,
|
||||||
|
@ -95,7 +95,7 @@ mod imp {
|
||||||
setup: Default::default(),
|
setup: Default::default(),
|
||||||
api: Default::default(),
|
api: Default::default(),
|
||||||
mpv,
|
mpv,
|
||||||
playlist_model: gio::ListStore::new::<PlaybinSong>(),
|
playlist_model: gio::ListStore::new::<Song>(),
|
||||||
|
|
||||||
_volume: (),
|
_volume: (),
|
||||||
_mute: (),
|
_mute: (),
|
||||||
|
@ -398,7 +398,7 @@ mod imp {
|
||||||
Rc::clone(api.as_ref().unwrap())
|
Rc::clone(api.as_ref().unwrap())
|
||||||
};
|
};
|
||||||
for song in api.get_random_songs(10).await.unwrap().into_iter() {
|
for song in api.get_random_songs(10).await.unwrap().into_iter() {
|
||||||
let song = PlaybinSong::from_child(&api, &song);
|
let song = Song::from_child(&api, &song);
|
||||||
self.mpv
|
self.mpv
|
||||||
.command(["loadfile", &song.stream_url(), "append-play"])
|
.command(["loadfile", &song.stream_url(), "append-play"])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -474,7 +474,7 @@ mod imp {
|
||||||
|
|
||||||
{
|
{
|
||||||
let left = duration.map(|f| f as i64);
|
let left = duration.map(|f| f as i64);
|
||||||
let right = self.song().as_ref().map(crate::PlaybinSong::duration);
|
let right = self.song().as_ref().map(crate::model::Song::duration);
|
||||||
if left != right {
|
if left != right {
|
||||||
event!(
|
event!(
|
||||||
Level::WARN,
|
Level::WARN,
|
||||||
|
@ -498,11 +498,11 @@ mod imp {
|
||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn song(&self) -> Option<PlaybinSong> {
|
fn song(&self) -> Option<Song> {
|
||||||
if self.obj().playlist_pos() < 0 {
|
if self.obj().playlist_pos() < 0 {
|
||||||
None
|
None
|
||||||
} else {
|
} else {
|
||||||
let song: PlaybinSong = self
|
let song: Song = self
|
||||||
.obj()
|
.obj()
|
||||||
.playlist_model()
|
.playlist_model()
|
||||||
.item(self.obj().playlist_pos() as u32)
|
.item(self.obj().playlist_pos() as u32)
|
||||||
|
@ -627,4 +627,39 @@ impl Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn playlist_move(&self, from: u32, to: u32) {
|
||||||
|
// NOTE: for mpv, to refers to the "gap" right before i
|
||||||
|
// so playlist-move i 0 makes a track the first
|
||||||
|
// playlist-move i 1 makes a track the second
|
||||||
|
// and playlist-move i playlist-count makes a track the last
|
||||||
|
// (so if to is the position of another track, from is left behind to)
|
||||||
|
self.imp()
|
||||||
|
.mpv
|
||||||
|
.command(["playlist-move", &from.to_string(), &to.to_string()])
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if from < to {
|
||||||
|
// F1234T -> 1234FT
|
||||||
|
let mut spliced = Vec::with_capacity((to - from) as usize);
|
||||||
|
for i in from + 1..to {
|
||||||
|
spliced.push(self.playlist_model().item(i).unwrap());
|
||||||
|
}
|
||||||
|
spliced.push(self.playlist_model().item(from).unwrap());
|
||||||
|
|
||||||
|
self.playlist_model()
|
||||||
|
.splice(from, spliced.len() as u32, &spliced);
|
||||||
|
} else if to < from {
|
||||||
|
// T1234F -> FT1234
|
||||||
|
let mut spliced = Vec::with_capacity((from - to + 1) as usize);
|
||||||
|
spliced.push(self.playlist_model().item(from).unwrap());
|
||||||
|
spliced.push(self.playlist_model().item(to).unwrap());
|
||||||
|
for i in to + 1..from {
|
||||||
|
spliced.push(self.playlist_model().item(i).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playlist_model()
|
||||||
|
.splice(to, spliced.len() as u32, &spliced);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue