mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-27 12:17:24 +01:00
Initial port to GTK4
This commit is contained in:
parent
1a9e58d627
commit
801a130ef8
76 changed files with 3098 additions and 6625 deletions
|
|
@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen};
|
|||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -63,22 +64,22 @@ impl EnsembleSelector {
|
|||
async move { clone.backend.db().get_ensembles().await.unwrap() }
|
||||
}));
|
||||
|
||||
this.selector.set_make_widget(|ensemble| {
|
||||
let label = gtk::Label::new(Some(&ensemble.name));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
this.selector.set_make_widget(clone!(@strong this => move |ensemble| {
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&ensemble.name));
|
||||
|
||||
let ensemble = ensemble.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
this.select(&ensemble);
|
||||
}));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.selector
|
||||
.set_filter(|search, ensemble| ensemble.name.to_lowercase().contains(search));
|
||||
|
||||
this.selector
|
||||
.set_selected_cb(clone!(@strong this => move |ensemble| this.select(ensemble)));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ impl EnsembleSelector {
|
|||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Select a ensemble.
|
||||
/// Select an ensemble.
|
||||
fn select(&self, ensemble: &Ensemble) {
|
||||
if let Some(cb) = &*self.selected_cb.borrow() {
|
||||
cb(&ensemble);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen};
|
|||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -63,22 +64,22 @@ impl InstrumentSelector {
|
|||
async move { clone.backend.db().get_instruments().await.unwrap() }
|
||||
}));
|
||||
|
||||
this.selector.set_make_widget(|instrument| {
|
||||
let label = gtk::Label::new(Some(&instrument.name));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
this.selector.set_make_widget(clone!(@strong this => move |instrument| {
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&instrument.name));
|
||||
|
||||
let instrument = instrument.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
this.select(&instrument);
|
||||
}));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.selector
|
||||
.set_filter(|search, instrument| instrument.name.to_lowercase().contains(search));
|
||||
|
||||
this.selector
|
||||
.set_selected_cb(clone!(@strong this => move |instrument| this.select(instrument)));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ impl InstrumentSelector {
|
|||
self.selected_cb.replace(Some(Box::new(cb)));
|
||||
}
|
||||
|
||||
/// Select a instrument.
|
||||
/// Select an instrument.
|
||||
fn select(&self, instrument: &Instrument) {
|
||||
if let Some(cb) = &*self.selected_cb.borrow() {
|
||||
cb(&instrument);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen};
|
|||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -63,22 +64,22 @@ impl PersonSelector {
|
|||
async move { clone.backend.db().get_persons().await.unwrap() }
|
||||
}));
|
||||
|
||||
this.selector.set_make_widget(|person| {
|
||||
let label = gtk::Label::new(Some(&person.name_lf()));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
this.selector.set_make_widget(clone!(@strong this => move |person| {
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&person.name_lf()));
|
||||
|
||||
let person = person.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
this.select(&person);
|
||||
}));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.selector
|
||||
.set_filter(|search, person| person.name_fl().to_lowercase().contains(search));
|
||||
|
||||
this.selector
|
||||
.set_selected_cb(clone!(@strong this => move |person| this.select(person)));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen};
|
|||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -73,23 +74,23 @@ impl RecordingSelector {
|
|||
async move { clone.backend.db().get_recordings_for_work(&clone.work.id).await.unwrap() }
|
||||
}));
|
||||
|
||||
this.selector.set_make_widget(|recording| {
|
||||
let label = gtk::Label::new(Some(&recording.get_performers()));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
this.selector.set_make_widget(clone!(@strong this => move |recording| {
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&recording.get_performers()));
|
||||
|
||||
let recording = recording.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
this.select(&recording);
|
||||
}));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.selector.set_filter(|search, recording| {
|
||||
recording.get_performers().to_lowercase().contains(search)
|
||||
});
|
||||
|
||||
this.selector
|
||||
.set_selected_cb(clone!(@strong this => move |recording| this.select(recording)));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
use crate::widgets::List;
|
||||
use anyhow::Result;
|
||||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use gtk_macros::get_widget;
|
||||
use libhandy::HeaderBarExt;
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
|
|
@ -14,12 +12,16 @@ use std::rc::Rc;
|
|||
/// database and to search within the list.
|
||||
pub struct Selector<T: 'static> {
|
||||
pub widget: gtk::Box,
|
||||
header: libhandy::HeaderBar,
|
||||
title_label: gtk::Label,
|
||||
subtitle_label: gtk::Label,
|
||||
search_entry: gtk::SearchEntry,
|
||||
server_check_button: gtk::CheckButton,
|
||||
stack: gtk::Stack,
|
||||
list: Rc<List<T>>,
|
||||
list: Rc<List>,
|
||||
items: RefCell<Vec<T>>,
|
||||
back_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
|
||||
add_cb: RefCell<Option<Box<dyn Fn() -> ()>>>,
|
||||
make_widget: RefCell<Option<Box<dyn Fn(&T) -> gtk::Widget>>>,
|
||||
load_online: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Result<Vec<T>>>>>>>,
|
||||
load_local: RefCell<Option<Box<dyn Fn() -> Box<dyn Future<Output = Vec<T>>>>>>,
|
||||
filter: RefCell<Option<Box<dyn Fn(&str, &T) -> bool>>>,
|
||||
|
|
@ -33,7 +35,8 @@ impl<T> Selector<T> {
|
|||
let builder = gtk::Builder::from_resource("/de/johrpan/musicus/ui/selector.ui");
|
||||
|
||||
get_widget!(builder, gtk::Box, widget);
|
||||
get_widget!(builder, libhandy::HeaderBar, header);
|
||||
get_widget!(builder, gtk::Label, title_label);
|
||||
get_widget!(builder, gtk::Label, subtitle_label);
|
||||
get_widget!(builder, gtk::Button, back_button);
|
||||
get_widget!(builder, gtk::Button, add_button);
|
||||
get_widget!(builder, gtk::SearchEntry, search_entry);
|
||||
|
|
@ -42,17 +45,21 @@ impl<T> Selector<T> {
|
|||
get_widget!(builder, gtk::Frame, frame);
|
||||
get_widget!(builder, gtk::Button, try_again_button);
|
||||
|
||||
let list = List::<T>::new(&gettext("Nothing found."));
|
||||
frame.add(&list.widget);
|
||||
let list = List::new();
|
||||
frame.set_child(Some(&list.widget));
|
||||
|
||||
let this = Rc::new(Self {
|
||||
widget,
|
||||
header,
|
||||
title_label,
|
||||
subtitle_label,
|
||||
search_entry,
|
||||
server_check_button,
|
||||
stack,
|
||||
list,
|
||||
items: RefCell::new(Vec::new()),
|
||||
back_cb: RefCell::new(None),
|
||||
add_cb: RefCell::new(None),
|
||||
make_widget: RefCell::new(None),
|
||||
load_online: RefCell::new(None),
|
||||
load_local: RefCell::new(None),
|
||||
filter: RefCell::new(None),
|
||||
|
|
@ -72,7 +79,7 @@ impl<T> Selector<T> {
|
|||
}
|
||||
}));
|
||||
|
||||
search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.search_entry.connect_search_changed(clone!(@strong this => move |_| {
|
||||
this.list.invalidate_filter();
|
||||
}));
|
||||
|
||||
|
|
@ -85,17 +92,25 @@ impl<T> Selector<T> {
|
|||
}
|
||||
}));
|
||||
|
||||
this.list.set_filter(
|
||||
clone!(@strong this, @strong search_entry => move |item: &T| {
|
||||
match &*this.filter.borrow() {
|
||||
Some(filter) => {
|
||||
let search = search_entry.get_text().to_string().to_lowercase();
|
||||
search.is_empty() || filter(&search, item)
|
||||
}
|
||||
None => true,
|
||||
this.list.set_make_widget_cb(clone!(@strong this => move |index| {
|
||||
if let Some(cb) = &*this.make_widget.borrow() {
|
||||
let item = &this.items.borrow()[index];
|
||||
cb(item)
|
||||
} else {
|
||||
gtk::Label::new(None).upcast()
|
||||
}
|
||||
}));
|
||||
|
||||
this.list.set_filter_cb(clone!(@strong this => move |index| {
|
||||
match &*this.filter.borrow() {
|
||||
Some(filter) => {
|
||||
let item = &this.items.borrow()[index];
|
||||
let search = this.search_entry.get_text().unwrap().to_string().to_lowercase();
|
||||
search.is_empty() || filter(&search, item)
|
||||
}
|
||||
}),
|
||||
);
|
||||
None => true,
|
||||
}
|
||||
}));
|
||||
|
||||
try_again_button.connect_clicked(clone!(@strong this => move |_| {
|
||||
this.clone().load_online();
|
||||
|
|
@ -109,12 +124,13 @@ impl<T> Selector<T> {
|
|||
|
||||
/// Set the title to be shown in the header.
|
||||
pub fn set_title(&self, title: &str) {
|
||||
self.header.set_title(Some(title));
|
||||
self.title_label.set_label(&title);
|
||||
}
|
||||
|
||||
/// Set the subtitle to be shown in the header.
|
||||
pub fn set_subtitle(&self, subtitle: &str) {
|
||||
self.header.set_subtitle(Some(subtitle));
|
||||
self.subtitle_label.set_label(&subtitle);
|
||||
self.subtitle_label.show();
|
||||
}
|
||||
|
||||
/// Set the closure to be called when the user wants to go back.
|
||||
|
|
@ -150,7 +166,7 @@ impl<T> Selector<T> {
|
|||
|
||||
/// Set the closure to be called for creating a new list row.
|
||||
pub fn set_make_widget<F: Fn(&T) -> gtk::Widget + 'static>(&self, make_widget: F) {
|
||||
self.list.set_make_widget(make_widget);
|
||||
self.make_widget.replace(Some(Box::new(make_widget)));
|
||||
}
|
||||
|
||||
/// Set a closure to call when deciding whether to show an item based on a search string. The
|
||||
|
|
@ -159,11 +175,6 @@ impl<T> Selector<T> {
|
|||
self.filter.replace(Some(Box::new(filter)));
|
||||
}
|
||||
|
||||
/// Set the closure to be called when an item is selected.
|
||||
pub fn set_selected_cb<F: Fn(&T) -> () + 'static>(&self, cb: F) {
|
||||
self.list.set_selected(cb);
|
||||
}
|
||||
|
||||
fn load_online(self: Rc<Self>) {
|
||||
let context = glib::MainContext::default();
|
||||
let clone = self.clone();
|
||||
|
|
@ -173,11 +184,10 @@ impl<T> Selector<T> {
|
|||
|
||||
match Pin::from(cb()).await {
|
||||
Ok(items) => {
|
||||
clone.list.show_items(items);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
clone.show_items(items);
|
||||
}
|
||||
Err(_) => {
|
||||
clone.list.show_items(Vec::new());
|
||||
clone.show_items(Vec::new());
|
||||
clone.stack.set_visible_child_name("error");
|
||||
}
|
||||
}
|
||||
|
|
@ -193,9 +203,15 @@ impl<T> Selector<T> {
|
|||
self.stack.set_visible_child_name("loading");
|
||||
|
||||
let items = Pin::from(cb()).await;
|
||||
clone.list.show_items(items);
|
||||
clone.stack.set_visible_child_name("content");
|
||||
clone.show_items(items);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn show_items(&self, items: Vec<T>) {
|
||||
let length = items.len();
|
||||
self.items.replace(items);
|
||||
self.list.update(length);
|
||||
self.stack.set_visible_child_name("content");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use crate::widgets::{Navigator, NavigatorScreen};
|
|||
use gettextrs::gettext;
|
||||
use glib::clone;
|
||||
use gtk::prelude::*;
|
||||
use libhandy::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
|
||||
|
|
@ -73,22 +74,22 @@ impl WorkSelector {
|
|||
async move { clone.backend.db().get_works(&clone.person.id).await.unwrap() }
|
||||
}));
|
||||
|
||||
this.selector.set_make_widget(|work| {
|
||||
let label = gtk::Label::new(Some(&work.title));
|
||||
label.set_halign(gtk::Align::Start);
|
||||
label.set_margin_start(6);
|
||||
label.set_margin_end(6);
|
||||
label.set_margin_top(6);
|
||||
label.set_margin_bottom(6);
|
||||
label.upcast()
|
||||
});
|
||||
this.selector.set_make_widget(clone!(@strong this => move |work| {
|
||||
let row = libhandy::ActionRow::new();
|
||||
row.set_activatable(true);
|
||||
row.set_title(Some(&work.title));
|
||||
|
||||
let work = work.to_owned();
|
||||
row.connect_activated(clone!(@strong this => move |_| {
|
||||
this.select(&work);
|
||||
}));
|
||||
|
||||
row.upcast()
|
||||
}));
|
||||
|
||||
this.selector
|
||||
.set_filter(|search, work| work.title.to_lowercase().contains(search));
|
||||
|
||||
this.selector
|
||||
.set_selected_cb(clone!(@strong this => move |work| this.select(work)));
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue