diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6ad8d0e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'eww'", + "cargo": { + "args": [ + "build", + "--bin=eww", + "--package=eww" + ], + "filter": { + "name": "eww", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'eww'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=eww", + "--package=eww" + ], + "filter": { + "name": "eww", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9739073..3c19d84 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -24,6 +24,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.32" @@ -120,6 +129,22 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "ctor" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39858aa5bac06462d4dd4b9164848eb81ffc4aa5c479746393598fd193afa227" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "difference" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" + [[package]] name = "either" version = "1.6.0" @@ -200,6 +225,7 @@ dependencies = [ "glib", "gtk", "hocon", + "pretty_assertions", "regex", "try_match", ] @@ -630,6 +656,15 @@ version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" +[[package]] +name = "output_vt100" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" +dependencies = [ + "winapi", +] + [[package]] name = "pango" version = "0.9.1" @@ -695,6 +730,18 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" +[[package]] +name = "pretty_assertions" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" +dependencies = [ + "ansi_term", + "ctor", + "difference", + "output_vt100", +] + [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -996,3 +1043,25 @@ name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml index 17f666b..d48e4a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,3 +23,4 @@ anyhow = "1.0" #thiserror = "1.0" +pretty_assertions = "0.6.1" diff --git a/src/config/mod.rs b/src/config/mod.rs index 5c88b62..abf8108 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,8 +1,8 @@ use anyhow::*; -use glib::{types, value}; use hocon::*; use hocon_ext::HoconExt; use std::collections::HashMap; +use std::convert::TryFrom; use try_match::try_match; pub mod hocon_ext; @@ -11,6 +11,7 @@ pub mod hocon_ext; pub struct EwwConfig { widgets: HashMap, windows: HashMap, + default_vars: HashMap, } impl EwwConfig { @@ -36,15 +37,26 @@ impl EwwConfig { .iter() .map(|(name, def)| Ok((name.clone(), EwwWindowDefinition::from_hocon(def)?))) .collect::>>()?, + default_vars: data + .get("default_vars") + .unwrap_or(&Hocon::Hash(HashMap::new())) + .as_hash() + .context("default_vars needs to be a map")? + .iter() + .map(|(name, def)| Ok((name.clone(), AttrValue::try_from(def)?))) + .collect::>>()?, }) } - pub fn widgets(&self) -> &HashMap { + pub fn get_widgets(&self) -> &HashMap { &self.widgets } - pub fn windows(&self) -> &HashMap { + pub fn get_windows(&self) -> &HashMap { &self.windows } + pub fn get_default_vars(&self) -> &HashMap { + &self.default_vars + } } #[derive(Debug, Clone, PartialEq)] @@ -106,6 +118,22 @@ impl AttrValue { } } +impl std::convert::TryFrom<&Hocon> for AttrValue { + type Error = anyhow::Error; + fn try_from(value: &Hocon) -> Result { + Ok(match value { + Hocon::String(s) if s.starts_with("$$") => { + AttrValue::VarRef(s.trim_start_matches("$$").to_string()) + } + Hocon::String(s) => AttrValue::String(s.to_string()), + Hocon::Integer(n) => AttrValue::Number(*n as f64), + Hocon::Real(n) => AttrValue::Number(*n as f64), + Hocon::Boolean(b) => AttrValue::Boolean(*b), + _ => return Err(anyhow!("cannot convert {} to config::AttrValue")), + }) + } +} + #[derive(Debug, Clone, PartialEq)] pub struct WidgetDefinition { pub name: String, @@ -193,19 +221,7 @@ pub fn parse_widget_use(data: HashMap) -> Result { let attrs: HashMap = widget_config .into_iter() - .filter_map(|(key, value)| { - Some(( - key.to_lowercase(), - match value { - Hocon::String(s) if s.starts_with("$$") => AttrValue::String(s.to_string()), - Hocon::String(s) => AttrValue::String(s.to_string()), - Hocon::Integer(n) => AttrValue::Number(*n as f64), - Hocon::Real(n) => AttrValue::Number(*n as f64), - Hocon::Boolean(b) => AttrValue::Boolean(*b), - _ => return None, - }, - )) - }) + .filter_map(|(key, value)| Some((key.to_lowercase(), AttrValue::try_from(value).ok()?))) .collect(); Ok(WidgetUse { diff --git a/src/main.rs b/src/main.rs index f0aef4e..c253858 100644 --- a/src/main.rs +++ b/src/main.rs @@ -23,13 +23,17 @@ const EXAMPLE_CONFIG: &str = r#"{ children: [ "hi", { button: { children: "click me you" } } - { slider: { value: 12, min: 0, max: 50, onchange: "notify-send 'changed' {}" } } + { slider: { value: "$$some_value", min: 0, max: 100, onchange: "notify-send 'changed' {}" } } + { slider: { value: "$$some_value", min: 0, max: 100, onchange: "notify-send 'changed' {}" } } "hu" ] } } } }, + default_vars: { + ree: 12 + } windows: { main_window: { pos.x: 200 @@ -37,20 +41,35 @@ const EXAMPLE_CONFIG: &str = r#"{ size.x: 500 size.y: 50 widget: { - some_widget: {} + some_widget: { + some_value: "$$ree" + } } } }, - }"#; +macro_rules! build { + ($var_name:ident = $value:expr ; $code:block) => {{ + let mut $var_name = $value; + $code; + $var_name + }}; +} + +#[derive(Debug)] +enum MuhhMsg { + UpdateValue(String, config::AttrValue), +} + fn main() -> Result<()> { let eww_config = config::EwwConfig::from_hocon(&config::parse_hocon(EXAMPLE_CONFIG)?)?; + dbg!(&eww_config); let application = Application::new(Some("de.elkowar.eww"), gio::ApplicationFlags::FLAGS_NONE) - .expect("failed to initialize GTK application"); + .expect("failed to initialize GTK application "); - let window_def = eww_config.windows()["main_window"].clone(); + let window_def = eww_config.get_windows()["main_window"].clone(); application.connect_activate(move |app| { let app_window = ApplicationWindow::new(app); @@ -76,17 +95,33 @@ fn main() -> Result<()> { app_window.fullscreen(); - let widget_state = WidgetState(HashMap::new()); + let mut eww_state = EwwState::from_default_vars(eww_config.get_default_vars().clone()); + let empty_local_state = HashMap::new(); app_window.add( - &element_to_gtk_widget(&eww_config.widgets(), &widget_state, &window_def.widget) - .unwrap(), + &element_to_gtk_thing( + &eww_config.get_widgets(), + &mut eww_state, + &empty_local_state, + &window_def.widget, + ) + .unwrap(), ); app_window.show_all(); - let window = app_window.get_window().unwrap(); + let (tx, rx) = glib::MainContext::channel(glib::PRIORITY_DEFAULT); + std::thread::spawn(move || event_loop(tx)); + rx.attach(None, move |msg| { + match msg { + MuhhMsg::UpdateValue(key, value) => eww_state.update_value(key, value), + } + + glib::Continue(true) + }); + + let window = app_window.get_window().unwrap(); window.set_override_redirect(true); window.move_(window_def.position.0, window_def.position.1); window.show(); @@ -94,37 +129,67 @@ fn main() -> Result<()> { }); application.run(&[]); + Ok(()) } -fn element_to_gtk_widget( +fn event_loop(sender: glib::Sender) { + let mut x = 0; + loop { + x += 1; + std::thread::sleep_ms(1000); + sender + .send(MuhhMsg::UpdateValue( + "ree".to_string(), + config::AttrValue::Number(x as f64 * 10.0), + )) + .unwrap(); + } +} + +fn element_to_gtk_thing( widget_definitions: &HashMap, - widget_state: &WidgetState, + eww_state: &mut EwwState, + local_environment: &HashMap, element: &config::ElementUse, ) -> Option { match element { config::ElementUse::Text(text) => Some(gtk::Label::new(Some(&text)).upcast()), config::ElementUse::Widget(widget) => { - let gtk_widget = - widget_use_to_gtk_container(widget_definitions, widget_state, &widget).or( - widget_use_to_gtk_widget(widget_definitions, widget_state, &widget), - )?; - if let Some(css_class) = widget - .attrs - .get("class") - .and_then(config::AttrValue::as_string) - { - gtk_widget.get_style_context().add_class(css_class); - } - - Some(gtk_widget) + widget_use_to_gtk_thing(widget_definitions, eww_state, local_environment, widget) } } } +fn widget_use_to_gtk_thing( + widget_definitions: &HashMap, + eww_state: &mut EwwState, + local_environment: &HashMap, + widget: &config::WidgetUse, +) -> Option { + let gtk_widget = + widget_use_to_gtk_container(widget_definitions, eww_state, &local_environment, &widget) + .or(widget_use_to_gtk_widget( + widget_definitions, + eww_state, + &local_environment, + &widget, + ))?; + if let Some(css_class) = widget + .attrs + .get("class") + .and_then(config::AttrValue::as_string) + { + gtk_widget.get_style_context().add_class(css_class); + } + + Some(gtk_widget) +} + fn widget_use_to_gtk_container( widget_definitions: &HashMap, - widget_state: &WidgetState, + eww_state: &mut EwwState, + local_environment: &HashMap, widget: &config::WidgetUse, ) -> Option { let container_widget: gtk::Container = match widget.name.as_str() { @@ -134,9 +199,10 @@ fn widget_use_to_gtk_container( }; for child in &widget.children { - container_widget.add(&element_to_gtk_widget( + container_widget.add(&element_to_gtk_thing( widget_definitions, - widget_state, + eww_state, + local_environment, child, )?); } @@ -145,68 +211,136 @@ fn widget_use_to_gtk_container( fn widget_use_to_gtk_widget( widget_definitions: &HashMap, - state: &WidgetState, + eww_state: &mut EwwState, + local_env: &HashMap, widget: &config::WidgetUse, ) -> Option { let new_widget: gtk::Widget = match widget.name.as_str() { "slider" => { - let slider_value: f64 = state.resolve(widget.attrs.get("value")?)?.as_f64()?; - let slider_min: Option = - try { state.resolve(widget.attrs.get("min")?)?.as_f64()? }; - let slider_min = slider_min.unwrap_or(0f64); - let slider_max: Option = - try { state.resolve(widget.attrs.get("max")?)?.as_f64()? }; - let slider_max = slider_max.unwrap_or(100f64); - - let on_change: Option = try { - state - .resolve(widget.attrs.get("onchange")?)? - .as_string()? - .clone() - }; - let scale = gtk::Scale::new( gtk::Orientation::Horizontal, - Some(>k::Adjustment::new( - slider_value, - slider_min, - slider_max, - 1.0, - 1.0, - 1.0, - )), + Some(>k::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)), ); - scale.set_property("draw-value", &false.to_value()).ok()?; - - if let Some(on_change) = on_change { - scale.connect_value_changed(move |scale| { - run_command(&on_change, scale.get_value()); - }); - } + eww_state.resolve_f64(local_env, widget.attrs.get("value")?, { + let scale = scale.clone(); + move |value| scale.set_value(value) + }); + eww_state.resolve_f64(local_env, widget.attrs.get("min")?, { + let scale = scale.clone(); + move |value| scale.get_adjustment().set_lower(value) + }); + eww_state.resolve_f64(local_env, widget.attrs.get("max")?, { + let scale = scale.clone(); + move |value| scale.get_adjustment().set_upper(value) + }); + eww_state.resolve_string(local_env, widget.attrs.get("onchange")?, { + let scale = scale.clone(); + move |on_change| { + scale.connect_value_changed(move |scale| { + run_command(&on_change, scale.get_value()); + }); + } + }); + //scale.set_property("draw-value", &false.to_value()).ok()?; scale.upcast() } name if widget_definitions.contains_key(name) => { let def = &widget_definitions[name]; - element_to_gtk_widget(widget_definitions, state, &def.structure)? + let local_environment = build!(env = local_env.clone(); { + env.extend(widget.attrs.clone()); + }); + + element_to_gtk_thing( + widget_definitions, + eww_state, + &local_environment, + &def.structure, + )? } _ => return None, }; Some(new_widget) } -struct WidgetState(HashMap); +#[derive(Default)] +struct EwwState { + on_change_handlers: HashMap>>, + state: HashMap, +} -impl WidgetState { - pub fn resolve(&self, value: &config::AttrValue) -> Option { - if let config::AttrValue::VarRef(name) = value { - // TODO REEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE - self.0.get(name).cloned() - } else { - Some(value.clone()) +impl EwwState { + pub fn from_default_vars(defaults: HashMap) -> Self { + EwwState { + state: defaults, + ..EwwState::default() } } + pub fn update_value(&mut self, key: String, value: config::AttrValue) { + if let Some(handlers) = self.on_change_handlers.get(&key) { + for on_change in handlers { + on_change(value.clone()); + } + } + self.state.insert(key, value); + } + + pub fn resolve( + &mut self, + local_env: &HashMap, + value: &config::AttrValue, + set_value: F, + ) -> bool { + dbg!("resolve: ", value); + if let config::AttrValue::VarRef(name) = value { + if let Some(value) = self.state.get(name).cloned() { + self.on_change_handlers + .entry(name.to_string()) + .or_insert_with(Vec::new) + .push(Box::new(set_value.clone())); + self.resolve(local_env, &value, set_value) + } else if let Some(value) = local_env.get(name).cloned() { + self.resolve(local_env, &value, set_value) + } else { + false + } + } else { + set_value(value.clone()); + true + } + } + + pub fn resolve_f64( + &mut self, + local_env: &HashMap, + value: &config::AttrValue, + set_value: F, + ) -> bool { + self.resolve(local_env, value, move |x| { + x.as_f64().map(|v| set_value(v)); + }) + } + pub fn resolve_bool( + &mut self, + local_env: &HashMap, + value: &config::AttrValue, + set_value: F, + ) -> bool { + self.resolve(local_env, value, move |x| { + x.as_bool().map(|v| set_value(v)); + }) + } + pub fn resolve_string( + &mut self, + local_env: &HashMap, + value: &config::AttrValue, + set_value: F, + ) -> bool { + self.resolve(local_env, value, move |x| { + x.as_string().map(|s| set_value(s.clone())); + }) + } } fn run_command(cmd: &str, arg: T) { @@ -215,11 +349,3 @@ fn run_command(cmd: &str, arg: T) { eprintln!("{}", e); } } - -// macro_rules! build { -// ($var_name:ident = $value:expr ; $code:block) => {{ -// let mut $var_name = $value; -// $code; -// $var_name -// }}; -// }