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",
]
[[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"

View file

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

View file

@ -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<String, WidgetDefinition>,
windows: HashMap<String, EwwWindowDefinition>,
default_vars: HashMap<String, AttrValue>,
}
impl EwwConfig {
@ -36,15 +37,26 @@ impl EwwConfig {
.iter()
.map(|(name, def)| Ok((name.clone(), EwwWindowDefinition::from_hocon(def)?)))
.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
}
pub fn windows(&self) -> &HashMap<String, EwwWindowDefinition> {
pub fn get_windows(&self) -> &HashMap<String, EwwWindowDefinition> {
&self.windows
}
pub fn get_default_vars(&self) -> &HashMap<String, AttrValue> {
&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<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)]
pub struct WidgetDefinition {
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
.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 {

View file

@ -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<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_state: &WidgetState,
eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
element: &config::ElementUse,
) -> Option<gtk::Widget> {
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<String, config::WidgetDefinition>,
eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse,
) -> Option<gtk::Widget> {
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<String, config::WidgetDefinition>,
widget_state: &WidgetState,
eww_state: &mut EwwState,
local_environment: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse,
) -> Option<gtk::Widget> {
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<String, config::WidgetDefinition>,
state: &WidgetState,
eww_state: &mut EwwState,
local_env: &HashMap<String, config::AttrValue>,
widget: &config::WidgetUse,
) -> Option<gtk::Widget> {
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<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(
gtk::Orientation::Horizontal,
Some(&gtk::Adjustment::new(
slider_value,
slider_min,
slider_max,
1.0,
1.0,
1.0,
)),
Some(&gtk::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<String, config::AttrValue>);
#[derive(Default)]
struct EwwState {
on_change_handlers: HashMap<String, Vec<Box<dyn Fn(config::AttrValue) + 'static>>>,
state: HashMap<String, config::AttrValue>,
}
impl WidgetState {
pub fn resolve(&self, value: &config::AttrValue) -> Option<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()));
})
}
}
fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
@ -215,11 +349,3 @@ fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
eprintln!("{}", e);
}
}
// macro_rules! build {
// ($var_name:ident = $value:expr ; $code:block) => {{
// let mut $var_name = $value;
// $code;
// $var_name
// }};
// }