mirror of
https://github.com/johrpan/musicus.git
synced 2025-10-26 11:47:25 +01:00
Implement program editor
This commit is contained in:
parent
fa94d61e1e
commit
8950b04ed2
8 changed files with 590 additions and 81 deletions
|
|
@ -56,55 +56,65 @@
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
.program {
|
.program-tile {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
|
transition: transform 100ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
.program .title {
|
.program-tile:hover {
|
||||||
|
transform: scale(1.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
.program-tile:active {
|
||||||
|
transform: scale(0.99);
|
||||||
|
}
|
||||||
|
|
||||||
|
.program-tile .title {
|
||||||
margin-top: 6px;
|
margin-top: 6px;
|
||||||
font-size: larger;
|
font-size: larger;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.program.highlight {
|
.program-design-button {
|
||||||
color: white;
|
min-width: 24px;
|
||||||
transition: transform 100ms;
|
min-height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.program.highlight.program1 {
|
.program-design-button:checked {
|
||||||
|
box-shadow: 0 0 0 3px var(--accent-bg-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.program-1 {
|
||||||
|
color: white;
|
||||||
background: linear-gradient(-225deg, #ac32e4 0%, #7918f2 48%, #4801ff 100%);
|
background: linear-gradient(-225deg, #ac32e4 0%, #7918f2 48%, #4801ff 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.program.highlight.program2 {
|
.program-2 {
|
||||||
|
color: white;
|
||||||
background: linear-gradient(145deg, #f12711, #f5af19);
|
background: linear-gradient(145deg, #f12711, #f5af19);
|
||||||
}
|
}
|
||||||
|
|
||||||
.program.highlight.program3 {
|
.program-3 {
|
||||||
|
color: white;
|
||||||
background: linear-gradient(-80deg, #ad5389, #3c1053);
|
background: linear-gradient(-80deg, #ad5389, #3c1053);
|
||||||
}
|
}
|
||||||
|
|
||||||
.program.highlight.program4 {
|
.program-4 {
|
||||||
|
color: white;
|
||||||
background: linear-gradient(140deg, #136797, #0b486b);
|
background: linear-gradient(140deg, #136797, #0b486b);
|
||||||
}
|
}
|
||||||
|
|
||||||
.program.highlight.program5 {
|
.program-5 {
|
||||||
|
color: white;
|
||||||
background: linear-gradient(100deg, #6a9113, #141517);
|
background: linear-gradient(100deg, #6a9113, #141517);
|
||||||
}
|
}
|
||||||
|
|
||||||
.program.highlight.program6 {
|
.program-6 {
|
||||||
|
color: white;
|
||||||
background: linear-gradient(120deg, #870000, #190a05);
|
background: linear-gradient(120deg, #870000, #190a05);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.program.highlight:hover {
|
|
||||||
transform: scale(1.01);
|
|
||||||
}
|
|
||||||
|
|
||||||
.program.highlight:active {
|
|
||||||
transform: scale(0.99);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector>contents {
|
.selector>contents {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
229
data/ui/editor/program.blp
Normal file
229
data/ui/editor/program.blp
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
using Gtk 4.0;
|
||||||
|
using Adw 1;
|
||||||
|
|
||||||
|
template $MusicusProgramEditor: Adw.NavigationPage {
|
||||||
|
title: _("Program");
|
||||||
|
|
||||||
|
Adw.ToolbarView {
|
||||||
|
[top]
|
||||||
|
Adw.HeaderBar header_bar {}
|
||||||
|
|
||||||
|
Gtk.ScrolledWindow {
|
||||||
|
Adw.Clamp {
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
margin-bottom: 24;
|
||||||
|
margin-start: 12;
|
||||||
|
margin-end: 12;
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: _("Appearance");
|
||||||
|
xalign: 0;
|
||||||
|
margin-top: 24;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"heading",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ListBox {
|
||||||
|
selection-mode: none;
|
||||||
|
margin-top: 12;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"boxed-list",
|
||||||
|
]
|
||||||
|
|
||||||
|
Adw.EntryRow title_row {
|
||||||
|
title: _("Title");
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.EntryRow description_row {
|
||||||
|
title: _("Description");
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.PreferencesRow design_row {
|
||||||
|
title: _("Design");
|
||||||
|
activatable: false;
|
||||||
|
focusable: false;
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
orientation: vertical;
|
||||||
|
spacing: 8;
|
||||||
|
margin-start: 12;
|
||||||
|
margin-end: 12;
|
||||||
|
margin-top: 6;
|
||||||
|
margin-bottom: 6;
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: _("Design");
|
||||||
|
xalign: 0.0;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"subtitle",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Box {
|
||||||
|
spacing: 6;
|
||||||
|
|
||||||
|
Gtk.ToggleButton {
|
||||||
|
action-name: "program.set-design";
|
||||||
|
action-target: "'program-1'";
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"program-design-button",
|
||||||
|
"program-1",
|
||||||
|
"circular",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ToggleButton {
|
||||||
|
action-name: "program.set-design";
|
||||||
|
action-target: "'program-2'";
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"program-design-button",
|
||||||
|
"program-2",
|
||||||
|
"circular",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ToggleButton {
|
||||||
|
action-name: "program.set-design";
|
||||||
|
action-target: "'program-3'";
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"program-design-button",
|
||||||
|
"program-3",
|
||||||
|
"circular",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ToggleButton {
|
||||||
|
action-name: "program.set-design";
|
||||||
|
action-target: "'program-4'";
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"program-design-button",
|
||||||
|
"program-4",
|
||||||
|
"circular",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ToggleButton {
|
||||||
|
action-name: "program.set-design";
|
||||||
|
action-target: "'program-5'";
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"program-design-button",
|
||||||
|
"program-5",
|
||||||
|
"circular",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ToggleButton {
|
||||||
|
action-name: "program.set-design";
|
||||||
|
action-target: "'program-6'";
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"program-design-button",
|
||||||
|
"program-6",
|
||||||
|
"circular",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.Label {
|
||||||
|
label: _("Settings");
|
||||||
|
xalign: 0;
|
||||||
|
margin-top: 24;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"heading",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ListBox {
|
||||||
|
selection-mode: none;
|
||||||
|
margin-top: 12;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"boxed-list",
|
||||||
|
]
|
||||||
|
|
||||||
|
$MusicusSliderRow {
|
||||||
|
title: _("Prefer recordings that haven't been played for a long time");
|
||||||
|
suffix: _("%");
|
||||||
|
|
||||||
|
adjustment: Gtk.Adjustment prefer_least_recently_played_adjustment {
|
||||||
|
lower: 0;
|
||||||
|
upper: 100;
|
||||||
|
step-increment: 1;
|
||||||
|
page-increment: 10;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$MusicusSliderRow {
|
||||||
|
title: _("Prefer recordings that were recently added");
|
||||||
|
suffix: _("%");
|
||||||
|
|
||||||
|
adjustment: Gtk.Adjustment prefer_recently_added_adjustment {
|
||||||
|
lower: 0;
|
||||||
|
upper: 100;
|
||||||
|
step-increment: 1;
|
||||||
|
page-increment: 10;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$MusicusSliderRow {
|
||||||
|
title: _("Avoid repeating composers");
|
||||||
|
suffix: _(" min");
|
||||||
|
|
||||||
|
adjustment: Gtk.Adjustment avoid_repeated_composers_adjustment {
|
||||||
|
lower: 0;
|
||||||
|
upper: 120;
|
||||||
|
step-increment: 10;
|
||||||
|
page-increment: 30;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
$MusicusSliderRow {
|
||||||
|
title: _("Avoid repeating instruments");
|
||||||
|
suffix: _(" min");
|
||||||
|
|
||||||
|
adjustment: Gtk.Adjustment avoid_repeated_instruments_adjustment {
|
||||||
|
lower: 0;
|
||||||
|
upper: 120;
|
||||||
|
step-increment: 10;
|
||||||
|
page-increment: 30;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Adw.SwitchRow play_full_recordings_row {
|
||||||
|
title: _("Play full recordings");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Gtk.ListBox {
|
||||||
|
selection-mode: none;
|
||||||
|
margin-top: 24;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"boxed-list",
|
||||||
|
]
|
||||||
|
|
||||||
|
Adw.ButtonRow save_row {
|
||||||
|
title: _("_Save program");
|
||||||
|
use-underline: true;
|
||||||
|
activated => $save() swapped;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,32 +1,46 @@
|
||||||
using Gtk 4.0;
|
using Gtk 4.0;
|
||||||
|
|
||||||
template $MusicusProgramTile : Gtk.FlowBoxChild {
|
template $MusicusProgramTile: Gtk.FlowBoxChild {
|
||||||
styles ["program", "card", "activatable"]
|
styles [
|
||||||
|
"program-tile",
|
||||||
|
"card",
|
||||||
|
"activatable",
|
||||||
|
]
|
||||||
|
|
||||||
Gtk.Box {
|
Gtk.Box {
|
||||||
orientation: vertical;
|
orientation: vertical;
|
||||||
|
|
||||||
Gtk.Button edit_button {
|
Gtk.Button edit_button {
|
||||||
styles ["flat", "circular"]
|
|
||||||
halign: end;
|
halign: end;
|
||||||
icon-name: "document-edit-symbolic";
|
icon-name: "document-edit-symbolic";
|
||||||
|
clicked => $edit_button_clicked() swapped;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"flat",
|
||||||
|
"circular",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Label title_label {
|
Gtk.Label title_label {
|
||||||
styles ["title"]
|
|
||||||
halign: start;
|
halign: start;
|
||||||
margin-top: 24;
|
margin-top: 24;
|
||||||
wrap: true;
|
wrap: true;
|
||||||
max-width-chars: 0;
|
max-width-chars: 0;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"title",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Gtk.Label description_label {
|
Gtk.Label description_label {
|
||||||
styles ["description"]
|
|
||||||
margin-top: 6;
|
margin-top: 6;
|
||||||
halign: start;
|
halign: start;
|
||||||
wrap: true;
|
wrap: true;
|
||||||
max-width-chars: 0;
|
max-width-chars: 0;
|
||||||
|
|
||||||
|
styles [
|
||||||
|
"description",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ pub mod album;
|
||||||
pub mod ensemble;
|
pub mod ensemble;
|
||||||
pub mod instrument;
|
pub mod instrument;
|
||||||
pub mod person;
|
pub mod person;
|
||||||
|
pub mod program;
|
||||||
pub mod recording;
|
pub mod recording;
|
||||||
pub mod role;
|
pub mod role;
|
||||||
pub mod tracks;
|
pub mod tracks;
|
||||||
|
|
|
||||||
181
src/editor/program.rs
Normal file
181
src/editor/program.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
use std::{cell::OnceCell, str::FromStr};
|
||||||
|
|
||||||
|
use adw::{prelude::*, subclass::prelude::*};
|
||||||
|
use gtk::{
|
||||||
|
gio,
|
||||||
|
glib::{self, subclass::Signal},
|
||||||
|
};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
program::{Program, ProgramDesign},
|
||||||
|
slider_row::SliderRow,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod imp {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, gtk::CompositeTemplate)]
|
||||||
|
#[template(file = "data/ui/editor/program.blp")]
|
||||||
|
pub struct ProgramEditor {
|
||||||
|
pub navigation: OnceCell<adw::NavigationView>,
|
||||||
|
pub action_group: OnceCell<gio::SimpleActionGroup>,
|
||||||
|
|
||||||
|
#[template_child]
|
||||||
|
pub title_row: TemplateChild<adw::EntryRow>,
|
||||||
|
#[template_child]
|
||||||
|
pub description_row: TemplateChild<adw::EntryRow>,
|
||||||
|
#[template_child]
|
||||||
|
pub prefer_least_recently_played_adjustment: TemplateChild<gtk::Adjustment>,
|
||||||
|
#[template_child]
|
||||||
|
pub prefer_recently_added_adjustment: TemplateChild<gtk::Adjustment>,
|
||||||
|
#[template_child]
|
||||||
|
pub avoid_repeated_composers_adjustment: TemplateChild<gtk::Adjustment>,
|
||||||
|
#[template_child]
|
||||||
|
pub avoid_repeated_instruments_adjustment: TemplateChild<gtk::Adjustment>,
|
||||||
|
#[template_child]
|
||||||
|
pub play_full_recordings_row: TemplateChild<adw::SwitchRow>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[glib::object_subclass]
|
||||||
|
impl ObjectSubclass for ProgramEditor {
|
||||||
|
const NAME: &'static str = "MusicusProgramEditor";
|
||||||
|
type Type = super::ProgramEditor;
|
||||||
|
type ParentType = adw::NavigationPage;
|
||||||
|
|
||||||
|
fn class_init(klass: &mut Self::Class) {
|
||||||
|
SliderRow::static_type();
|
||||||
|
klass.bind_template();
|
||||||
|
klass.bind_template_instance_callbacks();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
obj.init_template();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ObjectImpl for ProgramEditor {
|
||||||
|
fn signals() -> &'static [Signal] {
|
||||||
|
static SIGNALS: Lazy<Vec<Signal>> = Lazy::new(|| {
|
||||||
|
vec![Signal::builder("save")
|
||||||
|
.param_types([Program::static_type()])
|
||||||
|
.build()]
|
||||||
|
});
|
||||||
|
|
||||||
|
SIGNALS.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
let set_design_action = gio::ActionEntry::builder("set-design")
|
||||||
|
.parameter_type(Some(&glib::VariantTy::STRING))
|
||||||
|
.state(glib::Variant::from("program-1"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let actions = gio::SimpleActionGroup::new();
|
||||||
|
actions.add_action_entries([set_design_action]);
|
||||||
|
self.obj().insert_action_group("program", Some(&actions));
|
||||||
|
self.action_group.set(actions).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WidgetImpl for ProgramEditor {}
|
||||||
|
impl NavigationPageImpl for ProgramEditor {}
|
||||||
|
}
|
||||||
|
|
||||||
|
glib::wrapper! {
|
||||||
|
pub struct ProgramEditor(ObjectSubclass<imp::ProgramEditor>)
|
||||||
|
@extends gtk::Widget, adw::NavigationPage;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
|
impl ProgramEditor {
|
||||||
|
pub fn new(navigation: &adw::NavigationView, program: Option<&Program>) -> Self {
|
||||||
|
let obj: Self = glib::Object::new();
|
||||||
|
|
||||||
|
if let Some(program) = program {
|
||||||
|
if let Some(title) = program.title() {
|
||||||
|
obj.imp().title_row.set_text(&title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(description) = program.description() {
|
||||||
|
obj.imp().description_row.set_text(&description);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Err(err) = obj.activate_action(
|
||||||
|
"program.set-design",
|
||||||
|
Some(&glib::Variant::from(&program.design().to_string())),
|
||||||
|
) {
|
||||||
|
log::warn!("Failed to initialize program design buttons: {err:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.imp()
|
||||||
|
.prefer_least_recently_played_adjustment
|
||||||
|
.set_value(program.prefer_least_recently_played() * 100.0);
|
||||||
|
|
||||||
|
obj.imp()
|
||||||
|
.prefer_recently_added_adjustment
|
||||||
|
.set_value(program.prefer_recently_added() * 100.0);
|
||||||
|
|
||||||
|
obj.imp()
|
||||||
|
.avoid_repeated_composers_adjustment
|
||||||
|
.set_value(program.avoid_repeated_composers() as f64);
|
||||||
|
|
||||||
|
obj.imp()
|
||||||
|
.avoid_repeated_instruments_adjustment
|
||||||
|
.set_value(program.avoid_repeated_instruments() as f64);
|
||||||
|
|
||||||
|
obj.imp()
|
||||||
|
.play_full_recordings_row
|
||||||
|
.set_active(program.play_full_recordings());
|
||||||
|
}
|
||||||
|
|
||||||
|
obj.imp().navigation.set(navigation.to_owned()).unwrap();
|
||||||
|
obj
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn connect_save<F: Fn(&Self, Program) + 'static>(&self, f: F) -> glib::SignalHandlerId {
|
||||||
|
self.connect_local("save", true, move |values| {
|
||||||
|
let obj = values[0].get::<Self>().unwrap();
|
||||||
|
let program = values[1].get::<Program>().unwrap();
|
||||||
|
f(&obj, program);
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn save(&self) {
|
||||||
|
let program = Program::new(
|
||||||
|
&self.imp().title_row.text(),
|
||||||
|
&self.imp().description_row.text(),
|
||||||
|
ProgramDesign::from_str(
|
||||||
|
&self
|
||||||
|
.imp()
|
||||||
|
.action_group
|
||||||
|
.get()
|
||||||
|
.unwrap()
|
||||||
|
.action_state("set-design")
|
||||||
|
.map(|v| v.get::<String>().unwrap_or_default())
|
||||||
|
.unwrap_or_default(),
|
||||||
|
)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
program.set_prefer_least_recently_played(
|
||||||
|
self.imp().prefer_least_recently_played_adjustment.value() / 100.0,
|
||||||
|
);
|
||||||
|
program
|
||||||
|
.set_prefer_recently_added(self.imp().prefer_recently_added_adjustment.value() / 100.0);
|
||||||
|
program.set_avoid_repeated_composers(
|
||||||
|
self.imp().avoid_repeated_composers_adjustment.value() as i32,
|
||||||
|
);
|
||||||
|
program.set_avoid_repeated_instruments(
|
||||||
|
self.imp().avoid_repeated_instruments_adjustment.value() as i32,
|
||||||
|
);
|
||||||
|
program.set_play_full_recordings(self.imp().play_full_recordings_row.is_active());
|
||||||
|
|
||||||
|
self.emit_by_name::<()>("save", &[&program]);
|
||||||
|
self.imp().navigation.get().unwrap().pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
use std::cell::{Cell, RefCell};
|
use std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use gtk::{gio, glib, glib::Properties, prelude::*, subclass::prelude::*};
|
use gtk::{gio, glib, glib::Properties, prelude::*, subclass::prelude::*};
|
||||||
|
|
@ -156,10 +159,15 @@ impl Program {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Program {
|
||||||
|
fn default() -> Self {
|
||||||
|
glib::Object::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(glib::Enum, Serialize, Deserialize, Eq, PartialEq, Clone, Copy, Debug)]
|
#[derive(glib::Enum, Serialize, Deserialize, Eq, PartialEq, Clone, Copy, Debug)]
|
||||||
#[enum_type(name = "MusicusProgramDesign")]
|
#[enum_type(name = "MusicusProgramDesign")]
|
||||||
pub enum ProgramDesign {
|
pub enum ProgramDesign {
|
||||||
Generic,
|
|
||||||
Program1,
|
Program1,
|
||||||
Program2,
|
Program2,
|
||||||
Program3,
|
Program3,
|
||||||
|
|
@ -168,8 +176,43 @@ pub enum ProgramDesign {
|
||||||
Program6,
|
Program6,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ProgramDesign {
|
impl ProgramDesign {
|
||||||
fn default() -> Self {
|
pub fn css_class(&self) -> String {
|
||||||
Self::Generic
|
self.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ProgramDesign {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Program1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToString for ProgramDesign {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
String::from(match self {
|
||||||
|
ProgramDesign::Program1 => "program-1",
|
||||||
|
ProgramDesign::Program2 => "program-2",
|
||||||
|
ProgramDesign::Program3 => "program-3",
|
||||||
|
ProgramDesign::Program4 => "program-4",
|
||||||
|
ProgramDesign::Program5 => "program-5",
|
||||||
|
ProgramDesign::Program6 => "program-6",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ProgramDesign {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, ()> {
|
||||||
|
match s {
|
||||||
|
"program-1" => Ok(ProgramDesign::Program1),
|
||||||
|
"program-2" => Ok(ProgramDesign::Program2),
|
||||||
|
"program-3" => Ok(ProgramDesign::Program3),
|
||||||
|
"program-4" => Ok(ProgramDesign::Program4),
|
||||||
|
"program-5" => Ok(ProgramDesign::Program5),
|
||||||
|
"program-6" => Ok(ProgramDesign::Program6),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
use std::cell::OnceCell;
|
use std::cell::{OnceCell, RefCell};
|
||||||
|
|
||||||
use gtk::{
|
use gtk::{
|
||||||
glib::{self, Properties},
|
gio,
|
||||||
|
glib::{self, clone, Properties},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
subclass::prelude::*,
|
subclass::prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::program::{Program, ProgramDesign};
|
use crate::{config, editor::program::ProgramEditor, program::Program};
|
||||||
|
|
||||||
mod imp {
|
mod imp {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -16,7 +17,11 @@ mod imp {
|
||||||
#[template(file = "data/ui/program_tile.blp")]
|
#[template(file = "data/ui/program_tile.blp")]
|
||||||
pub struct ProgramTile {
|
pub struct ProgramTile {
|
||||||
#[property(get, construct_only)]
|
#[property(get, construct_only)]
|
||||||
pub program: OnceCell<Program>,
|
pub navigation: OnceCell<adw::NavigationView>,
|
||||||
|
#[property(get, construct_only)]
|
||||||
|
pub key: OnceCell<String>,
|
||||||
|
#[property(get, set = Self::set_program)]
|
||||||
|
pub program: RefCell<Program>,
|
||||||
|
|
||||||
#[template_child]
|
#[template_child]
|
||||||
pub edit_button: TemplateChild<gtk::Button>,
|
pub edit_button: TemplateChild<gtk::Button>,
|
||||||
|
|
@ -34,6 +39,7 @@ mod imp {
|
||||||
|
|
||||||
fn class_init(klass: &mut Self::Class) {
|
fn class_init(klass: &mut Self::Class) {
|
||||||
klass.bind_template();
|
klass.bind_template();
|
||||||
|
klass.bind_template_instance_callbacks();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
|
||||||
|
|
@ -42,10 +48,46 @@ mod imp {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[glib::derived_properties]
|
#[glib::derived_properties]
|
||||||
impl ObjectImpl for ProgramTile {}
|
impl ObjectImpl for ProgramTile {
|
||||||
|
fn constructed(&self) {
|
||||||
|
self.parent_constructed();
|
||||||
|
|
||||||
|
let settings = gio::Settings::new(config::APP_ID);
|
||||||
|
self.set_program_from_settings(&settings);
|
||||||
|
|
||||||
|
let obj = self.obj().to_owned();
|
||||||
|
settings.connect_changed(Some(&self.key.get().unwrap()), move |settings, _| {
|
||||||
|
obj.imp().set_program_from_settings(settings);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WidgetImpl for ProgramTile {}
|
impl WidgetImpl for ProgramTile {}
|
||||||
impl FlowBoxChildImpl for ProgramTile {}
|
impl FlowBoxChildImpl for ProgramTile {}
|
||||||
|
|
||||||
|
impl ProgramTile {
|
||||||
|
fn set_program_from_settings(&self, settings: &gio::Settings) {
|
||||||
|
match Program::deserialize(&settings.string(self.key.get().unwrap())) {
|
||||||
|
Ok(program) => self.set_program(&program),
|
||||||
|
Err(err) => log::error!("Failed to deserialize program from settings: {err:?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_program(&self, program: &Program) {
|
||||||
|
self.obj()
|
||||||
|
.set_css_classes(&["program-tile", &program.design().css_class()]);
|
||||||
|
|
||||||
|
if let Some(title) = program.title() {
|
||||||
|
self.title_label.set_label(&title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(description) = program.description() {
|
||||||
|
self.description_label.set_label(&description);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.program.replace(program.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
glib::wrapper! {
|
glib::wrapper! {
|
||||||
|
|
@ -53,35 +95,33 @@ glib::wrapper! {
|
||||||
@extends gtk::Widget, gtk::FlowBoxChild;
|
@extends gtk::Widget, gtk::FlowBoxChild;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[gtk::template_callbacks]
|
||||||
impl ProgramTile {
|
impl ProgramTile {
|
||||||
pub fn new(program: Program) -> Self {
|
pub fn new_for_setting(navigation: &adw::NavigationView, key: &str) -> Self {
|
||||||
let obj: Self = glib::Object::builder()
|
let obj: Self = glib::Object::builder()
|
||||||
.property("program", &program)
|
.property("navigation", navigation)
|
||||||
|
.property("key", key)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let imp = obj.imp();
|
|
||||||
|
|
||||||
if program.design() != ProgramDesign::Generic {
|
|
||||||
obj.add_css_class("highlight");
|
|
||||||
obj.add_css_class(match program.design() {
|
|
||||||
ProgramDesign::Generic => "generic",
|
|
||||||
ProgramDesign::Program1 => "program1",
|
|
||||||
ProgramDesign::Program2 => "program2",
|
|
||||||
ProgramDesign::Program3 => "program3",
|
|
||||||
ProgramDesign::Program4 => "program4",
|
|
||||||
ProgramDesign::Program5 => "program5",
|
|
||||||
ProgramDesign::Program6 => "program6",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(title) = program.title() {
|
|
||||||
imp.title_label.set_label(&title);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(description) = program.description() {
|
|
||||||
imp.description_label.set_label(&description);
|
|
||||||
}
|
|
||||||
|
|
||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[template_callback]
|
||||||
|
fn edit_button_clicked(&self) {
|
||||||
|
let editor = ProgramEditor::new(&self.navigation(), Some(&self.program()));
|
||||||
|
|
||||||
|
editor.connect_save(clone!(
|
||||||
|
#[weak(rename_to = obj)]
|
||||||
|
self,
|
||||||
|
move |_, program| {
|
||||||
|
let settings = gio::Settings::new(config::APP_ID);
|
||||||
|
if let Err(err) = settings.set_string(&obj.key(), &program.serialize()) {
|
||||||
|
log::error!("Failed to save program to settings: {err:?}");
|
||||||
|
};
|
||||||
|
obj.set_program(&program);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
self.navigation().push(&editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ use gtk::{
|
||||||
use crate::{
|
use crate::{
|
||||||
album_page::AlbumPage,
|
album_page::AlbumPage,
|
||||||
album_tile::AlbumTile,
|
album_tile::AlbumTile,
|
||||||
config,
|
|
||||||
db::models::*,
|
db::models::*,
|
||||||
editor::{
|
editor::{
|
||||||
ensemble::EnsembleEditor, instrument::InstrumentEditor, person::PersonEditor,
|
ensemble::EnsembleEditor, instrument::InstrumentEditor, person::PersonEditor,
|
||||||
|
|
@ -50,7 +49,7 @@ mod imp {
|
||||||
pub query: OnceCell<LibraryQuery>,
|
pub query: OnceCell<LibraryQuery>,
|
||||||
pub highlight: RefCell<Option<Tag>>,
|
pub highlight: RefCell<Option<Tag>>,
|
||||||
|
|
||||||
pub programs: RefCell<Vec<Program>>,
|
pub program_tiles: RefCell<Vec<ProgramTile>>,
|
||||||
pub composers: RefCell<Vec<Person>>,
|
pub composers: RefCell<Vec<Person>>,
|
||||||
pub performers: RefCell<Vec<Person>>,
|
pub performers: RefCell<Vec<Person>>,
|
||||||
pub ensembles: RefCell<Vec<Ensemble>>,
|
pub ensembles: RefCell<Vec<Ensemble>>,
|
||||||
|
|
@ -180,21 +179,11 @@ impl SearchPage {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
if query.is_empty() {
|
if query.is_empty() {
|
||||||
let settings = gio::Settings::new(&config::APP_ID);
|
for key in &["program1", "program2", "program3"] {
|
||||||
|
|
||||||
let programs = vec![
|
|
||||||
Program::deserialize(&settings.string("program1")).unwrap(),
|
|
||||||
Program::deserialize(&settings.string("program2")).unwrap(),
|
|
||||||
Program::deserialize(&settings.string("program3")).unwrap(),
|
|
||||||
];
|
|
||||||
|
|
||||||
for program in &programs {
|
|
||||||
obj.imp()
|
obj.imp()
|
||||||
.programs_flow_box
|
.programs_flow_box
|
||||||
.append(&ProgramTile::new(program.to_owned()));
|
.append(&ProgramTile::new_for_setting(navigation, key));
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.imp().programs.replace(programs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.imp().query.set(query).unwrap();
|
obj.imp().query.set(query).unwrap();
|
||||||
|
|
@ -326,9 +315,11 @@ impl SearchPage {
|
||||||
let imp = self.imp();
|
let imp = self.imp();
|
||||||
|
|
||||||
if imp.programs_flow_box.is_visible() {
|
if imp.programs_flow_box.is_visible() {
|
||||||
if let Some(program) = imp.programs.borrow().first().cloned() {
|
if let Some(widget) = imp.programs_flow_box.first_child() {
|
||||||
self.player().set_program(program);
|
if let Ok(program_tile) = widget.downcast::<ProgramTile>() {
|
||||||
self.player().play_from_program();
|
self.player().set_program(program_tile.program());
|
||||||
|
self.player().play_from_program();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut new_query = self.imp().query.get().unwrap().clone();
|
let mut new_query = self.imp().query.get().unwrap().clone();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue