diff --git a/src/app.rs b/src/app.rs index efe3f68..4b379f9 100644 --- a/src/app.rs +++ b/src/app.rs @@ -26,7 +26,7 @@ pub struct App { impl App { pub fn handle_user_command(&mut self, opts: &Opt) -> Result<()> { match &opts.action { - OptAction::Update { fieldname, value } => self.update_state(fieldname.clone(), value.clone()), + OptAction::Update { fieldname, value } => self.update_state(fieldname.clone(), value.clone())?, OptAction::OpenWindow { window_name } => self.open_window(&window_name)?, OptAction::CloseWindow { window_name } => self.close_window(&window_name)?, OptAction::KillServer => { @@ -43,7 +43,7 @@ impl App { let result: Result<_> = try { match event { EwwEvent::UserCommand(command) => self.handle_user_command(&command)?, - EwwEvent::UpdateVar(key, value) => self.update_state(key, value), + EwwEvent::UpdateVar(key, value) => self.update_state(key, value)?, EwwEvent::ReloadConfig(config) => self.reload_all_windows(config)?, EwwEvent::ReloadCss(css) => self.load_css(&css)?, } @@ -53,8 +53,8 @@ impl App { } } - fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) { - self.eww_state.update_value(fieldname, value); + fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) -> Result<()> { + self.eww_state.update_value(fieldname, value) } fn close_window(&mut self, window_name: &str) -> Result<()> { diff --git a/src/eww_state.rs b/src/eww_state.rs index 71ef02f..f4330c9 100644 --- a/src/eww_state.rs +++ b/src/eww_state.rs @@ -7,24 +7,56 @@ use std::sync::Arc; use crate::value::{AttrValue, PrimitiveValue}; //pub struct StateChangeHandler(Box) + 'static>); +pub struct StateChangeHandler { + func: Box) -> Result<()> + 'static>, + constant_values: HashMap, + unresolved_attrs: HashMap, +} + +impl StateChangeHandler { + fn run_with_state(&self, state: &HashMap) -> Result<()> { + let mut all_resolved_attrs = self.constant_values.clone(); + for (attr_name, var_ref) in self.unresolved_attrs.iter() { + let resolved = state + .get(var_ref) + // TODO provide context here, including line numbers + .with_context(|| format!("Unknown variable '{}' was referenced", var_ref))?; + all_resolved_attrs.insert(attr_name.to_owned(), resolved.clone()); + } + + let result: Result<_> = (self.func)(all_resolved_attrs); + if let Err(err) = result { + eprintln!("WARN: Error while resolving attributes: {}", err); + } + + Ok(()) + } +} pub struct StateChangeHandlers { - handlers: HashMap) + 'static>>>, + handlers: HashMap>>, } impl StateChangeHandlers { - fn put_handler(&mut self, var_names: Vec, handler: Arc) + 'static>) { - for var_name in var_names { - let entry: &mut Vec) + 'static>> = - self.handlers.entry(var_name).or_insert_with(Vec::new); - entry.push(handler); + fn put_handler(&mut self, handler: StateChangeHandler) { + let handler = Arc::new(handler); + for var_name in handler.unresolved_attrs.values() { + let entry: &mut Vec> = self.handlers.entry(var_name.clone()).or_insert_with(Vec::new); + entry.push(handler.clone()); } } + + fn get(&self, key: &VarName) -> Option<&Vec>> { + self.handlers.get(key) + } + + fn clear(&mut self) { + self.handlers.clear(); + } } pub struct EwwState { state_change_handlers: StateChangeHandlers, - //on_change_handlers: HashMap>, state: HashMap, } @@ -54,97 +86,65 @@ impl EwwState { } pub fn clear_callbacks(&mut self) { - self.on_change_handlers.clear(); + self.state_change_handlers.clear(); } - pub fn update_value(&mut self, key: VarName, value: PrimitiveValue) { - if let Some(handlers) = self.on_change_handlers.get(&key) { - for on_change in handlers { - on_change(value.clone()); + pub fn update_value(&mut self, key: VarName, value: PrimitiveValue) -> Result<()> { + if let Some(handlers) = self.state_change_handlers.get(&key) { + self.state.insert(key.clone(), value); + for handler in handlers { + handler + .run_with_state(&self.state) + .with_context(|| format!("When updating value of {}", &key))?; } } - self.state.insert(key, value); + Ok(()) } - pub fn resolve( + pub fn resolve) -> Result<()> + 'static + Clone>( &mut self, local_env: &HashMap, - value: &AttrValue, + mut needed_attributes: HashMap, set_value: F, - ) -> bool { - match value { - AttrValue::VarRef(name) => { - // get value from globals - if let Some(value) = self.state.get(&name).cloned() { - self.on_change_handlers - .entry(name.clone()) - .or_insert_with(Vec::new) - .push(Box::new(set_value.clone())); - self.resolve(local_env, &value.into(), set_value) - } else if let Some(value) = local_env.get(&name).cloned() { - // get value from local - self.resolve(local_env, &value, set_value) - } else { - eprintln!("WARN: unknown variable '{}' was referenced", name); - false + ) { + let mut resolved_attrs = HashMap::new(); + let mut unresolved_attrs: HashMap = HashMap::new(); + needed_attributes + .drain() + .for_each(|(attr_name, attr_value)| match attr_value { + AttrValue::Concrete(primitive) => { + resolved_attrs.insert(attr_name, primitive); } + AttrValue::VarRef(var_name) => match local_env.get(&var_name) { + Some(AttrValue::VarRef(var_ref_from_local)) => { + unresolved_attrs.insert(attr_name, var_ref_from_local.clone()); + } + Some(AttrValue::Concrete(concrete_from_local)) => { + resolved_attrs.insert(attr_name, concrete_from_local.clone()); + } + None => { + unresolved_attrs.insert(attr_name, var_name); + } + }, + }); + + let result: Result<_> = try { + if unresolved_attrs.is_empty() { + set_value(resolved_attrs)?; + } else { + let handler = StateChangeHandler { + func: Box::new(set_value.clone()), + constant_values: resolved_attrs, + unresolved_attrs, + }; + handler.run_with_state(&self.state)?; + self.state_change_handlers.put_handler(handler); } - AttrValue::Concrete(value) => { - set_value(value.clone()); - true - } + }; + if let Err(e) = result { + eprintln!("{}", e); } } - - //pub fn resolve_attrs) + 'static + Clone>( - //&mut self, - //local_env: &HashMap, - //unresolved_attrs: HashMap, - //state_update_handler: F, - //) { - //let var_names = values.iter().filter_map(|value| value.as_var_ref().ok()).collect(); - //self.state_change_handlers - //.put_handler(var_names, Arc::new(state_update_handler)) - //} - - pub fn resolve_f64( - &mut self, - local_env: &HashMap, - value: &AttrValue, - set_value: F, - ) -> bool { - self.resolve(local_env, value, move |x| { - if let Err(e) = x.as_f64().map(|v| set_value(v)) { - eprintln!("error while resolving value: {}", e); - }; - }) - } - - #[allow(dead_code)] - pub fn resolve_bool( - &mut self, - local_env: &HashMap, - value: &AttrValue, - set_value: F, - ) -> bool { - self.resolve(local_env, value, move |x| { - if let Err(e) = x.as_bool().map(|v| set_value(v)) { - eprintln!("error while resolving value: {}", e); - }; - }) - } - pub fn resolve_str( - &mut self, - local_env: &HashMap, - value: &AttrValue, - set_value: F, - ) -> bool { - self.resolve(local_env, value, move |x| { - if let Err(e) = x.as_string().map(|v| set_value(v.clone())) { - eprintln!("error while resolving value: {}", e); - }; - }) - } } pub fn run_command(cmd: &str) -> Result { @@ -152,3 +152,11 @@ pub fn run_command(cmd: &str) -> Result { let output = output.trim_matches('\n'); Ok(PrimitiveValue::from(output)) } + +pub fn recursive_lookup<'a>(data: &'a HashMap, key: &VarName) -> Result<&'a PrimitiveValue> { + match data.get(key) { + Some(AttrValue::Concrete(x)) => Ok(x), + Some(AttrValue::VarRef(x)) => recursive_lookup(data, x), + None => Err(anyhow!("No value found for key '{}'", key)), + } +} diff --git a/src/value.rs b/src/value.rs index 3153c87..15ea06d 100644 --- a/src/value.rs +++ b/src/value.rs @@ -158,7 +158,7 @@ impl AttrValue { } } - pub fn as_var_ref(&self) -> Result { + pub fn as_var_ref(&self) -> Result<&VarName> { match self { AttrValue::VarRef(x) => Ok(x), _ => Err(anyhow!("{:?} is not a variable reference", self)), diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 67840c8..f726014 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -37,6 +37,7 @@ pub fn element_to_gtk_thing( let gtk_widget = if let Some(gtk_container) = gtk_container { gtk_container } else if let Some(def) = widget_definitions.get(widget.name.as_str()) { + // TODO widget cleanup phase, where widget arguments are resolved as far as possible beforehand? let mut local_env = local_env.clone(); local_env.extend(widget.attrs.clone().into_iter().map(|(k, v)| (VarName(k), v))); let custom_widget = element_to_gtk_thing(widget_definitions, eww_state, &local_env, &def.structure)?; diff --git a/src/widgets/widget_definitions.rs b/src/widgets/widget_definitions.rs index 3c9dff1..4453d07 100644 --- a/src/widgets/widget_definitions.rs +++ b/src/widgets/widget_definitions.rs @@ -4,58 +4,101 @@ use crate::value::{AttrValue, PrimitiveValue, VarName}; use anyhow::*; use gtk::prelude::*; use gtk::ImageExt; +use maplit::hashmap; use std::path::Path; // TODO figure out how to // TODO https://developer.gnome.org/gtk3/stable/GtkFixed.html -// general attributes +#[macro_export] +macro_rules! resolve_block { + ($args:ident, $gtk_widget:ident, { + $( + prop( $( $attr_name:ident : $typecast_func:ident ),*) $code:block + ),+ $(,)? + }) => { + $({ + $( + $args.unhandled_attrs.retain(|a| a != &::std::stringify!($attr_name).replace('_', "-")); + )* + // TODO reimplement unused warnings + let attr_map: Result<_> = try { + ::maplit::hashmap! { + $( + ::std::stringify!($attr_name).to_owned() => $args.widget.get_attr(&::std::stringify!($attr_name).replace('_', "-"))?.clone() + ),* + } + }; + if let Ok(attr_map) = attr_map { + $args.eww_state.resolve( + $args.local_env, + attr_map, + ::glib::clone!(@strong $gtk_widget => move |attrs| { + $( + let $attr_name = attrs.get( ::std::stringify!($attr_name) ).context("REEE")?.$typecast_func()?; + )* + $code + Ok(()) + }) + ); + } + })+ + }; + // required + //($args:ident, $gtk_widget:ident, $func:ident => $attr:literal req => |$arg:ident| $body:expr) => { + //$args.unhandled_attrs.retain(|a| a != &$attr); + //$args.eww_state.$func($args.local_env, $args.widget.get_attr($attr)?, { + //let $gtk_widget = $gtk_widget.clone(); + //move |$arg| { $body; } + //}); + //}; +} /// attributes that apply to all widgets pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Widget) { - resolve!(bargs, gtk_widget, { - resolve_str => "class" => |v| gtk_widget.get_style_context().add_class(&v), - resolve_bool => "active" = true => |v| gtk_widget.set_sensitive(v), - resolve_str => "valign" => |v| gtk_widget.set_valign(parse_align(&v)), - resolve_str => "halign" => |v| gtk_widget.set_halign(parse_align(&v)), - resolve_f64 => "width" => |v| gtk_widget.set_size_request(v as i32, gtk_widget.get_allocated_height()), - resolve_f64 => "height" => |v| gtk_widget.set_size_request(gtk_widget.get_allocated_width(), v as i32), - resolve_bool => "visible" => |v| { + resolve_block!(bargs, gtk_widget, { + prop(class: as_string) { gtk_widget.get_style_context().add_class(&class) }, + prop(valign: as_string) { gtk_widget.set_valign(parse_align(&valign)) }, + prop(halign: as_string) { gtk_widget.set_halign(parse_align(&halign)) }, + prop(width: as_f64 ) { gtk_widget.set_size_request(width as i32, gtk_widget.get_allocated_height()) }, + prop(height: as_f64 ) { gtk_widget.set_size_request(gtk_widget.get_allocated_width(), height as i32) }, + prop(active: as_bool ) { gtk_widget.set_sensitive(active) }, + prop(visible: as_bool ) { // TODO how do i call this only after the widget has been mapped? this is actually an issue,.... - if v { gtk_widget.show(); } else { gtk_widget.hide(); } - } + if visible { gtk_widget.show(); } else { gtk_widget.hide(); } + }, }); } /// attributes that apply to all container widgets pub(super) fn resolve_container_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Container) { - resolve!(bargs, gtk_widget, { - resolve_bool => "vexpand" = false => |v| gtk_widget.set_vexpand(v), - resolve_bool => "hexpand" = false => |v| gtk_widget.set_hexpand(v), + resolve_block!(bargs, gtk_widget, { + prop(vexpand: as_bool) { gtk_widget.set_vexpand(vexpand) }, + prop(hexpand: as_bool) { gtk_widget.set_hexpand(hexpand) }, }); } pub(super) fn resolve_range_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Range) { - resolve!(bargs, gtk_widget, { - resolve_f64 => "value" = req => |v| gtk_widget.set_value(v), - resolve_f64 => "min" => |v| gtk_widget.get_adjustment().set_lower(v), - resolve_f64 => "max" => |v| gtk_widget.get_adjustment().set_upper(v), - resolve_str => "orientation" => |v| gtk_widget.set_orientation(parse_orientation(&v)), - resolve_str => "onchange" => |cmd| { + resolve_block!(bargs, gtk_widget, { + prop(value : as_f64) { gtk_widget.set_value(value)}, + prop(min : as_f64) { gtk_widget.get_adjustment().set_lower(min)}, + prop(max : as_f64) { gtk_widget.get_adjustment().set_upper(max)}, + prop(orientation : as_string) { gtk_widget.set_orientation(parse_orientation(&orientation)) }, + prop(onchange : as_string) { gtk_widget.connect_value_changed(move |gtk_widget| { - run_command(&cmd, gtk_widget.get_value()); + run_command(&onchange, gtk_widget.get_value()); }); } }); } pub(super) fn resolve_orientable_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Range) { - resolve!(bargs, gtk_widget, { - resolve_str => "orientation" => |v| gtk_widget.set_orientation(parse_orientation(&v)), + resolve_block!(bargs, gtk_widget, { + prop(orientation: as_string) { gtk_widget.set_orientation(parse_orientation(&orientation)) }, }); } -// widget definitions +//// widget definitions pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result> { let gtk_widget = match bargs.widget.name.as_str() { @@ -78,45 +121,43 @@ fn build_gtk_scale(bargs: &mut BuilderArgs) -> Result { gtk::Orientation::Horizontal, Some(>k::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)), ); - resolve!(bargs, gtk_widget, { - resolve_bool => "flipped" => |v| gtk_widget.set_inverted(v), - resolve_bool => "draw-value" = false => |v| gtk_widget.set_draw_value(v), + resolve_block!(bargs, gtk_widget, { + prop(flipped: as_bool) { gtk_widget.set_inverted(flipped) }, + prop(draw_value: as_bool) { gtk_widget.set_draw_value(draw_value) }, }); Ok(gtk_widget) } fn build_gtk_button(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::Button::new(); - resolve!(bargs, gtk_widget, { - resolve_str => "onclick" => |v| gtk_widget.connect_clicked(move |_| run_command(&v, "")) + resolve_block!(bargs, gtk_widget, { + prop(onclick: as_string) { gtk_widget.connect_clicked(move |_| run_command(&onclick, "")); } }); Ok(gtk_widget) } fn build_gtk_image(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::Image::new(); - resolve!(bargs, gtk_widget, { - resolve_str => "path" = req => |v| { - gtk_widget.set_from_file(Path::new(&v)); - } + resolve_block!(bargs, gtk_widget, { + prop(path: as_string) { gtk_widget.set_from_file(Path::new(&path)); } }); Ok(gtk_widget) } fn build_gtk_layout(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0); - resolve!(bargs, gtk_widget, { - resolve_f64 => "spacing" = 0.0 => |v| gtk_widget.set_spacing(v as i32), - resolve_str => "orientation" => |v| gtk_widget.set_orientation(parse_orientation(&v)), - resolve_bool => "space-evenly" = true => |v| gtk_widget.set_homogeneous(v), + resolve_block!(bargs, gtk_widget, { + prop(spacing: as_f64 ) { gtk_widget.set_spacing(spacing as i32) }, + prop(orientation: as_string ) { gtk_widget.set_orientation(parse_orientation(&orientation)) }, + prop(space_evenly: as_bool) { gtk_widget.set_homogeneous(space_evenly) }, }); Ok(gtk_widget) } fn build_gtk_label(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::Label::new(None); - resolve!(bargs, gtk_widget, { - resolve_str => "text" => |v| gtk_widget.set_text(&v), + resolve_block!(bargs, gtk_widget, { + prop(text: as_string) { gtk_widget.set_text(&text) }, }); Ok(gtk_widget) } @@ -130,17 +171,16 @@ fn build_gtk_text(bargs: &mut BuilderArgs) -> Result { .context("text node must contain exactly one child")? .get_attr("text")?; let gtk_widget = gtk::Label::new(None); - bargs.eww_state.resolve_str( + bargs.eww_state.resolve( bargs.local_env, - text, - glib::clone!(@strong gtk_widget => move |v| gtk_widget.set_text(&v)), + hashmap! {"text".to_owned() => text.clone() }, + glib::clone!(@strong gtk_widget => move |v| { gtk_widget.set_text(&v.get("text").unwrap().as_string().unwrap()); Ok(())}), ); Ok(gtk_widget) } fn build_gtk_aspect_frame(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::AspectFrame::new(None, 0.5, 0.5, 1.0, true); - //resolve!(bargs, gtk_widget, {}); Ok(gtk_widget) }