From a6fcbfac713b6e46a34c63a3ded09474f0aff590 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 15 Aug 2023 08:45:08 -0400 Subject: [PATCH 1/5] Make the main app window appear, start working on config --- Cargo.lock | 14 ++ Cargo.toml | 7 + build.sh | 1 + gm-control-panel/Cargo.toml | 19 +++ gm-control-panel/build.rs | 7 + gm-control-panel/resources/gresources.xml | 3 + gm-control-panel/src/app_window.rs | 12 ++ gm-control-panel/src/config.rs | 184 ++++++++++++++++++++++ gm-control-panel/src/main.rs | 48 ++++++ 9 files changed, 295 insertions(+) create mode 100644 gm-control-panel/Cargo.toml create mode 100644 gm-control-panel/build.rs create mode 100644 gm-control-panel/resources/gresources.xml create mode 100644 gm-control-panel/src/app_window.rs create mode 100644 gm-control-panel/src/config.rs create mode 100644 gm-control-panel/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 76e05d6..46f5afb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -900,6 +900,20 @@ dependencies = [ "system-deps", ] +[[package]] +name = "gm-control-panel" +version = "0.1.0" +dependencies = [ + "futures", + "gdk4", + "gio", + "glib", + "glib-build-tools", + "gtk4", + "libadwaita", + "tokio", +] + [[package]] name = "gobject-sys" version = "0.17.10" diff --git a/Cargo.toml b/Cargo.toml index 2d81067..b843e37 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,11 @@ [workspace] members = [ "changeset", +<<<<<<< HEAD "config", "config-derive", +======= +>>>>>>> 36f1bb1 (Make the main app window appear, start working on config) "coordinates", "cyberpunk-splash", "dashboard", @@ -10,6 +13,10 @@ members = [ "flow", "fluent-ergonomics", "geo-types", +<<<<<<< HEAD +======= + "gm-control-panel", +>>>>>>> 36f1bb1 (Make the main app window appear, start working on config) "hex-grid", "ifc", "kifu/core", diff --git a/build.sh b/build.sh index ebfc926..87a75e3 100755 --- a/build.sh +++ b/build.sh @@ -14,6 +14,7 @@ RUST_ALL_TARGETS=( "flow" "fluent-ergonomics" "geo-types" + "gm-control-panel" "hex-grid" "ifc" "kifu-core" diff --git a/gm-control-panel/Cargo.toml b/gm-control-panel/Cargo.toml new file mode 100644 index 0000000..cb8375a --- /dev/null +++ b/gm-control-panel/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "gm-control-panel" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +adw = { version = "0.4", package = "libadwaita", features = [ "v1_2" ] } +futures = { version = "0.3" } +gio = { version = "0.17" } +glib = { version = "0.17" } +gdk = { version = "0.6", package = "gdk4" } +gtk = { version = "0.6", package = "gtk4" } +tokio = { version = "1", features = ["full"] } + +[build-dependencies] +glib-build-tools = "0.16" + diff --git a/gm-control-panel/build.rs b/gm-control-panel/build.rs new file mode 100644 index 0000000..2f8c7ae --- /dev/null +++ b/gm-control-panel/build.rs @@ -0,0 +1,7 @@ +fn main() { + glib_build_tools::compile_resources( + "resources", + "resources/gresources.xml", + "com.luminescent-dreams.dashboard.gresource", + ); +} diff --git a/gm-control-panel/resources/gresources.xml b/gm-control-panel/resources/gresources.xml new file mode 100644 index 0000000..1447b98 --- /dev/null +++ b/gm-control-panel/resources/gresources.xml @@ -0,0 +1,3 @@ + + + diff --git a/gm-control-panel/src/app_window.rs b/gm-control-panel/src/app_window.rs new file mode 100644 index 0000000..fb07457 --- /dev/null +++ b/gm-control-panel/src/app_window.rs @@ -0,0 +1,12 @@ +#[derive(Clone)] +pub struct ApplicationWindow { + pub window: adw::ApplicationWindow, +} + +impl ApplicationWindow { + pub fn new(app: &adw::Application) -> Self { + let window = adw::ApplicationWindow::new(app); + + Self { window } + } +} diff --git a/gm-control-panel/src/config.rs b/gm-control-panel/src/config.rs new file mode 100644 index 0000000..2d932fb --- /dev/null +++ b/gm-control-panel/src/config.rs @@ -0,0 +1,184 @@ +use crate::types::Player; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + fs::File, + io::{ErrorKind, Read}, + path::PathBuf, +}; +use thiserror::Error; + +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +enum OptionNames { + Language, + MusicPath, + PlaylistDatabasePath, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(untagged)] +pub enum ConfigOption { + Language(Language), + MusicPath(MusicPath), + PlaylistDatabasePath(PlaylistDatabasePath), +} + +#[derive(Debug, Error)] +pub enum ConfigReadError { + #[error("Cannot read the configuration file: {0}")] + CannotRead(std::io::Error), + #[error("Cannot open the configuration file for reading: {0}")] + CannotOpen(std::io::Error), + #[error("Invalid json data found in the configurationfile: {0}")] + InvalidJSON(serde_json::Error), +} + +#[derive(Clone, Debug)] +pub struct Config { + config_path: PathBuf, + values: HashMap, +} + +impl Config { + pub fn new(config_path: PathBuf) -> Self { + Self { + config_path, + values: HashMap::new(), + } + } + + pub fn from_path(config_path: PathBuf) -> Result { + let mut settings = config_path.clone(); + settings.push("config"); + + match File::open(settings) { + Ok(mut file) => { + let mut buf = String::new(); + file.read_to_string(&mut buf) + .map_err(|err| ConfigReadError::CannotRead(err))?; + let values = serde_json::from_str(buf.as_ref()) + .map_err(|err| ConfigReadError::InvalidJSON(err))?; + Ok(Self { + config_path, + values, + }) + } + Err(io_err) => { + match io_err.kind() { + ErrorKind::NotFound => { + /* create the path and an empty file */ + Ok(Self { + config_path, + values: HashMap::new(), + }) + } + _ => Err(ConfigReadError::CannotOpen(io_err)), + } + } + } + } + + pub fn set(&mut self, val: ConfigOption) { + let _ = match val { + ConfigOption::Language(_) => self.values.insert(OptionNames::Language, val), + ConfigOption::MusicPath(_) => self.values.insert(OptionNames::MusicPath, val), + ConfigOption::PlaylistDatabasePath(_) => { + self.values.insert(OptionNames::PlaylistDatabasePath, val) + } + }; + } + + pub fn get<'a, T>(&'a self) -> T + where + T: From<&'a Self>, + { + self.into() + } +} + +/* +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct DatabasePath(PathBuf); + +impl std::ops::Deref for DatabasePath { + type Target = PathBuf; + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From<&Config> for DatabasePath { + fn from(config: &Config) -> Self { + match config.values.get(&OptionNames::DatabasePath) { + Some(ConfigOption::DatabasePath(path)) => path.clone(), + _ => DatabasePath(config.config_path.clone()), + } + } +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Me(Player); + +impl From<&Config> for Option { + fn from(config: &Config) -> Self { + config + .values + .get(&OptionNames::Me) + .and_then(|val| match val { + ConfigOption::Me(me) => Some(me.clone()), + _ => None, + }) + } +} + +impl std::ops::Deref for Me { + type Target = Player; + fn deref(&self) -> &Self::Target { + &self.0 + } +} +*/ + +#[cfg(test)] +mod test { + use super::*; + use crate::types::Rank; + use cool_asserts::assert_matches; + + #[test] + fn it_can_set_and_get_options() { + let mut config = Config::new(PathBuf::from(".")); + config.set(ConfigOption::DatabasePath(DatabasePath(PathBuf::from( + "fixtures/five_games", + )))); + config.set(ConfigOption::Me(Me(Player { + name: "Savanni".to_owned(), + rank: Some(Rank::Kyu(10)), + }))); + } + + #[test] + fn it_can_serialize_and_deserialize() { + let mut config = Config::new(PathBuf::from(".")); + config.set(ConfigOption::DatabasePath(DatabasePath(PathBuf::from( + "fixtures/five_games", + )))); + config.set(ConfigOption::Me(Me(Player { + name: "Savanni".to_owned(), + rank: Some(Rank::Kyu(10)), + }))); + let s = serde_json::to_string(&config.values).unwrap(); + println!("{}", s); + let values: HashMap = serde_json::from_str(s.as_ref()).unwrap(); + println!("options: {:?}", values); + + assert_matches!(values.get(&OptionNames::DatabasePath), + Some(ConfigOption::DatabasePath(db_path)) => + assert_eq!(*db_path, config.get()) + ); + + assert_matches!(values.get(&OptionNames::Me), Some(ConfigOption::Me(val)) => + assert_eq!(Some(val.clone()), config.get()) + ); + } +} diff --git a/gm-control-panel/src/main.rs b/gm-control-panel/src/main.rs new file mode 100644 index 0000000..cb63dc7 --- /dev/null +++ b/gm-control-panel/src/main.rs @@ -0,0 +1,48 @@ +use glib::{Continue, Sender}; +use gtk::prelude::*; +use std::{ + env, + sync::{Arc, RwLock}, +}; + +mod app_window; +use app_window::ApplicationWindow; + +#[derive(Clone, Debug)] +pub enum Message {} + +#[derive(Clone)] +pub struct Core { + tx: Arc>>>, +} + +pub fn main() { + let app = adw::Application::builder() + .application_id("com.luminescent-dreams.gm-control-panel") + .build(); + + let runtime = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + + let core = Core { + tx: Arc::new(RwLock::new(None)), + }; + + app.connect_activate(move |app| { + let (gtk_tx, gtk_rx) = + gtk::glib::MainContext::channel::(gtk::glib::PRIORITY_DEFAULT); + + *core.tx.write().unwrap() = Some(gtk_tx); + + let window = ApplicationWindow::new(app); + window.window.present(); + + gtk_rx.attach(None, move |_msg| Continue(true)); + }); + + let args: Vec = env::args().collect(); + ApplicationExtManual::run_with_args(&app, &args); + runtime.shutdown_background(); +} -- 2.44.1 From d59c2585db1115fcae7ac5dca3882c73e974d003 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Thu, 17 Aug 2023 23:48:34 -0400 Subject: [PATCH 2/5] Set up configuration --- Cargo.lock | 4 + Cargo.toml | 6 -- build.sh | 1 - gm-control-panel/Cargo.toml | 4 + gm-control-panel/src/config.rs | 178 ++++----------------------------- gm-control-panel/src/main.rs | 2 + 6 files changed, 27 insertions(+), 168 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 46f5afb..abd507e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -904,6 +904,8 @@ dependencies = [ name = "gm-control-panel" version = "0.1.0" dependencies = [ + "config", + "config-derive", "futures", "gdk4", "gio", @@ -911,6 +913,8 @@ dependencies = [ "glib-build-tools", "gtk4", "libadwaita", + "serde", + "serde_json", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index b843e37..fc88da2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,8 @@ [workspace] members = [ "changeset", -<<<<<<< HEAD "config", "config-derive", -======= ->>>>>>> 36f1bb1 (Make the main app window appear, start working on config) "coordinates", "cyberpunk-splash", "dashboard", @@ -13,10 +10,7 @@ members = [ "flow", "fluent-ergonomics", "geo-types", -<<<<<<< HEAD -======= "gm-control-panel", ->>>>>>> 36f1bb1 (Make the main app window appear, start working on config) "hex-grid", "ifc", "kifu/core", diff --git a/build.sh b/build.sh index 87a75e3..1d443b2 100755 --- a/build.sh +++ b/build.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -set -x RUST_ALL_TARGETS=( "changeset" diff --git a/gm-control-panel/Cargo.toml b/gm-control-panel/Cargo.toml index cb8375a..bf29138 100644 --- a/gm-control-panel/Cargo.toml +++ b/gm-control-panel/Cargo.toml @@ -7,11 +7,15 @@ edition = "2021" [dependencies] adw = { version = "0.4", package = "libadwaita", features = [ "v1_2" ] } +config = { path = "../config" } +config-derive = { path = "../config-derive" } futures = { version = "0.3" } gio = { version = "0.17" } glib = { version = "0.17" } gdk = { version = "0.6", package = "gdk4" } gtk = { version = "0.6", package = "gtk4" } +serde = { version = "1" } +serde_json = { version = "*" } tokio = { version = "1", features = ["full"] } [build-dependencies] diff --git a/gm-control-panel/src/config.rs b/gm-control-panel/src/config.rs index 2d932fb..e8ea768 100644 --- a/gm-control-panel/src/config.rs +++ b/gm-control-panel/src/config.rs @@ -1,184 +1,40 @@ -use crate::types::Player; +use config::define_config; +use config_derive::ConfigOption; use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - fs::File, - io::{ErrorKind, Read}, - path::PathBuf, -}; -use thiserror::Error; +use std::path::PathBuf; -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -enum OptionNames { - Language, - MusicPath, - PlaylistDatabasePath, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -#[serde(untagged)] -pub enum ConfigOption { +define_config! { Language(Language), MusicPath(MusicPath), PlaylistDatabasePath(PlaylistDatabasePath), } -#[derive(Debug, Error)] -pub enum ConfigReadError { - #[error("Cannot read the configuration file: {0}")] - CannotRead(std::io::Error), - #[error("Cannot open the configuration file for reading: {0}")] - CannotOpen(std::io::Error), - #[error("Invalid json data found in the configurationfile: {0}")] - InvalidJSON(serde_json::Error), -} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ConfigOption)] +pub struct Language(String); -#[derive(Clone, Debug)] -pub struct Config { - config_path: PathBuf, - values: HashMap, -} - -impl Config { - pub fn new(config_path: PathBuf) -> Self { - Self { - config_path, - values: HashMap::new(), - } - } - - pub fn from_path(config_path: PathBuf) -> Result { - let mut settings = config_path.clone(); - settings.push("config"); - - match File::open(settings) { - Ok(mut file) => { - let mut buf = String::new(); - file.read_to_string(&mut buf) - .map_err(|err| ConfigReadError::CannotRead(err))?; - let values = serde_json::from_str(buf.as_ref()) - .map_err(|err| ConfigReadError::InvalidJSON(err))?; - Ok(Self { - config_path, - values, - }) - } - Err(io_err) => { - match io_err.kind() { - ErrorKind::NotFound => { - /* create the path and an empty file */ - Ok(Self { - config_path, - values: HashMap::new(), - }) - } - _ => Err(ConfigReadError::CannotOpen(io_err)), - } - } - } - } - - pub fn set(&mut self, val: ConfigOption) { - let _ = match val { - ConfigOption::Language(_) => self.values.insert(OptionNames::Language, val), - ConfigOption::MusicPath(_) => self.values.insert(OptionNames::MusicPath, val), - ConfigOption::PlaylistDatabasePath(_) => { - self.values.insert(OptionNames::PlaylistDatabasePath, val) - } - }; - } - - pub fn get<'a, T>(&'a self) -> T - where - T: From<&'a Self>, - { - self.into() +impl std::ops::Deref for Language { + type Target = String; + fn deref(&self) -> &Self::Target { + &self.0 } } -/* -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct DatabasePath(PathBuf); +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ConfigOption)] +pub struct MusicPath(PathBuf); -impl std::ops::Deref for DatabasePath { +impl std::ops::Deref for MusicPath { type Target = PathBuf; fn deref(&self) -> &Self::Target { &self.0 } } -impl From<&Config> for DatabasePath { - fn from(config: &Config) -> Self { - match config.values.get(&OptionNames::DatabasePath) { - Some(ConfigOption::DatabasePath(path)) => path.clone(), - _ => DatabasePath(config.config_path.clone()), - } - } -} +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ConfigOption)] +pub struct PlaylistDatabasePath(PathBuf); -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Me(Player); - -impl From<&Config> for Option { - fn from(config: &Config) -> Self { - config - .values - .get(&OptionNames::Me) - .and_then(|val| match val { - ConfigOption::Me(me) => Some(me.clone()), - _ => None, - }) - } -} - -impl std::ops::Deref for Me { - type Target = Player; +impl std::ops::Deref for PlaylistDatabasePath { + type Target = PathBuf; fn deref(&self) -> &Self::Target { &self.0 } } -*/ - -#[cfg(test)] -mod test { - use super::*; - use crate::types::Rank; - use cool_asserts::assert_matches; - - #[test] - fn it_can_set_and_get_options() { - let mut config = Config::new(PathBuf::from(".")); - config.set(ConfigOption::DatabasePath(DatabasePath(PathBuf::from( - "fixtures/five_games", - )))); - config.set(ConfigOption::Me(Me(Player { - name: "Savanni".to_owned(), - rank: Some(Rank::Kyu(10)), - }))); - } - - #[test] - fn it_can_serialize_and_deserialize() { - let mut config = Config::new(PathBuf::from(".")); - config.set(ConfigOption::DatabasePath(DatabasePath(PathBuf::from( - "fixtures/five_games", - )))); - config.set(ConfigOption::Me(Me(Player { - name: "Savanni".to_owned(), - rank: Some(Rank::Kyu(10)), - }))); - let s = serde_json::to_string(&config.values).unwrap(); - println!("{}", s); - let values: HashMap = serde_json::from_str(s.as_ref()).unwrap(); - println!("options: {:?}", values); - - assert_matches!(values.get(&OptionNames::DatabasePath), - Some(ConfigOption::DatabasePath(db_path)) => - assert_eq!(*db_path, config.get()) - ); - - assert_matches!(values.get(&OptionNames::Me), Some(ConfigOption::Me(val)) => - assert_eq!(Some(val.clone()), config.get()) - ); - } -} diff --git a/gm-control-panel/src/main.rs b/gm-control-panel/src/main.rs index cb63dc7..60900e5 100644 --- a/gm-control-panel/src/main.rs +++ b/gm-control-panel/src/main.rs @@ -8,6 +8,8 @@ use std::{ mod app_window; use app_window::ApplicationWindow; +mod config; + #[derive(Clone, Debug)] pub enum Message {} -- 2.44.1 From 69583dfd64abe9aa05d74702f21a277ebaa230bc Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 18 Aug 2023 23:07:57 -0400 Subject: [PATCH 3/5] Create placeholder elements for each playlist card --- gm-control-panel/build.rs | 2 +- gm-control-panel/resources/gresources.xml | 3 ++ gm-control-panel/resources/style.css | 4 ++ gm-control-panel/src/app_window.rs | 52 +++++++++++++++++++- gm-control-panel/src/main.rs | 9 ++++ gm-control-panel/src/playlist_card.rs | 60 +++++++++++++++++++++++ gm-control-panel/src/types.rs | 16 ++++++ 7 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 gm-control-panel/resources/style.css create mode 100644 gm-control-panel/src/playlist_card.rs create mode 100644 gm-control-panel/src/types.rs diff --git a/gm-control-panel/build.rs b/gm-control-panel/build.rs index 2f8c7ae..c83cc1e 100644 --- a/gm-control-panel/build.rs +++ b/gm-control-panel/build.rs @@ -2,6 +2,6 @@ fn main() { glib_build_tools::compile_resources( "resources", "resources/gresources.xml", - "com.luminescent-dreams.dashboard.gresource", + "com.luminescent-dreams.gm-control-panel.gresource", ); } diff --git a/gm-control-panel/resources/gresources.xml b/gm-control-panel/resources/gresources.xml index 1447b98..1caf7fc 100644 --- a/gm-control-panel/resources/gresources.xml +++ b/gm-control-panel/resources/gresources.xml @@ -1,3 +1,6 @@ + + style.css + diff --git a/gm-control-panel/resources/style.css b/gm-control-panel/resources/style.css new file mode 100644 index 0000000..02761c3 --- /dev/null +++ b/gm-control-panel/resources/style.css @@ -0,0 +1,4 @@ +.playlist-card { + margin: 8px; + padding: 8px; +} diff --git a/gm-control-panel/src/app_window.rs b/gm-control-panel/src/app_window.rs index fb07457..98c69f1 100644 --- a/gm-control-panel/src/app_window.rs +++ b/gm-control-panel/src/app_window.rs @@ -1,12 +1,62 @@ +use crate::PlaylistCard; +use adw::prelude::AdwApplicationWindowExt; +use gio::resources_lookup_data; +use gtk::{prelude::*, STYLE_PROVIDER_PRIORITY_USER}; +use std::iter::Iterator; + #[derive(Clone)] pub struct ApplicationWindow { pub window: adw::ApplicationWindow, + pub grid: gtk::Grid, + pub playlists: Vec, } impl ApplicationWindow { pub fn new(app: &adw::Application) -> Self { let window = adw::ApplicationWindow::new(app); - Self { window } + let stylesheet = String::from_utf8( + resources_lookup_data( + "/com/luminescent-dreams/gm-control-panel/style.css", + gio::ResourceLookupFlags::NONE, + ) + .expect("stylesheet should just be available") + .to_vec(), + ) + .expect("to parse stylesheet"); + + let provider = gtk::CssProvider::new(); + provider.load_from_data(&stylesheet); + let context = window.style_context(); + context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER); + + let grid = gtk::Grid::new(); + + let playlists: Vec = vec![ + "Creepy Cathedral", + "Joyful Tavern", + "Exploring", + "Out on the streets", + "The North Abbey", + ] + .into_iter() + .map(|name| { + let playlist = PlaylistCard::new(); + playlist.set_name(name); + playlist + }) + .collect(); + + playlists.iter().enumerate().for_each(|(i, card)| { + grid.attach(card, 0, i as i32, 1, 1); + }); + + window.set_content(Some(&grid)); + + Self { + window, + grid, + playlists, + } } } diff --git a/gm-control-panel/src/main.rs b/gm-control-panel/src/main.rs index 60900e5..da86db8 100644 --- a/gm-control-panel/src/main.rs +++ b/gm-control-panel/src/main.rs @@ -10,6 +10,12 @@ use app_window::ApplicationWindow; mod config; +mod playlist_card; +use playlist_card::PlaylistCard; + +mod types; +use types::PlaybackState; + #[derive(Clone, Debug)] pub enum Message {} @@ -19,6 +25,9 @@ pub struct Core { } pub fn main() { + gio::resources_register_include!("com.luminescent-dreams.gm-control-panel.gresource") + .expect("Failed to register resource"); + let app = adw::Application::builder() .application_id("com.luminescent-dreams.gm-control-panel") .build(); diff --git a/gm-control-panel/src/playlist_card.rs b/gm-control-panel/src/playlist_card.rs new file mode 100644 index 0000000..4b1939b --- /dev/null +++ b/gm-control-panel/src/playlist_card.rs @@ -0,0 +1,60 @@ +use crate::PlaybackState; +use glib::Object; +use gtk::{prelude::*, subclass::prelude::*}; + +pub struct PlaylistCardPrivate { + name: gtk::Label, + playing: gtk::Label, +} + +impl Default for PlaylistCardPrivate { + fn default() -> Self { + Self { + name: gtk::Label::new(None), + playing: gtk::Label::new(Some("Stopped")), + } + } +} + +#[glib::object_subclass] +impl ObjectSubclass for PlaylistCardPrivate { + const NAME: &'static str = "PlaylistCard"; + type Type = PlaylistCard; + type ParentType = gtk::Box; +} + +impl ObjectImpl for PlaylistCardPrivate {} +impl WidgetImpl for PlaylistCardPrivate {} +impl BoxImpl for PlaylistCardPrivate {} + +glib::wrapper! { + pub struct PlaylistCard(ObjectSubclass) @extends gtk::Box, gtk::Widget, @implements gtk::Orientable; +} + +impl PlaylistCard { + pub fn new() -> Self { + let s: Self = Object::builder().build(); + s.set_orientation(gtk::Orientation::Vertical); + s.add_css_class("playlist-card"); + s.add_css_class("card"); + /* + s.set_margin_start(8); + s.set_margin_end(8); + s.set_margin_top(8); + s.set_margin_bottom(8); + */ + + s.append(&s.imp().name); + s.append(&s.imp().playing); + + s + } + + pub fn set_name(&self, s: &str) { + self.imp().name.set_text(s); + } + + pub fn set_playback(&self, s: PlaybackState) { + self.imp().playing.set_text(&format!("{}", s)) + } +} diff --git a/gm-control-panel/src/types.rs b/gm-control-panel/src/types.rs new file mode 100644 index 0000000..f0a6349 --- /dev/null +++ b/gm-control-panel/src/types.rs @@ -0,0 +1,16 @@ +use std::fmt; + +#[derive(Clone, Debug)] +pub enum PlaybackState { + Stopped, + Playing, +} + +impl fmt::Display for PlaybackState { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Stopped => write!(f, "Stopped"), + Self::Playing => write!(f, "Playing"), + } + } +} -- 2.44.1 From e203b17c8bf5dc2a01c4654f08d7e62bfa42790b Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Fri, 18 Aug 2023 23:21:46 -0400 Subject: [PATCH 4/5] Try to set up a title bar --- gm-control-panel/Cargo.toml | 2 +- gm-control-panel/src/app_window.rs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/gm-control-panel/Cargo.toml b/gm-control-panel/Cargo.toml index bf29138..78e355a 100644 --- a/gm-control-panel/Cargo.toml +++ b/gm-control-panel/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -adw = { version = "0.4", package = "libadwaita", features = [ "v1_2" ] } +adw = { version = "0.4", package = "libadwaita", features = [ "v1_2", "gtk_v4_6" ] } config = { path = "../config" } config-derive = { path = "../config-derive" } futures = { version = "0.3" } diff --git a/gm-control-panel/src/app_window.rs b/gm-control-panel/src/app_window.rs index 98c69f1..5bee12b 100644 --- a/gm-control-panel/src/app_window.rs +++ b/gm-control-panel/src/app_window.rs @@ -13,7 +13,10 @@ pub struct ApplicationWindow { impl ApplicationWindow { pub fn new(app: &adw::Application) -> Self { - let window = adw::ApplicationWindow::new(app); + let window = adw::ApplicationWindow::builder() + .application(app) + .title("GM-control-panel") + .build(); let stylesheet = String::from_utf8( resources_lookup_data( -- 2.44.1 From 5478d388cb99b1bb696a957f6dc87cffaf88df25 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Sat, 19 Aug 2023 19:41:29 -0400 Subject: [PATCH 5/5] Set up a reflowing layout for the cards --- gm-control-panel/Cargo.toml | 2 +- gm-control-panel/resources/style.css | 2 ++ gm-control-panel/src/app_window.rs | 13 ++++++------- gm-control-panel/src/playlist_card.rs | 6 ------ 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/gm-control-panel/Cargo.toml b/gm-control-panel/Cargo.toml index 78e355a..81029d6 100644 --- a/gm-control-panel/Cargo.toml +++ b/gm-control-panel/Cargo.toml @@ -13,7 +13,7 @@ futures = { version = "0.3" } gio = { version = "0.17" } glib = { version = "0.17" } gdk = { version = "0.6", package = "gdk4" } -gtk = { version = "0.6", package = "gtk4" } +gtk = { version = "0.6", package = "gtk4", features = [ "v4_6" ] } serde = { version = "1" } serde_json = { version = "*" } tokio = { version = "1", features = ["full"] } diff --git a/gm-control-panel/resources/style.css b/gm-control-panel/resources/style.css index 02761c3..32b6d6a 100644 --- a/gm-control-panel/resources/style.css +++ b/gm-control-panel/resources/style.css @@ -1,4 +1,6 @@ .playlist-card { margin: 8px; padding: 8px; + min-width: 100px; + min-height: 100px; } diff --git a/gm-control-panel/src/app_window.rs b/gm-control-panel/src/app_window.rs index 5bee12b..26574f2 100644 --- a/gm-control-panel/src/app_window.rs +++ b/gm-control-panel/src/app_window.rs @@ -7,7 +7,7 @@ use std::iter::Iterator; #[derive(Clone)] pub struct ApplicationWindow { pub window: adw::ApplicationWindow, - pub grid: gtk::Grid, + pub layout: gtk::FlowBox, pub playlists: Vec, } @@ -16,6 +16,7 @@ impl ApplicationWindow { let window = adw::ApplicationWindow::builder() .application(app) .title("GM-control-panel") + .width_request(500) .build(); let stylesheet = String::from_utf8( @@ -33,7 +34,7 @@ impl ApplicationWindow { let context = window.style_context(); context.add_provider(&provider, STYLE_PROVIDER_PRIORITY_USER); - let grid = gtk::Grid::new(); + let layout = gtk::FlowBox::new(); let playlists: Vec = vec![ "Creepy Cathedral", @@ -50,15 +51,13 @@ impl ApplicationWindow { }) .collect(); - playlists.iter().enumerate().for_each(|(i, card)| { - grid.attach(card, 0, i as i32, 1, 1); - }); + playlists.iter().for_each(|card| layout.append(card)); - window.set_content(Some(&grid)); + window.set_content(Some(&layout)); Self { window, - grid, + layout, playlists, } } diff --git a/gm-control-panel/src/playlist_card.rs b/gm-control-panel/src/playlist_card.rs index 4b1939b..d1d0007 100644 --- a/gm-control-panel/src/playlist_card.rs +++ b/gm-control-panel/src/playlist_card.rs @@ -37,12 +37,6 @@ impl PlaylistCard { s.set_orientation(gtk::Orientation::Vertical); s.add_css_class("playlist-card"); s.add_css_class("card"); - /* - s.set_margin_start(8); - s.set_margin_end(8); - s.set_margin_top(8); - s.set_margin_bottom(8); - */ s.append(&s.imp().name); s.append(&s.imp().playing); -- 2.44.1