Add lot's of features

This commit is contained in:
elkowar 2020-09-19 22:07:03 +02:00
parent f173ec04e3
commit f7021201ca
4 changed files with 294 additions and 108 deletions

1
Cargo.lock generated
View file

@ -197,6 +197,7 @@ dependencies = [
"anyhow", "anyhow",
"gdk", "gdk",
"gio", "gio",
"glib",
"gtk", "gtk",
"hocon", "hocon",
"regex", "regex",

View file

@ -10,6 +10,7 @@ edition = "2018"
gtk = { version = "0.9", features = [ "v3_16" ] } gtk = { version = "0.9", features = [ "v3_16" ] }
gdk = { version = "", features = ["v3_16"] } gdk = { version = "", features = ["v3_16"] }
gio = { version = "", features = ["v2_44"] } gio = { version = "", features = ["v2_44"] }
glib = { version = "", features = ["v2_44"] }
regex = "1" regex = "1"

View file

@ -1,27 +1,125 @@
use anyhow::*; use anyhow::*;
use glib::{types, value};
use hocon::*; use hocon::*;
use hocon_ext::HoconExt; use hocon_ext::HoconExt;
use std::{collections::HashMap, convert::TryFrom}; use std::collections::HashMap;
use try_match::try_match; use try_match::try_match;
pub mod hocon_ext; pub mod hocon_ext;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, Clone)]
pub struct AttrValue(pub String); pub struct EwwConfig {
widgets: HashMap<String, WidgetDefinition>,
windows: HashMap<String, EwwWindowDefinition>,
}
#[derive(Debug, PartialEq)] impl EwwConfig {
pub fn from_hocon(hocon: &Hocon) -> Result<EwwConfig> {
let data = hocon
.as_hash()
.context("eww config has to be a map structure")?;
Ok(EwwConfig {
widgets: data
.get("widgets")
.context("widgets need to be provided")?
.as_hash()
.context("widgets need to be a map")?
.iter()
.map(|(name, def)| Ok((name.clone(), parse_widget_definition(name.clone(), def)?)))
.collect::<Result<HashMap<String, WidgetDefinition>>>()?,
windows: data
.get("windows")
.context("windows need to be provided")?
.as_hash()
.context("windows need to be a map")?
.iter()
.map(|(name, def)| Ok((name.clone(), EwwWindowDefinition::from_hocon(def)?)))
.collect::<Result<HashMap<String, EwwWindowDefinition>>>()?,
})
}
pub fn widgets(&self) -> &HashMap<String, WidgetDefinition> {
&self.widgets
}
pub fn windows(&self) -> &HashMap<String, EwwWindowDefinition> {
&self.windows
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct EwwWindowDefinition {
pub position: (i32, i32),
pub size: (i32, i32),
pub widget: ElementUse,
}
impl EwwWindowDefinition {
pub fn from_hocon(hocon: &Hocon) -> Result<EwwWindowDefinition> {
let data = hocon
.as_hash()
.context("window config has to be a map structure")?;
let position: Option<_> = try {
(
data.get("pos")?.as_hash()?.get("x")?.as_i64()? as i32,
data.get("pos")?.as_hash()?.get("y")?.as_i64()? as i32,
)
};
let size: Option<_> = try {
(
data.get("size")?.as_hash()?.get("x")?.as_i64()? as i32,
data.get("size")?.as_hash()?.get("y")?.as_i64()? as i32,
)
};
let element =
parse_element_use(data.get("widget").context("no widget use given")?.clone())?;
Ok(EwwWindowDefinition {
position: position.context("pos.x and pos.y need to be set")?,
size: size.context("size.x and size.y need to be set")?,
widget: element,
})
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum AttrValue {
String(String),
Number(f64),
Boolean(bool),
VarRef(String),
}
impl AttrValue {
pub fn as_string(&self) -> Option<&String> {
try_match!(AttrValue::String(x) = self).ok()
}
pub fn as_f64(&self) -> Option<f64> {
try_match!(AttrValue::Number(x) = self => *x).ok()
}
pub fn as_bool(&self) -> Option<bool> {
try_match!(AttrValue::Boolean(x) = self => *x).ok()
}
pub fn as_var_ref(&self) -> Option<&String> {
try_match!(AttrValue::VarRef(x) = self).ok()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct WidgetDefinition { pub struct WidgetDefinition {
pub name: String, pub name: String,
pub structure: ElementUse, pub structure: ElementUse,
pub size: Option<(i32, i32)>,
} }
#[derive(Debug, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum ElementUse { pub enum ElementUse {
Widget(WidgetUse), Widget(WidgetUse),
Text(String), Text(String),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub struct WidgetUse { pub struct WidgetUse {
pub name: String, pub name: String,
pub children: Vec<ElementUse>, pub children: Vec<ElementUse>,
@ -44,18 +142,25 @@ impl From<WidgetUse> for ElementUse {
} }
} }
pub fn parse_widget_definition(text: &str) -> Result<WidgetDefinition> { pub fn parse_widget_definition(name: String, hocon: &Hocon) -> Result<WidgetDefinition> {
let hocon = parse_hocon(text)?;
let definition = hocon let definition = hocon
.as_hash() .as_hash()
.ok_or_else(|| anyhow!("{:?} is not a hash", text))?; .context("widget definition was not a hash")?;
let structure = definition
.get("structure")
.cloned()
.context("structure needs to be set")
.and_then(parse_element_use)?;
Ok(WidgetDefinition { Ok(WidgetDefinition {
name: definition["name"] name,
.as_string() structure,
.context("name was not a string")?, size: try {
structure: parse_element_use(definition.get("structure").unwrap().clone())?, (
definition.get("size_x")?.as_i64()? as i32,
definition.get("size_y")?.as_i64()? as i32,
)
},
}) })
} }
@ -90,12 +195,13 @@ pub fn parse_widget_use(data: HashMap<String, Hocon>) -> Result<WidgetUse> {
.into_iter() .into_iter()
.filter_map(|(key, value)| { .filter_map(|(key, value)| {
Some(( Some((
key.clone(), key.to_lowercase(),
match value { match value {
Hocon::String(s) => AttrValue(s.to_string()), Hocon::String(s) if s.starts_with("$$") => AttrValue::String(s.to_string()),
Hocon::Integer(n) => AttrValue(format!("{}", n)), Hocon::String(s) => AttrValue::String(s.to_string()),
Hocon::Real(n) => AttrValue(format!("{}", n)), Hocon::Integer(n) => AttrValue::Number(*n as f64),
Hocon::Boolean(b) => AttrValue(format!("{}", b)), Hocon::Real(n) => AttrValue::Number(*n as f64),
Hocon::Boolean(b) => AttrValue::Boolean(*b),
_ => return None, _ => return None,
}, },
)) ))
@ -137,31 +243,31 @@ mod test {
); );
} }
#[test] // #[test]
fn test_parse_widget_definition() { // fn test_parse_widget_definition() {
let expected = WidgetDefinition { // let expected = WidgetDefinition {
name: "example_widget".to_string(), // name: "example_widget".to_string(),
structure: ElementUse::Widget(WidgetUse { // structure: ElementUse::Widget(WidgetUse {
name: "layout_horizontal".to_string(), // name: "layout_horizontal".to_string(),
attrs: HashMap::new(), // attrs: HashMap::new(),
children: vec![ // children: vec![
ElementUse::Widget(WidgetUse::new( // ElementUse::Widget(WidgetUse::new(
"text".to_string(), // "text".to_string(),
vec![ElementUse::Text("hi".to_string())], // vec![ElementUse::Text("hi".to_string())],
)), // )),
ElementUse::Widget(WidgetUse::new("text".to_string(), vec![])), // ElementUse::Widget(WidgetUse::new("text".to_string(), vec![])),
], // ],
}), // }),
}; // };
let parsed_hocon = parse_hocon("{ text: { children: \"hi\" } }").unwrap(); // let parsed_hocon = parse_hocon("{ text: { children: \"hi\" } }").unwrap();
assert_eq!( // assert_eq!(
parse_element_use(parsed_hocon).unwrap(), // parse_element_use(parsed_hocon).unwrap(),
ElementUse::Widget(WidgetUse::new( // ElementUse::Widget(WidgetUse::new(
"text".to_string(), // "text".to_string(),
vec![ElementUse::Text("hi".to_string())] // vec![ElementUse::Text("hi".to_string())]
)) // ))
); // );
assert_eq!(parse_widget_definition(EXAMPLE_CONFIG).unwrap(), expected); // assert_eq!(parse_widget_definition(EXAMPLE_CONFIG).unwrap(), expected);
} // }
} }

View file

@ -2,51 +2,95 @@
extern crate gio; extern crate gio;
extern crate gtk; extern crate gtk;
use anyhow::{self, Context, Result}; use anyhow::{self, Result};
use gdk::*; use gdk::*;
use gio::prelude::*; use gio::prelude::*;
use gtk::prelude::*; use gtk::prelude::*;
use gtk::{Adjustment, Application, ApplicationWindow, Button, Scale}; use gtk::{Application, ApplicationWindow};
use regex::Regex; use std::{collections::HashMap, process::Command};
use std::{collections::HashMap, str::FromStr};
pub mod config; pub mod config;
pub mod widgets; pub mod widgets;
const CMD_STRING_PLACEHODLER: &str = "{}";
const EXAMPLE_CONFIG: &str = r#"{
widgets: {
some_widget: {
structure: {
layout_horizontal: {
class: "container",
children: [
"hi",
{ button: { children: "click me you" } }
{ slider: { value: 12, min: 0, max: 50, onchange: "notify-send 'changed' {}" } }
"hu"
]
}
}
}
},
windows: {
main_window: {
pos.x: 200
pos.y: 1550
size.x: 500
size.y: 50
widget: {
some_widget: {}
}
}
},
}"#;
fn main() -> Result<()> { fn main() -> Result<()> {
let application = Application::new(Some("de.elkowar.eww"), Default::default()) let eww_config = config::EwwConfig::from_hocon(&config::parse_hocon(EXAMPLE_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");
application.connect_activate(|app| { let window_def = eww_config.windows()["main_window"].clone();
let window = ApplicationWindow::new(app);
window.set_title("Eww");
window.set_wmclass("noswallow", "noswallow");
window.set_type_hint(gdk::WindowTypeHint::Dock);
window.set_position(gtk::WindowPosition::Center);
window.set_keep_above(true);
let element = config::parse_element_use( application.connect_activate(move |app| {
config::parse_hocon( let app_window = ApplicationWindow::new(app);
r#"{ app_window.set_title("Eww");
layout_horizontal: { app_window.set_wmclass("noswallow", "noswallow");
children: [ app_window.set_type_hint(gdk::WindowTypeHint::Dock);
"hi", app_window.set_position(gtk::WindowPosition::Center);
{ button: { children: "click me you" } } app_window.set_keep_above(true);
{ slider: {} } app_window.set_default_size(window_def.size.0, window_def.size.1);
"hu" app_window.set_visual(
] app_window
} .get_display()
}"#, .get_default_screen()
) .get_rgba_visual()
.unwrap(), .or_else(|| {
) app_window
.unwrap(); .get_display()
.get_default_screen()
.get_system_visual()
})
.as_ref(),
);
app_window.fullscreen();
let widget_state = WidgetState(HashMap::new()); let widget_state = WidgetState(HashMap::new());
window.add(&element_to_gtk_widget(&widget_state, &element).unwrap()); app_window.add(
&element_to_gtk_widget(&eww_config.widgets(), &widget_state, &window_def.widget)
.unwrap(),
);
window.show_all(); app_window.show_all();
let window = app_window.get_window().unwrap();
window.set_override_redirect(true);
window.move_(window_def.position.0, window_def.position.1);
window.show();
window.raise();
}); });
application.run(&[]); application.run(&[]);
@ -54,17 +98,32 @@ fn main() -> Result<()> {
} }
fn element_to_gtk_widget( fn element_to_gtk_widget(
widget_definitions: &HashMap<String, config::WidgetDefinition>,
widget_state: &WidgetState, widget_state: &WidgetState,
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) => widget_use_to_gtk_container(widget_state, &widget) config::ElementUse::Widget(widget) => {
.or(widget_use_to_gtk_widget(widget_state, &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)
}
} }
} }
fn widget_use_to_gtk_container( fn widget_use_to_gtk_container(
widget_definitions: &HashMap<String, config::WidgetDefinition>,
widget_state: &WidgetState, widget_state: &WidgetState,
widget: &config::WidgetUse, widget: &config::WidgetUse,
) -> Option<gtk::Widget> { ) -> Option<gtk::Widget> {
@ -75,33 +134,63 @@ fn widget_use_to_gtk_container(
}; };
for child in &widget.children { for child in &widget.children {
container_widget.add(&element_to_gtk_widget(widget_state, child)?); container_widget.add(&element_to_gtk_widget(
widget_definitions,
widget_state,
child,
)?);
} }
Some(container_widget.upcast()) Some(container_widget.upcast())
} }
fn widget_use_to_gtk_widget( fn widget_use_to_gtk_widget(
widget_state: &WidgetState, widget_definitions: &HashMap<String, config::WidgetDefinition>,
state: &WidgetState,
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 = widget_state.resolve(widget.attrs.get("value")?)?; 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);
gtk::Scale::new( let on_change: Option<String> = try {
state
.resolve(widget.attrs.get("onchange")?)?
.as_string()?
.clone()
};
let scale = gtk::Scale::new(
gtk::Orientation::Horizontal, gtk::Orientation::Horizontal,
Some(&gtk::Adjustment::new( Some(&gtk::Adjustment::new(
slider_value, slider_value,
0.0, slider_min,
100.0, slider_max,
1.0, 1.0,
1.0, 1.0,
1.0, 1.0,
)), )),
) );
.upcast() 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());
});
}
scale.upcast()
} }
name if widget_definitions.contains_key(name) => {
let def = &widget_definitions[name];
element_to_gtk_widget(widget_definitions, state, &def.structure)?
}
_ => return None, _ => return None,
}; };
Some(new_widget) Some(new_widget)
@ -110,31 +199,20 @@ fn widget_use_to_gtk_widget(
struct WidgetState(HashMap<String, config::AttrValue>); struct WidgetState(HashMap<String, config::AttrValue>);
impl WidgetState { impl WidgetState {
pub fn resolve<T>(&self, value: &config::AttrValue) -> Option<String> pub fn resolve(&self, value: &config::AttrValue) -> Option<config::AttrValue> {
where if let config::AttrValue::VarRef(name) = value {
T: FromStr, // TODO REEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
{ self.0.get(name).cloned()
let var_pattern: Regex = Regex::new(r"\$\$\{(.*)\}").unwrap(); } else {
let config::AttrValue(value) = value; Some(value.clone())
}
let mut missing_var: Option<String> = None;
var_pattern.replace_all(value, |caps: &regex::Captures| {
self.lookup_full::<T>(&caps[1]).unwrap_or_else(|| {
missing_var = Some(caps[1].to_string());
"missing".to_string()
})
});
// TODO REEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEe
unimplemented!();
} }
}
pub fn lookup_full<T>(&self, key: &str) -> Option<String> fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
where let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg));
T: FromStr, if let Err(e) = Command::new("bash").arg("-c").arg(cmd).output() {
{ eprintln!("{}", e);
self.resolve::<T>(self.0.get(key)?)
} }
} }