variables

This commit is contained in:
elkowar 2020-09-20 18:02:37 +02:00
parent f7021201ca
commit 43b431c2dc
5 changed files with 348 additions and 91 deletions

45
.vscode/launch.json vendored Normal file
View file

@ -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}"
}
]
}

69
Cargo.lock generated
View file

@ -24,6 +24,15 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.32" version = "1.0.32"
@ -120,6 +129,22 @@ version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 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]] [[package]]
name = "either" name = "either"
version = "1.6.0" version = "1.6.0"
@ -200,6 +225,7 @@ dependencies = [
"glib", "glib",
"gtk", "gtk",
"hocon", "hocon",
"pretty_assertions",
"regex", "regex",
"try_match", "try_match",
] ]
@ -630,6 +656,15 @@ version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" 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]] [[package]]
name = "pango" name = "pango"
version = "0.9.1" version = "0.9.1"
@ -695,6 +730,18 @@ version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" 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]] [[package]]
name = "proc-macro-crate" name = "proc-macro-crate"
version = "0.1.5" version = "0.1.5"
@ -996,3 +1043,25 @@ name = "wasi"
version = "0.9.0+wasi-snapshot-preview1" version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 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"

View file

@ -23,3 +23,4 @@ anyhow = "1.0"
#thiserror = "1.0" #thiserror = "1.0"
pretty_assertions = "0.6.1"

View file

@ -1,8 +1,8 @@
use anyhow::*; use anyhow::*;
use glib::{types, value};
use hocon::*; use hocon::*;
use hocon_ext::HoconExt; use hocon_ext::HoconExt;
use std::collections::HashMap; use std::collections::HashMap;
use std::convert::TryFrom;
use try_match::try_match; use try_match::try_match;
pub mod hocon_ext; pub mod hocon_ext;
@ -11,6 +11,7 @@ pub mod hocon_ext;
pub struct EwwConfig { pub struct EwwConfig {
widgets: HashMap<String, WidgetDefinition>, widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<String, EwwWindowDefinition>, windows: HashMap<String, EwwWindowDefinition>,
default_vars: HashMap<String, AttrValue>,
} }
impl EwwConfig { impl EwwConfig {
@ -36,15 +37,26 @@ impl EwwConfig {
.iter() .iter()
.map(|(name, def)| Ok((name.clone(), EwwWindowDefinition::from_hocon(def)?))) .map(|(name, def)| Ok((name.clone(), EwwWindowDefinition::from_hocon(def)?)))
.collect::<Result<HashMap<String, EwwWindowDefinition>>>()?, .collect::<Result<HashMap<String, EwwWindowDefinition>>>()?,
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::<Result<HashMap<_, _>>>()?,
}) })
} }
pub fn widgets(&self) -> &HashMap<String, WidgetDefinition> { pub fn get_widgets(&self) -> &HashMap<String, WidgetDefinition> {
&self.widgets &self.widgets
} }
pub fn windows(&self) -> &HashMap<String, EwwWindowDefinition> { pub fn get_windows(&self) -> &HashMap<String, EwwWindowDefinition> {
&self.windows &self.windows
} }
pub fn get_default_vars(&self) -> &HashMap<String, AttrValue> {
&self.default_vars
}
} }
#[derive(Debug, Clone, PartialEq)] #[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<Self> {
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)] #[derive(Debug, Clone, PartialEq)]
pub struct WidgetDefinition { pub struct WidgetDefinition {
pub name: String, pub name: String,
@ -193,19 +221,7 @@ pub fn parse_widget_use(data: HashMap<String, Hocon>) -> Result<WidgetUse> {
let attrs: HashMap<String, AttrValue> = widget_config let attrs: HashMap<String, AttrValue> = widget_config
.into_iter() .into_iter()
.filter_map(|(key, value)| { .filter_map(|(key, value)| Some((key.to_lowercase(), AttrValue::try_from(value).ok()?)))
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,
},
))
})
.collect(); .collect();
Ok(WidgetUse { Ok(WidgetUse {

View file

@ -23,13 +23,17 @@ const EXAMPLE_CONFIG: &str = r#"{
children: [ children: [
"hi", "hi",
{ button: { children: "click me you" } } { 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" "hu"
] ]
} }
} }
} }
}, },
default_vars: {
ree: 12
}
windows: { windows: {
main_window: { main_window: {
pos.x: 200 pos.x: 200
@ -37,20 +41,35 @@ const EXAMPLE_CONFIG: &str = r#"{
size.x: 500 size.x: 500
size.y: 50 size.y: 50
widget: { 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<()> { fn main() -> Result<()> {
let eww_config = config::EwwConfig::from_hocon(&config::parse_hocon(EXAMPLE_CONFIG)?)?; 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) 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| { application.connect_activate(move |app| {
let app_window = ApplicationWindow::new(app); let app_window = ApplicationWindow::new(app);
@ -76,17 +95,33 @@ fn main() -> Result<()> {
app_window.fullscreen(); 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( app_window.add(
&element_to_gtk_widget(&eww_config.widgets(), &widget_state, &window_def.widget) &element_to_gtk_thing(
&eww_config.get_widgets(),
&mut eww_state,
&empty_local_state,
&window_def.widget,
)
.unwrap(), .unwrap(),
); );
app_window.show_all(); 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.set_override_redirect(true);
window.move_(window_def.position.0, window_def.position.1); window.move_(window_def.position.0, window_def.position.1);
window.show(); window.show();
@ -94,21 +129,52 @@ fn main() -> Result<()> {
}); });
application.run(&[]); application.run(&[]);
Ok(()) Ok(())
} }
fn element_to_gtk_widget( fn event_loop(sender: glib::Sender<MuhhMsg>) {
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<String, config::WidgetDefinition>, widget_definitions: &HashMap<String, config::WidgetDefinition>,
widget_state: &WidgetState, eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
element: &config::ElementUse, element: &config::ElementUse,
) -> Option<gtk::Widget> { ) -> Option<gtk::Widget> {
match element { match element {
config::ElementUse::Text(text) => Some(gtk::Label::new(Some(&text)).upcast()), config::ElementUse::Text(text) => Some(gtk::Label::new(Some(&text)).upcast()),
config::ElementUse::Widget(widget) => { config::ElementUse::Widget(widget) => {
widget_use_to_gtk_thing(widget_definitions, eww_state, local_environment, widget)
}
}
}
fn widget_use_to_gtk_thing(
widget_definitions: &HashMap<String, config::WidgetDefinition>,
eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse,
) -> Option<gtk::Widget> {
let gtk_widget = let gtk_widget =
widget_use_to_gtk_container(widget_definitions, widget_state, &widget).or( widget_use_to_gtk_container(widget_definitions, eww_state, &local_environment, &widget)
widget_use_to_gtk_widget(widget_definitions, widget_state, &widget), .or(widget_use_to_gtk_widget(
)?; widget_definitions,
eww_state,
&local_environment,
&widget,
))?;
if let Some(css_class) = widget if let Some(css_class) = widget
.attrs .attrs
.get("class") .get("class")
@ -119,12 +185,11 @@ fn element_to_gtk_widget(
Some(gtk_widget) Some(gtk_widget)
} }
}
}
fn widget_use_to_gtk_container( fn widget_use_to_gtk_container(
widget_definitions: &HashMap<String, config::WidgetDefinition>, widget_definitions: &HashMap<String, config::WidgetDefinition>,
widget_state: &WidgetState, eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse, widget: &config::WidgetUse,
) -> Option<gtk::Widget> { ) -> Option<gtk::Widget> {
let container_widget: gtk::Container = match widget.name.as_str() { 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 { for child in &widget.children {
container_widget.add(&element_to_gtk_widget( container_widget.add(&element_to_gtk_thing(
widget_definitions, widget_definitions,
widget_state, eww_state,
local_environment,
child, child,
)?); )?);
} }
@ -145,67 +211,135 @@ fn widget_use_to_gtk_container(
fn widget_use_to_gtk_widget( fn widget_use_to_gtk_widget(
widget_definitions: &HashMap<String, config::WidgetDefinition>, widget_definitions: &HashMap<String, config::WidgetDefinition>,
state: &WidgetState, eww_state: &mut EwwState,
local_env: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse, widget: &config::WidgetUse,
) -> Option<gtk::Widget> { ) -> Option<gtk::Widget> {
let new_widget: gtk::Widget = match widget.name.as_str() { let new_widget: gtk::Widget = match widget.name.as_str() {
"slider" => { "slider" => {
let slider_value: f64 = state.resolve(widget.attrs.get("value")?)?.as_f64()?;
let slider_min: Option<f64> =
try { state.resolve(widget.attrs.get("min")?)?.as_f64()? };
let slider_min = slider_min.unwrap_or(0f64);
let slider_max: Option<f64> =
try { state.resolve(widget.attrs.get("max")?)?.as_f64()? };
let slider_max = slider_max.unwrap_or(100f64);
let on_change: Option<String> = try {
state
.resolve(widget.attrs.get("onchange")?)?
.as_string()?
.clone()
};
let scale = gtk::Scale::new( let scale = gtk::Scale::new(
gtk::Orientation::Horizontal, gtk::Orientation::Horizontal,
Some(&gtk::Adjustment::new( Some(&gtk::Adjustment::new(0.0, 0.0, 100.0, 1.0, 1.0, 1.0)),
slider_value,
slider_min,
slider_max,
1.0,
1.0,
1.0,
)),
); );
scale.set_property("draw-value", &false.to_value()).ok()?; eww_state.resolve_f64(local_env, widget.attrs.get("value")?, {
let scale = scale.clone();
if let Some(on_change) = on_change { 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| { scale.connect_value_changed(move |scale| {
run_command(&on_change, scale.get_value()); run_command(&on_change, scale.get_value());
}); });
} }
});
//scale.set_property("draw-value", &false.to_value()).ok()?;
scale.upcast() 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];
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, _ => return None,
}; };
Some(new_widget) Some(new_widget)
} }
struct WidgetState(HashMap<String, config::AttrValue>); #[derive(Default)]
struct EwwState {
impl WidgetState { on_change_handlers: HashMap<String, Vec<Box<dyn Fn(config::AttrValue) + 'static>>>,
pub fn resolve(&self, value: &config::AttrValue) -> Option<config::AttrValue> { state: HashMap<String, config::AttrValue>,
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<String, config::AttrValue>) -> 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<F: Fn(config::AttrValue) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, config::AttrValue>,
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<F: Fn(f64) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, config::AttrValue>,
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<F: Fn(bool) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, config::AttrValue>,
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<F: Fn(String) + 'static + Clone>(
&mut self,
local_env: &HashMap<String, config::AttrValue>,
value: &config::AttrValue,
set_value: F,
) -> bool {
self.resolve(local_env, value, move |x| {
x.as_string().map(|s| set_value(s.clone()));
})
} }
} }
@ -215,11 +349,3 @@ fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
eprintln!("{}", e); eprintln!("{}", e);
} }
} }
// macro_rules! build {
// ($var_name:ident = $value:expr ; $code:block) => {{
// let mut $var_name = $value;
// $code;
// $var_name
// }};
// }