Create the basic user interface mockup for a GM control panel #63
|
@ -904,6 +904,8 @@ dependencies = [
|
||||||
name = "gm-control-panel"
|
name = "gm-control-panel"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"config",
|
||||||
|
"config-derive",
|
||||||
"futures",
|
"futures",
|
||||||
"gdk4",
|
"gdk4",
|
||||||
"gio",
|
"gio",
|
||||||
|
@ -911,6 +913,8 @@ dependencies = [
|
||||||
"glib-build-tools",
|
"glib-build-tools",
|
||||||
"gtk4",
|
"gtk4",
|
||||||
"libadwaita",
|
"libadwaita",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,8 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"changeset",
|
"changeset",
|
||||||
<<<<<<< HEAD
|
|
||||||
"config",
|
"config",
|
||||||
"config-derive",
|
"config-derive",
|
||||||
=======
|
|
||||||
>>>>>>> 36f1bb1 (Make the main app window appear, start working on config)
|
|
||||||
"coordinates",
|
"coordinates",
|
||||||
"cyberpunk-splash",
|
"cyberpunk-splash",
|
||||||
"dashboard",
|
"dashboard",
|
||||||
|
@ -13,10 +10,7 @@ members = [
|
||||||
"flow",
|
"flow",
|
||||||
"fluent-ergonomics",
|
"fluent-ergonomics",
|
||||||
"geo-types",
|
"geo-types",
|
||||||
<<<<<<< HEAD
|
|
||||||
=======
|
|
||||||
"gm-control-panel",
|
"gm-control-panel",
|
||||||
>>>>>>> 36f1bb1 (Make the main app window appear, start working on config)
|
|
||||||
"hex-grid",
|
"hex-grid",
|
||||||
"ifc",
|
"ifc",
|
||||||
"kifu/core",
|
"kifu/core",
|
||||||
|
|
1
build.sh
1
build.sh
|
@ -1,7 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
set -x
|
|
||||||
|
|
||||||
RUST_ALL_TARGETS=(
|
RUST_ALL_TARGETS=(
|
||||||
"changeset"
|
"changeset"
|
||||||
|
|
|
@ -7,11 +7,15 @@ edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
adw = { version = "0.4", package = "libadwaita", features = [ "v1_2" ] }
|
adw = { version = "0.4", package = "libadwaita", features = [ "v1_2" ] }
|
||||||
|
config = { path = "../config" }
|
||||||
|
config-derive = { path = "../config-derive" }
|
||||||
futures = { version = "0.3" }
|
futures = { version = "0.3" }
|
||||||
gio = { version = "0.17" }
|
gio = { version = "0.17" }
|
||||||
glib = { version = "0.17" }
|
glib = { version = "0.17" }
|
||||||
gdk = { version = "0.6", package = "gdk4" }
|
gdk = { version = "0.6", package = "gdk4" }
|
||||||
gtk = { version = "0.6", package = "gtk4" }
|
gtk = { version = "0.6", package = "gtk4" }
|
||||||
|
serde = { version = "1" }
|
||||||
|
serde_json = { version = "*" }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -1,184 +1,40 @@
|
||||||
use crate::types::Player;
|
use config::define_config;
|
||||||
|
use config_derive::ConfigOption;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::{
|
use std::path::PathBuf;
|
||||||
collections::HashMap,
|
|
||||||
fs::File,
|
|
||||||
io::{ErrorKind, Read},
|
|
||||||
path::PathBuf,
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
define_config! {
|
||||||
enum OptionNames {
|
|
||||||
Language,
|
|
||||||
MusicPath,
|
|
||||||
PlaylistDatabasePath,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum ConfigOption {
|
|
||||||
Language(Language),
|
Language(Language),
|
||||||
MusicPath(MusicPath),
|
MusicPath(MusicPath),
|
||||||
PlaylistDatabasePath(PlaylistDatabasePath),
|
PlaylistDatabasePath(PlaylistDatabasePath),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ConfigOption)]
|
||||||
pub enum ConfigReadError {
|
pub struct Language(String);
|
||||||
#[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)]
|
impl std::ops::Deref for Language {
|
||||||
pub struct Config {
|
type Target = String;
|
||||||
config_path: PathBuf,
|
fn deref(&self) -> &Self::Target {
|
||||||
values: HashMap<OptionNames, ConfigOption>,
|
&self.0
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
pub fn new(config_path: PathBuf) -> Self {
|
|
||||||
Self {
|
|
||||||
config_path,
|
|
||||||
values: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_path(config_path: PathBuf) -> Result<Self, ConfigReadError> {
|
|
||||||
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, ConfigOption)]
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
pub struct MusicPath(PathBuf);
|
||||||
pub struct DatabasePath(PathBuf);
|
|
||||||
|
|
||||||
impl std::ops::Deref for DatabasePath {
|
impl std::ops::Deref for MusicPath {
|
||||||
type Target = PathBuf;
|
type Target = PathBuf;
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&Config> for DatabasePath {
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, ConfigOption)]
|
||||||
fn from(config: &Config) -> Self {
|
pub struct PlaylistDatabasePath(PathBuf);
|
||||||
match config.values.get(&OptionNames::DatabasePath) {
|
|
||||||
Some(ConfigOption::DatabasePath(path)) => path.clone(),
|
|
||||||
_ => DatabasePath(config.config_path.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
impl std::ops::Deref for PlaylistDatabasePath {
|
||||||
pub struct Me(Player);
|
type Target = PathBuf;
|
||||||
|
|
||||||
impl From<&Config> for Option<Me> {
|
|
||||||
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 {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&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<OptionNames, ConfigOption> = 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())
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -8,6 +8,8 @@ use std::{
|
||||||
mod app_window;
|
mod app_window;
|
||||||
use app_window::ApplicationWindow;
|
use app_window::ApplicationWindow;
|
||||||
|
|
||||||
|
mod config;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum Message {}
|
pub enum Message {}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue