Initial port to GTK4

This commit is contained in:
Elias Projahn 2021-01-25 14:00:57 +01:00
parent 1a9e58d627
commit 801a130ef8
76 changed files with 3098 additions and 6625 deletions

View file

@ -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);

View file

@ -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);

View file

@ -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
}

View file

@ -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
}

View file

@ -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");
}
}

View file

@ -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
}