fancy macro makes things gud

This commit is contained in:
elkowar 2020-09-20 23:13:20 +02:00
parent 1969b6eb56
commit 7af0cb2489
7 changed files with 267 additions and 199 deletions

View file

@ -1,5 +1,6 @@
use super::*; use super::*;
use crate::value::AttrValue;
use hocon_ext::HoconExt; use hocon_ext::HoconExt;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
@ -95,6 +96,13 @@ impl WidgetUse {
attrs, attrs,
}) })
} }
pub fn get_attr(&self, key: &str) -> Result<&AttrValue> {
self.attrs.get(key).context(format!(
"attribute {} missing from widgetuse of {}",
key, &self.name
))
}
} }
impl From<WidgetUse> for ElementUse { impl From<WidgetUse> for ElementUse {

View file

@ -5,7 +5,6 @@ use hocon::*;
use hocon_ext::HoconExt; use hocon_ext::HoconExt;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom; use std::convert::TryFrom;
use try_match::try_match;
pub mod element; pub mod element;
pub mod hocon_ext; pub mod hocon_ext;
@ -111,55 +110,6 @@ impl EwwWindowDefinition {
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum AttrValue {
Concrete(PrimitiveValue),
VarRef(String),
}
impl AttrValue {
pub fn as_string(&self) -> Result<&String> {
try_match!(AttrValue::Concrete(x) = self)
.map_err(|e| anyhow!("{:?} is not a string", e))?
.as_string()
}
pub fn as_f64(&self) -> Result<f64> {
try_match!(AttrValue::Concrete(x) = self)
.map_err(|e| anyhow!("{:?} is not an f64", e))?
.as_f64()
}
pub fn as_bool(&self) -> Result<bool> {
try_match!(AttrValue::Concrete(x) = self)
.map_err(|e| anyhow!("{:?} is not a bool", e))?
.as_bool()
}
pub fn as_var_ref(&self) -> Result<&String> {
try_match!(AttrValue::VarRef(x) = self).map_err(|e| anyhow!("{:?} is not a VarRef", e))
}
}
impl From<PrimitiveValue> for AttrValue {
fn from(value: PrimitiveValue) -> Self {
AttrValue::Concrete(value)
}
}
impl std::convert::TryFrom<&Hocon> for AttrValue {
type Error = anyhow::Error;
fn try_from(value: &Hocon) -> Result<Self> {
Ok(match value {
Hocon::String(s) if s.starts_with("$$") => {
AttrValue::VarRef(s.trim_start_matches("$$").to_string())
}
Hocon::String(s) => AttrValue::Concrete(PrimitiveValue::String(s.clone())),
Hocon::Integer(n) => AttrValue::Concrete(PrimitiveValue::Number(*n as f64)),
Hocon::Real(n) => AttrValue::Concrete(PrimitiveValue::Number(*n as f64)),
Hocon::Boolean(b) => AttrValue::Concrete(PrimitiveValue::Boolean(*b)),
_ => return Err(anyhow!("cannot convert {:?} to config::AttrValue", &value)),
})
}
}
pub fn parse_hocon(s: &str) -> Result<Hocon> { pub fn parse_hocon(s: &str) -> Result<Hocon> {
Ok(HoconLoader::new().load_str(s)?.hocon()?) Ok(HoconLoader::new().load_str(s)?.hocon()?)
} }

93
src/eww_state.rs Normal file
View file

@ -0,0 +1,93 @@
use std::collections::HashMap;
use crate::value::{AttrValue, PrimitiveValue};
#[derive(Default)]
pub struct EwwState {
on_change_handlers: HashMap<String, Vec<Box<dyn Fn(PrimitiveValue) + 'static>>>,
state: HashMap<String, PrimitiveValue>,
}
impl EwwState {
pub fn from_default_vars(defaults: HashMap<String, PrimitiveValue>) -> Self {
EwwState {
state: defaults,
..EwwState::default()
}
}
pub fn update_value(&mut self, key: String, value: PrimitiveValue) {
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<F: Fn(PrimitiveValue) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, AttrValue>,
value: &AttrValue,
set_value: F,
) -> bool {
dbg!("resolve: ", value);
match value {
AttrValue::VarRef(name) => {
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.into(), set_value)
} else if let Some(value) = local_env.get(name).cloned() {
self.resolve(local_env, &value, set_value)
} else {
false
}
}
AttrValue::Concrete(value) => {
set_value(value.clone());
true
}
}
}
pub fn resolve_f64<F: Fn(f64) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, AttrValue>,
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<F: Fn(bool) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, AttrValue>,
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_string<F: Fn(String) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, AttrValue>,
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);
};
})
}
}

View file

@ -2,22 +2,29 @@
extern crate gio; extern crate gio;
extern crate gtk; extern crate gtk;
use anyhow::{self, Result}; use anyhow::*;
use gdk::*; use gdk::*;
use gio::prelude::*; use gio::prelude::*;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Application, ApplicationWindow}; use gtk::{Application, ApplicationWindow};
use std::{collections::HashMap, process::Command}; use std::collections::HashMap;
pub mod config; pub mod config;
pub mod eww_state;
pub mod value; pub mod value;
pub mod widgets; pub mod widgets;
use config::element; use config::element;
use config::AttrValue; use eww_state::*;
use value::PrimitiveValue; use value::{AttrValue, PrimitiveValue};
const CMD_STRING_PLACEHODLER: &str = "{}"; macro_rules! build {
($var_name:ident = $value:expr ; $code:block) => {{
let mut $var_name = $value;
$code;
$var_name
}};
}
const EXAMPLE_CONFIG: &str = r#"{ const EXAMPLE_CONFIG: &str = r#"{
widgets: { widgets: {
@ -61,14 +68,6 @@ const EXAMPLE_CONFIG: &str = r#"{
}, },
}"#; }"#;
macro_rules! build {
($var_name:ident = $value:expr ; $code:block) => {{
let mut $var_name = $value;
$code;
$var_name
}};
}
#[derive(Debug)] #[derive(Debug)]
enum MuhhMsg { enum MuhhMsg {
UpdateValue(String, PrimitiveValue), UpdateValue(String, PrimitiveValue),
@ -170,9 +169,9 @@ fn element_to_gtk_thing(
eww_state: &mut EwwState, eww_state: &mut EwwState,
local_environment: &HashMap<String, AttrValue>, local_environment: &HashMap<String, AttrValue>,
element: &element::ElementUse, element: &element::ElementUse,
) -> Option<gtk::Widget> { ) -> Result<gtk::Widget> {
match element { match element {
element::ElementUse::Text(text) => Some(gtk::Label::new(Some(&text)).upcast()), element::ElementUse::Text(text) => Ok(gtk::Label::new(Some(&text)).upcast()),
element::ElementUse::Widget(widget) => { element::ElementUse::Widget(widget) => {
widget_use_to_gtk_thing(widget_definitions, eww_state, local_environment, widget) widget_use_to_gtk_thing(widget_definitions, eww_state, local_environment, widget)
} }
@ -184,7 +183,7 @@ fn widget_use_to_gtk_thing(
eww_state: &mut EwwState, eww_state: &mut EwwState,
local_environment: &HashMap<String, AttrValue>, local_environment: &HashMap<String, AttrValue>,
widget: &element::WidgetUse, widget: &element::WidgetUse,
) -> Option<gtk::Widget> { ) -> Result<gtk::Widget> {
let gtk_widget = let gtk_widget =
widget_use_to_gtk_container(widget_definitions, eww_state, &local_environment, &widget) widget_use_to_gtk_container(widget_definitions, eww_state, &local_environment, &widget)
.or(widget_use_to_gtk_widget( .or(widget_use_to_gtk_widget(
@ -201,7 +200,7 @@ fn widget_use_to_gtk_thing(
gtk_widget.get_style_context().add_class(css_class); gtk_widget.get_style_context().add_class(css_class);
} }
Some(gtk_widget) Ok(gtk_widget)
} }
fn widget_use_to_gtk_container( fn widget_use_to_gtk_container(
@ -209,11 +208,11 @@ fn widget_use_to_gtk_container(
eww_state: &mut EwwState, eww_state: &mut EwwState,
local_environment: &HashMap<String, AttrValue>, local_environment: &HashMap<String, AttrValue>,
widget: &element::WidgetUse, widget: &element::WidgetUse,
) -> Option<gtk::Widget> { ) -> Result<gtk::Widget> {
let container_widget: gtk::Container = match widget.name.as_str() { let container_widget: gtk::Container = match widget.name.as_str() {
"layout_horizontal" => gtk::Box::new(gtk::Orientation::Horizontal, 0).upcast(), "layout_horizontal" => gtk::Box::new(gtk::Orientation::Horizontal, 0).upcast(),
"button" => gtk::Button::new().upcast(), "button" => gtk::Button::new().upcast(),
_ => return None, _ => return Err(anyhow!("{} is not a known container widget", widget.name)),
}; };
for child in &widget.children { for child in &widget.children {
@ -224,7 +223,7 @@ fn widget_use_to_gtk_container(
child, child,
)?); )?);
} }
Some(container_widget.upcast()) Ok(container_widget.upcast())
} }
fn widget_use_to_gtk_widget( fn widget_use_to_gtk_widget(
@ -232,37 +231,14 @@ fn widget_use_to_gtk_widget(
eww_state: &mut EwwState, eww_state: &mut EwwState,
local_env: &HashMap<String, AttrValue>, local_env: &HashMap<String, AttrValue>,
widget: &element::WidgetUse, widget: &element::WidgetUse,
) -> Option<gtk::Widget> { ) -> Result<gtk::Widget> {
let builder_args = widgets::BuilderArgs {
eww_state,
local_env: &local_env,
widget: &widget,
};
let new_widget: gtk::Widget = match widget.name.as_str() { let new_widget: gtk::Widget = match widget.name.as_str() {
"slider" => { "slider" => widgets::build_gtk_scale(builder_args)?.upcast(),
let scale = gtk::Scale::new(
gtk::Orientation::Horizontal,
Some(&gtk::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)),
);
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) => { name if widget_definitions.contains_key(name) => {
let def = &widget_definitions[name]; let def = &widget_definitions[name];
@ -277,104 +253,7 @@ fn widget_use_to_gtk_widget(
&def.structure, &def.structure,
)? )?
} }
_ => return None, _ => return Err(anyhow!("unknown widget {}", &widget.name)),
}; };
Some(new_widget) Ok(new_widget)
}
#[derive(Default)]
struct EwwState {
on_change_handlers: HashMap<String, Vec<Box<dyn Fn(PrimitiveValue) + 'static>>>,
state: HashMap<String, PrimitiveValue>,
}
impl EwwState {
pub fn from_default_vars(defaults: HashMap<String, PrimitiveValue>) -> Self {
EwwState {
state: defaults,
..EwwState::default()
}
}
pub fn update_value(&mut self, key: String, value: PrimitiveValue) {
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<F: Fn(PrimitiveValue) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, AttrValue>,
value: &AttrValue,
set_value: F,
) -> bool {
dbg!("resolve: ", value);
match value {
AttrValue::VarRef(name) => {
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.into(), set_value)
} else if let Some(value) = local_env.get(name).cloned() {
self.resolve(local_env, &value, set_value)
} else {
false
}
}
AttrValue::Concrete(value) => {
set_value(value.clone());
true
}
}
}
pub fn resolve_f64<F: Fn(f64) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, AttrValue>,
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<F: Fn(bool) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, AttrValue>,
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_string<F: Fn(String) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, AttrValue>,
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);
};
})
}
}
fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg));
if let Err(e) = Command::new("bash").arg("-c").arg(cmd).output() {
eprintln!("{}", e);
}
} }

View file

@ -44,3 +44,52 @@ impl std::convert::TryFrom<&Hocon> for PrimitiveValue {
}) })
} }
} }
#[derive(Clone, Debug, PartialEq)]
pub enum AttrValue {
Concrete(PrimitiveValue),
VarRef(String),
}
impl AttrValue {
pub fn as_string(&self) -> Result<&String> {
try_match!(AttrValue::Concrete(x) = self)
.map_err(|e| anyhow!("{:?} is not a string", e))?
.as_string()
}
pub fn as_f64(&self) -> Result<f64> {
try_match!(AttrValue::Concrete(x) = self)
.map_err(|e| anyhow!("{:?} is not an f64", e))?
.as_f64()
}
pub fn as_bool(&self) -> Result<bool> {
try_match!(AttrValue::Concrete(x) = self)
.map_err(|e| anyhow!("{:?} is not a bool", e))?
.as_bool()
}
pub fn as_var_ref(&self) -> Result<&String> {
try_match!(AttrValue::VarRef(x) = self).map_err(|e| anyhow!("{:?} is not a VarRef", e))
}
}
impl From<PrimitiveValue> for AttrValue {
fn from(value: PrimitiveValue) -> Self {
AttrValue::Concrete(value)
}
}
impl std::convert::TryFrom<&Hocon> for AttrValue {
type Error = anyhow::Error;
fn try_from(value: &Hocon) -> Result<Self> {
Ok(match value {
Hocon::String(s) if s.starts_with("$$") => {
AttrValue::VarRef(s.trim_start_matches("$$").to_string())
}
Hocon::String(s) => AttrValue::Concrete(PrimitiveValue::String(s.clone())),
Hocon::Integer(n) => AttrValue::Concrete(PrimitiveValue::Number(*n as f64)),
Hocon::Real(n) => AttrValue::Concrete(PrimitiveValue::Number(*n as f64)),
Hocon::Boolean(b) => AttrValue::Concrete(PrimitiveValue::Boolean(*b)),
_ => return Err(anyhow!("cannot convert {:?} to config::AttrValue", &value)),
})
}
}

89
src/widgets.rs Normal file
View file

@ -0,0 +1,89 @@
use crate::config::element;
use crate::eww_state::*;
use crate::value::AttrValue;
use anyhow::*;
use gtk::prelude::*;
use std::{collections::HashMap, process::Command};
const CMD_STRING_PLACEHODLER: &str = "{}";
macro_rules! resolve {
($args:ident, $gtk_widget:ident, {
$($func:ident =>
{
$($attr:literal => |$arg:ident| $body:expr),+
}
),+
}) => {
$(
$(
$args.eww_state.$func($args.local_env, $args.widget.get_attr($attr)?, {
let $gtk_widget = $gtk_widget.clone();
move |$arg| { $body; }
});
)+
)+
}
}
pub struct BuilderArgs<'a, 'b, 'c> {
pub eww_state: &'a mut EwwState,
pub local_env: &'b HashMap<String, AttrValue>,
pub widget: &'c element::WidgetUse,
}
pub fn build_gtk_scale(builder_args: BuilderArgs) -> Result<gtk::Scale> {
let gtk_widget = gtk::Scale::new(
gtk::Orientation::Horizontal,
Some(&gtk::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)),
);
resolve!(builder_args, gtk_widget, {
resolve_f64 => {
"value" => |v| gtk_widget.set_value(v),
"min" => |v| gtk_widget.get_adjustment().set_lower(v),
"max" => |v| gtk_widget.get_adjustment().set_upper(v)
},
resolve_string => {
"onchange" => |cmd| {
gtk_widget.connect_value_changed(move |gtk_widget| {
run_command(&cmd, gtk_widget.get_value());
});
}
}
});
Ok(gtk_widget)
}
pub fn build_gtk_button(builder_args: BuilderArgs) -> Result<gtk::Button> {
let gtk_widget = gtk::Button::new();
resolve!(builder_args, gtk_widget, {
resolve_bool => {
"active" => |v| gtk_widget.set_sensitive(v)
},
resolve_string => {
"onclick" => |cmd| gtk_widget.connect_clicked(move |_| run_command(&cmd, ""))
}
});
Ok(gtk_widget)
}
pub fn build_gtk_layout(builder_args: BuilderArgs) -> Result<gtk::Box> {
let gtk_widget = gtk::Box::new(gtk::Orientation::Horizontal, 0);
resolve!(builder_args, gtk_widget, {
resolve_f64 => {
"spacing" => |v| gtk_widget.set_spacing(v as i32)
}
});
Ok(gtk_widget)
}
//"layout_horizontal" => gtk::Box::new(gtk::Orientation::Horizontal, 0).upcast(),
fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg));
if let Err(e) = Command::new("bash").arg("-c").arg(cmd).output() {
eprintln!("{}", e);
}
}

View file