From a6fcbfac713b6e46a34c63a3ded09474f0aff590 Mon Sep 17 00:00:00 2001 From: Savanni D'Gerinel Date: Tue, 15 Aug 2023 08:45:08 -0400 Subject: [PATCH] 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(); +}