State refactor (#121)
* Add widget_node trait and generic impl * Use WidgetNode stuff in some places * Use WidgetNode stuff in rendering code * Refactor
This commit is contained in:
parent
6251a2bc1d
commit
a18901f187
13 changed files with 403 additions and 287 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -230,6 +230,12 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
|
@ -258,6 +264,7 @@ dependencies = [
|
|||
"bincode",
|
||||
"debug_stub_derive",
|
||||
"derive_more",
|
||||
"dyn-clone",
|
||||
"extend",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
|
|
|
@ -55,6 +55,8 @@ futures-core = "0.3"
|
|||
futures-util = "0.3"
|
||||
tokio-util = "0.6"
|
||||
|
||||
dyn-clone = "1.0"
|
||||
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
inotify = "0.9"
|
||||
|
|
20
src/app.rs
20
src/app.rs
|
@ -3,8 +3,7 @@ use crate::{
|
|||
config::{window_definition::WindowName, AnchorPoint, WindowStacking},
|
||||
display_backend, eww_state,
|
||||
script_var_handler::*,
|
||||
value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName},
|
||||
widgets,
|
||||
value::{Coords, NumWithUnit, PrimitiveValue, VarName},
|
||||
};
|
||||
use anyhow::*;
|
||||
use debug_stub_derive::*;
|
||||
|
@ -67,7 +66,7 @@ pub enum DaemonCommand {
|
|||
PrintWindows(DaemonResponseSender),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EwwWindow {
|
||||
pub name: WindowName,
|
||||
pub definition: config::EwwWindowDefinition,
|
||||
|
@ -105,13 +104,14 @@ impl App {
|
|||
DaemonCommand::NoOp => {}
|
||||
DaemonCommand::UpdateVars(mappings) => {
|
||||
for (var_name, new_value) in mappings {
|
||||
self.update_state(var_name, new_value)?;
|
||||
self.update_state(var_name, new_value);
|
||||
}
|
||||
}
|
||||
DaemonCommand::ReloadConfigAndCss(sender) => {
|
||||
let mut errors = Vec::new();
|
||||
|
||||
let config_result = config::EwwConfig::read_from_file(&self.config_file_path);
|
||||
let config_result =
|
||||
config::RawEwwConfig::read_from_file(&self.config_file_path).and_then(config::EwwConfig::generate);
|
||||
match config_result {
|
||||
Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)),
|
||||
Err(e) => errors.push(e),
|
||||
|
@ -211,7 +211,7 @@ impl App {
|
|||
gtk::main_quit();
|
||||
}
|
||||
|
||||
fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) -> Result<()> {
|
||||
fn update_state(&mut self, fieldname: VarName, value: PrimitiveValue) {
|
||||
self.eww_state.update_variable(fieldname, value)
|
||||
}
|
||||
|
||||
|
@ -247,13 +247,7 @@ impl App {
|
|||
let mut window_def = self.eww_config.get_window(window_name)?.clone();
|
||||
window_def.geometry = window_def.geometry.override_if_given(anchor, pos, size);
|
||||
|
||||
let root_widget = widgets::widget_use_to_gtk_widget(
|
||||
&self.eww_config.get_widgets(),
|
||||
&mut self.eww_state,
|
||||
window_name,
|
||||
&maplit::hashmap! { "window_name".into() => AttrValue::from_primitive(window_name.to_string()) },
|
||||
&window_def.widget,
|
||||
)?;
|
||||
let root_widget = window_def.widget.render(&mut self.eww_state, window_name)?;
|
||||
root_widget.get_style_context().add_class(&window_name.to_string());
|
||||
|
||||
let monitor_geometry = get_monitor_geometry(window_def.screen_number.unwrap_or_else(get_default_monitor_index));
|
||||
|
|
|
@ -35,11 +35,6 @@ impl WidgetDefinition {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// returns all the variables that are referenced in this widget
|
||||
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
|
||||
self.structure.referenced_vars()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
|
@ -106,17 +101,6 @@ impl WidgetUse {
|
|||
self.text_pos = Some(text_pos);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_attr(&self, key: &str) -> Result<&AttrValue> {
|
||||
self.attrs
|
||||
.get(key)
|
||||
.context(format!("attribute '{}' missing from widgetuse of '{}'", key, &self.name))
|
||||
}
|
||||
|
||||
/// returns all the variables that are referenced in this widget
|
||||
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
|
||||
self.attrs.iter().flat_map(|(_, value)| value.var_refs())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
@ -9,22 +9,87 @@ use crate::{
|
|||
use super::{
|
||||
element::WidgetDefinition,
|
||||
xml_ext::{XmlElement, XmlNode},
|
||||
EwwWindowDefinition, ScriptVar, WindowName,
|
||||
EwwWindowDefinition, RawEwwWindowDefinition, ScriptVar, WindowName,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Eww configuration structure.
|
||||
#[derive(Debug, Clone)]
|
||||
/// Structure to hold the eww config
|
||||
pub struct EwwConfig {
|
||||
widgets: HashMap<String, WidgetDefinition>,
|
||||
windows: HashMap<WindowName, EwwWindowDefinition>,
|
||||
initial_variables: HashMap<VarName, PrimitiveValue>,
|
||||
script_vars: HashMap<VarName, ScriptVar>,
|
||||
pub filepath: PathBuf,
|
||||
}
|
||||
|
||||
impl EwwConfig {
|
||||
pub fn merge_includes(mut eww_config: EwwConfig, includes: Vec<EwwConfig>) -> Result<EwwConfig> {
|
||||
pub fn read_from_file<P: AsRef<std::path::Path>>(path: P) -> Result<Self> {
|
||||
Ok(Self::generate(RawEwwConfig::read_from_file(path)?)?)
|
||||
}
|
||||
|
||||
pub fn generate(conf: RawEwwConfig) -> Result<Self> {
|
||||
let RawEwwConfig {
|
||||
windows,
|
||||
initial_variables,
|
||||
script_vars,
|
||||
filepath,
|
||||
widgets,
|
||||
} = conf;
|
||||
Ok(EwwConfig {
|
||||
initial_variables,
|
||||
script_vars,
|
||||
filepath,
|
||||
windows: windows
|
||||
.into_iter()
|
||||
.map(|(name, window)| {
|
||||
Ok((
|
||||
name,
|
||||
EwwWindowDefinition::generate(&widgets, window).context("Failed expand window definition")?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<HashMap<_, _>>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO this is kinda ugly
|
||||
pub fn generate_initial_state(&self) -> Result<HashMap<VarName, PrimitiveValue>> {
|
||||
let mut vars = self
|
||||
.script_vars
|
||||
.iter()
|
||||
.map(|var| Ok((var.0.clone(), var.1.initial_value()?)))
|
||||
.collect::<Result<HashMap<_, _>>>()?;
|
||||
vars.extend(self.initial_variables.clone());
|
||||
Ok(vars)
|
||||
}
|
||||
|
||||
pub fn get_windows(&self) -> &HashMap<WindowName, EwwWindowDefinition> {
|
||||
&self.windows
|
||||
}
|
||||
|
||||
pub fn get_window(&self, name: &WindowName) -> Result<&EwwWindowDefinition> {
|
||||
self.windows
|
||||
.get(name)
|
||||
.with_context(|| format!("No window named '{}' exists", name))
|
||||
}
|
||||
|
||||
pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVar> {
|
||||
self.script_vars
|
||||
.get(name)
|
||||
.with_context(|| format!("No script var named '{}' exists", name))
|
||||
}
|
||||
}
|
||||
|
||||
/// Raw Eww configuration, before expanding widget usages.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RawEwwConfig {
|
||||
widgets: HashMap<String, WidgetDefinition>,
|
||||
windows: HashMap<WindowName, RawEwwWindowDefinition>,
|
||||
initial_variables: HashMap<VarName, PrimitiveValue>,
|
||||
script_vars: HashMap<VarName, ScriptVar>,
|
||||
pub filepath: PathBuf,
|
||||
}
|
||||
|
||||
impl RawEwwConfig {
|
||||
pub fn merge_includes(mut eww_config: RawEwwConfig, includes: Vec<RawEwwConfig>) -> Result<RawEwwConfig> {
|
||||
let config_path = eww_config.filepath.clone();
|
||||
let log_conflict = |what: &str, conflict: &str, included_path: &std::path::PathBuf| {
|
||||
eprintln!(
|
||||
|
@ -37,16 +102,16 @@ impl EwwConfig {
|
|||
};
|
||||
|
||||
for included_config in includes {
|
||||
for conflict in extend_safe(&mut eww_config.widgets, included_config.widgets) {
|
||||
for conflict in util::extend_safe(&mut eww_config.widgets, included_config.widgets) {
|
||||
log_conflict("widget", &conflict, &included_config.filepath)
|
||||
}
|
||||
for conflict in extend_safe(&mut eww_config.windows, included_config.windows) {
|
||||
for conflict in util::extend_safe(&mut eww_config.windows, included_config.windows) {
|
||||
log_conflict("window", &conflict.to_string(), &included_config.filepath)
|
||||
}
|
||||
for conflict in extend_safe(&mut eww_config.script_vars, included_config.script_vars) {
|
||||
for conflict in util::extend_safe(&mut eww_config.script_vars, included_config.script_vars) {
|
||||
log_conflict("script-var", &conflict.to_string(), &included_config.filepath)
|
||||
}
|
||||
for conflict in extend_safe(&mut eww_config.initial_variables, included_config.initial_variables) {
|
||||
for conflict in util::extend_safe(&mut eww_config.initial_variables, included_config.initial_variables) {
|
||||
log_conflict("var", &conflict.to_string(), &included_config.filepath)
|
||||
}
|
||||
}
|
||||
|
@ -60,16 +125,16 @@ impl EwwConfig {
|
|||
let root_node = XmlNode::from(document.root_element());
|
||||
let root_element = root_node.as_element()?;
|
||||
|
||||
let (config, included_paths) = EwwConfig::from_xml_element(root_element.clone(), path.as_ref())
|
||||
let (config, included_paths) = Self::from_xml_element(root_element.clone(), path.as_ref())
|
||||
.with_context(|| format!("Error parsing eww config file {}", path.as_ref().display()))?;
|
||||
|
||||
let parsed_includes = included_paths
|
||||
.into_iter()
|
||||
.map(|included_path| EwwConfig::read_from_file(included_path))
|
||||
.map(|included_path| Self::read_from_file(included_path))
|
||||
.collect::<Result<Vec<_>>>()
|
||||
.with_context(|| format!("Included in {}", path.as_ref().display()))?;
|
||||
|
||||
EwwConfig::merge_includes(config, parsed_includes)
|
||||
Self::merge_includes(config, parsed_includes)
|
||||
.context("Failed to merge included files into parent configuration file")?
|
||||
};
|
||||
result.with_context(|| format!("Failed to load eww config file {}", path.as_ref().display()))
|
||||
|
@ -83,7 +148,7 @@ impl EwwConfig {
|
|||
.child_elements()
|
||||
.map(|child| {
|
||||
crate::ensure_xml_tag_is!(child, "file");
|
||||
Ok(join_path_pretty(path, PathBuf::from(child.attr("path")?)))
|
||||
Ok(util::join_path_pretty(path, PathBuf::from(child.attr("path")?)))
|
||||
})
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
None => Default::default(),
|
||||
|
@ -106,7 +171,7 @@ impl EwwConfig {
|
|||
Some(tag) => tag
|
||||
.child_elements()
|
||||
.map(|child| {
|
||||
let def = EwwWindowDefinition::from_xml_element(&child).with_context(|| {
|
||||
let def = RawEwwWindowDefinition::from_xml_element(&child).with_context(|| {
|
||||
format!("Error parsing window definition at {}:{}", path.display(), &child.text_pos())
|
||||
})?;
|
||||
Ok((def.name.to_owned(), def))
|
||||
|
@ -120,7 +185,7 @@ impl EwwConfig {
|
|||
None => Default::default(),
|
||||
};
|
||||
|
||||
let config = EwwConfig {
|
||||
let config = RawEwwConfig {
|
||||
widgets: definitions,
|
||||
windows,
|
||||
initial_variables,
|
||||
|
@ -129,45 +194,6 @@ impl EwwConfig {
|
|||
};
|
||||
Ok((config, included_paths))
|
||||
}
|
||||
|
||||
// TODO this is kinda ugly
|
||||
pub fn generate_initial_state(&self) -> Result<HashMap<VarName, PrimitiveValue>> {
|
||||
let mut vars = self
|
||||
.script_vars
|
||||
.iter()
|
||||
.map(|var| Ok((var.0.clone(), var.1.initial_value()?)))
|
||||
.collect::<Result<HashMap<_, _>>>()?;
|
||||
vars.extend(self.get_default_vars().clone());
|
||||
Ok(vars)
|
||||
}
|
||||
|
||||
pub fn get_widgets(&self) -> &HashMap<String, WidgetDefinition> {
|
||||
&self.widgets
|
||||
}
|
||||
|
||||
pub fn get_windows(&self) -> &HashMap<WindowName, EwwWindowDefinition> {
|
||||
&self.windows
|
||||
}
|
||||
|
||||
pub fn get_window(&self, name: &WindowName) -> Result<&EwwWindowDefinition> {
|
||||
self.windows
|
||||
.get(name)
|
||||
.with_context(|| format!("No window named '{}' exists", name))
|
||||
}
|
||||
|
||||
pub fn get_default_vars(&self) -> &HashMap<VarName, PrimitiveValue> {
|
||||
&self.initial_variables
|
||||
}
|
||||
|
||||
pub fn get_script_vars(&self) -> Vec<ScriptVar> {
|
||||
self.script_vars.values().cloned().collect()
|
||||
}
|
||||
|
||||
pub fn get_script_var(&self, name: &VarName) -> Result<&ScriptVar> {
|
||||
self.script_vars
|
||||
.get(name)
|
||||
.with_context(|| format!("No script var named '{}' exists", name))
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_variables_block(xml: XmlElement) -> Result<(HashMap<VarName, PrimitiveValue>, HashMap<VarName, ScriptVar>)> {
|
||||
|
@ -176,12 +202,11 @@ fn parse_variables_block(xml: XmlElement) -> Result<(HashMap<VarName, PrimitiveV
|
|||
for node in xml.child_elements() {
|
||||
match node.tag_name() {
|
||||
"var" => {
|
||||
let var_name = VarName(node.attr("name")?.to_owned());
|
||||
let value = node
|
||||
.only_child()
|
||||
.map(|c| c.as_text_or_sourcecode())
|
||||
.unwrap_or_else(|_| String::new());
|
||||
normal_vars.insert(var_name, PrimitiveValue::from_string(value));
|
||||
normal_vars.insert(VarName(node.attr("name")?.to_owned()), PrimitiveValue::from_string(value));
|
||||
}
|
||||
"script-var" => {
|
||||
let script_var = ScriptVar::from_xml_element(node)?;
|
||||
|
@ -193,33 +218,9 @@ fn parse_variables_block(xml: XmlElement) -> Result<(HashMap<VarName, PrimitiveV
|
|||
Ok((normal_vars, script_vars))
|
||||
}
|
||||
|
||||
/// Joins two paths while keeping it somewhat pretty.
|
||||
/// If the second path is absolute, this will just return the second path.
|
||||
/// If it is relative, it will return the second path joined onto the first path, removing any `./` if present.
|
||||
/// TODO this is not yet perfect, as it will still leave ../ and multiple ./ etc,... check for a Path::simplify or something.
|
||||
fn join_path_pretty<P: AsRef<std::path::Path>, P2: AsRef<std::path::Path>>(a: P, b: P2) -> PathBuf {
|
||||
let a = a.as_ref();
|
||||
let b = b.as_ref();
|
||||
if b.is_absolute() {
|
||||
b.to_path_buf()
|
||||
} else {
|
||||
a.parent().unwrap().join(b.strip_prefix("./").unwrap_or(&b))
|
||||
}
|
||||
}
|
||||
|
||||
/// extends a hashmap, returning a list of keys that already where present in the hashmap.
|
||||
fn extend_safe<K: std::cmp::Eq + std::hash::Hash + Clone, V, T: IntoIterator<Item = (K, V)>>(
|
||||
a: &mut HashMap<K, V>,
|
||||
b: T,
|
||||
) -> Vec<K> {
|
||||
b.into_iter()
|
||||
.filter_map(|(k, v)| a.insert(k.clone(), v).map(|_| k.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::config::{EwwConfig, XmlNode};
|
||||
use crate::config::{RawEwwConfig, XmlNode};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[test]
|
||||
|
@ -274,13 +275,13 @@ mod test {
|
|||
|
||||
let document1 = roxmltree::Document::parse(&input1).unwrap();
|
||||
let document2 = roxmltree::Document::parse(input2).unwrap();
|
||||
let config1 = EwwConfig::from_xml_element(XmlNode::from(document1.root_element()).as_element().unwrap().clone(), "")
|
||||
let config1 = RawEwwConfig::from_xml_element(XmlNode::from(document1.root_element()).as_element().unwrap().clone(), "")
|
||||
.unwrap()
|
||||
.0;
|
||||
let config2 = EwwConfig::from_xml_element(XmlNode::from(document2.root_element()).as_element().unwrap().clone(), "")
|
||||
let config2 = RawEwwConfig::from_xml_element(XmlNode::from(document2.root_element()).as_element().unwrap().clone(), "")
|
||||
.unwrap()
|
||||
.0;
|
||||
let base_config = EwwConfig {
|
||||
let base_config = RawEwwConfig {
|
||||
widgets: HashMap::new(),
|
||||
windows: HashMap::new(),
|
||||
initial_variables: HashMap::new(),
|
||||
|
@ -288,7 +289,7 @@ mod test {
|
|||
filepath: "test_path".into(),
|
||||
};
|
||||
|
||||
let merged_config = EwwConfig::merge_includes(base_config, vec![config1, config2]).unwrap();
|
||||
let merged_config = RawEwwConfig::merge_includes(base_config, vec![config1, config2]).unwrap();
|
||||
|
||||
assert_eq!(merged_config.widgets.len(), 2);
|
||||
assert_eq!(merged_config.windows.len(), 2);
|
||||
|
|
|
@ -41,7 +41,9 @@ impl ScriptVar {
|
|||
|
||||
pub fn initial_value(&self) -> Result<PrimitiveValue> {
|
||||
match self {
|
||||
ScriptVar::Poll(x) => Ok(run_command(&x.command)?),
|
||||
ScriptVar::Poll(x) => {
|
||||
run_command(&x.command).with_context(|| format!("Failed to compute initial value for {}", &self.name()))
|
||||
}
|
||||
ScriptVar::Tail(_) => Ok(PrimitiveValue::from_string(String::new())),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,28 +1,42 @@
|
|||
use crate::{ensure_xml_tag_is, value::NumWithUnit};
|
||||
use crate::{ensure_xml_tag_is, value::NumWithUnit, widgets::widget_node};
|
||||
use anyhow::*;
|
||||
use derive_more::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smart_default::SmartDefault;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault)]
|
||||
pub enum Side {
|
||||
#[default]
|
||||
Top,
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
pub struct StrutDefinition {
|
||||
pub side: Side,
|
||||
pub dist: NumWithUnit,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
/// Full window-definition containing the fully expanded widget tree.
|
||||
/// **Use this** rather than `[RawEwwWindowDefinition]`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EwwWindowDefinition {
|
||||
pub name: WindowName,
|
||||
pub geometry: EwwWindowGeometry,
|
||||
pub stacking: WindowStacking,
|
||||
pub screen_number: Option<i32>,
|
||||
pub widget: Box<dyn widget_node::WidgetNode>,
|
||||
pub struts: StrutDefinition,
|
||||
pub focusable: bool,
|
||||
}
|
||||
|
||||
impl EwwWindowDefinition {
|
||||
pub fn generate(defs: &HashMap<String, WidgetDefinition>, window: RawEwwWindowDefinition) -> Result<Self> {
|
||||
Ok(EwwWindowDefinition {
|
||||
name: window.name,
|
||||
geometry: window.geometry,
|
||||
stacking: window.stacking,
|
||||
screen_number: window.screen_number,
|
||||
widget: widget_node::generate_generic_widget_node(defs, &HashMap::new(), window.widget)?,
|
||||
struts: window.struts,
|
||||
focusable: window.focusable,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Window-definition storing the raw WidgetUse, as received directly from parsing.
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct RawEwwWindowDefinition {
|
||||
pub name: WindowName,
|
||||
pub geometry: EwwWindowGeometry,
|
||||
pub stacking: WindowStacking,
|
||||
|
@ -32,7 +46,7 @@ pub struct EwwWindowDefinition {
|
|||
pub focusable: bool,
|
||||
}
|
||||
|
||||
impl EwwWindowDefinition {
|
||||
impl RawEwwWindowDefinition {
|
||||
pub fn from_xml_element(xml: &XmlElement) -> Result<Self> {
|
||||
ensure_xml_tag_is!(xml, "window");
|
||||
let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default();
|
||||
|
@ -44,11 +58,11 @@ impl EwwWindowDefinition {
|
|||
let struts: Option<StrutDefinition> = xml
|
||||
.child("reserve")
|
||||
.ok()
|
||||
.map(parse_strut_definition)
|
||||
.map(StrutDefinition::from_xml_element)
|
||||
.transpose()
|
||||
.context("Failed to parse <reserve>")?;
|
||||
|
||||
Ok(EwwWindowDefinition {
|
||||
Ok(RawEwwWindowDefinition {
|
||||
name: WindowName(xml.attr("name")?.to_owned()),
|
||||
geometry: match xml.child("geometry") {
|
||||
Ok(node) => EwwWindowGeometry::from_xml_element(node)?,
|
||||
|
@ -61,21 +75,20 @@ impl EwwWindowDefinition {
|
|||
struts: struts.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// returns all the variables that are referenced in this window
|
||||
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
|
||||
self.widget.referenced_vars()
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_strut_definition(xml: XmlElement) -> Result<StrutDefinition> {
|
||||
Ok(StrutDefinition {
|
||||
side: parse_side(xml.attr("side")?)?,
|
||||
dist: xml.attr("distance")?.parse()?,
|
||||
})
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault)]
|
||||
pub enum Side {
|
||||
#[default]
|
||||
Top,
|
||||
Left,
|
||||
Right,
|
||||
Bottom,
|
||||
}
|
||||
impl std::str::FromStr for Side {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn parse_side(s: &str) -> Result<Side> {
|
||||
fn from_str(s: &str) -> Result<Side> {
|
||||
match s {
|
||||
"l" | "left" => Ok(Side::Left),
|
||||
"r" | "right" => Ok(Side::Right),
|
||||
|
@ -86,6 +99,22 @@ fn parse_side(s: &str) -> Result<Side> {
|
|||
s
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||
pub struct StrutDefinition {
|
||||
pub side: Side,
|
||||
pub dist: NumWithUnit,
|
||||
}
|
||||
|
||||
impl StrutDefinition {
|
||||
pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
|
||||
Ok(StrutDefinition {
|
||||
side: xml.attr("side")?.parse()?,
|
||||
dist: xml.attr("distance")?.parse()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, derive_more::Display, SmartDefault)]
|
||||
|
|
|
@ -7,11 +7,11 @@ use std::{collections::HashMap, sync::Arc};
|
|||
|
||||
use crate::value::{AttrValue, PrimitiveValue};
|
||||
|
||||
/// Handler that get's executed to apply the necessary parts of the eww state to
|
||||
/// Handler that gets executed to apply the necessary parts of the eww state to
|
||||
/// a gtk widget. These are created and initialized in EwwState::resolve.
|
||||
pub struct StateChangeHandler {
|
||||
func: Box<dyn Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static>,
|
||||
unresolved_values: Vec<(AttrName, AttrValue)>,
|
||||
unresolved_values: HashMap<AttrName, AttrValue>,
|
||||
}
|
||||
|
||||
impl StateChangeHandler {
|
||||
|
@ -24,19 +24,16 @@ impl StateChangeHandler {
|
|||
fn run_with_state(&self, state: &HashMap<VarName, PrimitiveValue>) {
|
||||
let resolved_attrs = self
|
||||
.unresolved_values
|
||||
.iter()
|
||||
.cloned()
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(attr_name, value)| Ok((attr_name, value.resolve_fully(state)?)))
|
||||
.collect::<Result<_>>();
|
||||
|
||||
match resolved_attrs {
|
||||
Ok(resolved_attrs) => {
|
||||
let result: Result<_> = (self.func)(resolved_attrs);
|
||||
crate::print_result_err!("while updating UI based after state change", &result);
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("Error while resolving attributes: {:?}", err);
|
||||
crate::print_result_err!("while updating UI based after state change", &(self.func)(resolved_attrs))
|
||||
}
|
||||
Err(err) => eprintln!("Error while resolving attributes: {:?}", err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -100,73 +97,56 @@ impl EwwState {
|
|||
|
||||
/// Update the value of a variable, running all registered
|
||||
/// [StateChangeHandler]s.
|
||||
pub fn update_variable(&mut self, key: VarName, value: PrimitiveValue) -> Result<()> {
|
||||
pub fn update_variable(&mut self, key: VarName, value: PrimitiveValue) {
|
||||
self.variables_state.insert(key.clone(), value);
|
||||
|
||||
let handlers = self
|
||||
.windows
|
||||
// run all of the handlers
|
||||
self.windows
|
||||
.values()
|
||||
.filter_map(|window_state| window_state.state_change_handlers.get(&key))
|
||||
.flatten();
|
||||
|
||||
for handler in handlers {
|
||||
handler.run_with_state(&self.variables_state)
|
||||
}
|
||||
Ok(())
|
||||
.flatten()
|
||||
.for_each(|handler| handler.run_with_state(&self.variables_state));
|
||||
}
|
||||
|
||||
/// resolves a value if possible, using the current eww_state
|
||||
/// Expects there to be at max one level of nesting var_refs from local-env.
|
||||
/// This means that no elements in the local_env may be var-refs into the
|
||||
/// local_env again, but only into the global state.
|
||||
pub fn resolve_once<'a>(
|
||||
&'a self,
|
||||
local_env: &'a HashMap<VarName, AttrValue>,
|
||||
value: &'a AttrValue,
|
||||
) -> Result<PrimitiveValue> {
|
||||
/// Look up a single variable in the eww state, returning an `Err` when the value is not found.
|
||||
pub fn lookup(&self, var_name: &VarName) -> Result<&PrimitiveValue> {
|
||||
self.variables_state
|
||||
.get(var_name)
|
||||
.with_context(|| format!("Unknown variable '{}' referenced", var_name))
|
||||
}
|
||||
|
||||
/// resolves a value if possible, using the current eww_state.
|
||||
pub fn resolve_once<'a>(&'a self, value: &'a AttrValue) -> Result<PrimitiveValue> {
|
||||
value
|
||||
.iter()
|
||||
.map(|element| match element {
|
||||
AttrValueElement::Primitive(primitive) => Ok(primitive.clone()),
|
||||
AttrValueElement::VarRef(var_name) => self
|
||||
.variables_state
|
||||
.get(var_name)
|
||||
.cloned()
|
||||
.or_else(|| local_env.get(var_name).and_then(|x| self.resolve_once(local_env, x).ok()))
|
||||
.with_context(|| format!("Unknown variable '{}' referenced", var_name)),
|
||||
AttrValueElement::VarRef(var_name) => self.lookup(var_name).cloned(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Resolve takes a function that applies a set of fully resolved attribute
|
||||
/// values to it's gtk widget. Expects there to be at max one level of
|
||||
/// nesting var_refs from local-env. This means that no elements in the
|
||||
/// local_env may be var-refs into the local_env again, but only into the
|
||||
/// global state.
|
||||
/// values to it's gtk widget.
|
||||
pub fn resolve<F: Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static + Clone>(
|
||||
&mut self,
|
||||
window_name: &WindowName,
|
||||
local_env: &HashMap<VarName, AttrValue>,
|
||||
required_attributes: HashMap<AttrName, AttrValue>,
|
||||
set_value: F,
|
||||
) {
|
||||
let handler = StateChangeHandler {
|
||||
func: Box::new(set_value),
|
||||
unresolved_values: required_attributes
|
||||
.into_iter()
|
||||
.map(|(attr_name, attr_value)| (attr_name, attr_value.resolve_one_level(local_env)))
|
||||
.collect(),
|
||||
unresolved_values: required_attributes,
|
||||
};
|
||||
|
||||
handler.run_with_state(&self.variables_state);
|
||||
|
||||
// only store the handler if at least one variable is being used
|
||||
if handler.used_variables().next().is_some() {
|
||||
let window_state = self
|
||||
.windows
|
||||
self.windows
|
||||
.entry(window_name.clone())
|
||||
.or_insert_with(EwwWindowState::default);
|
||||
window_state.put_handler(handler);
|
||||
.or_insert_with(EwwWindowState::default)
|
||||
.put_handler(handler);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
24
src/util.rs
24
src/util.rs
|
@ -50,6 +50,30 @@ macro_rules! loop_select {
|
|||
}
|
||||
}
|
||||
|
||||
/// Joins two paths while keeping it somewhat pretty.
|
||||
/// If the second path is absolute, this will just return the second path.
|
||||
/// If it is relative, it will return the second path joined onto the first path, removing any `./` if present.
|
||||
/// TODO this is not yet perfect, as it will still leave ../ and multiple ./ etc,... check for a Path::simplify or something.
|
||||
pub fn join_path_pretty<P: AsRef<std::path::Path>, P2: AsRef<std::path::Path>>(a: P, b: P2) -> std::path::PathBuf {
|
||||
let a = a.as_ref();
|
||||
let b = b.as_ref();
|
||||
if b.is_absolute() {
|
||||
b.to_path_buf()
|
||||
} else {
|
||||
a.parent().unwrap().join(b.strip_prefix("./").unwrap_or(&b))
|
||||
}
|
||||
}
|
||||
|
||||
/// extends a hashmap, returning a list of keys that already where present in the hashmap.
|
||||
pub fn extend_safe<K: std::cmp::Eq + std::hash::Hash + Clone, V, T: IntoIterator<Item = (K, V)>>(
|
||||
a: &mut std::collections::HashMap<K, V>,
|
||||
b: T,
|
||||
) -> Vec<K> {
|
||||
b.into_iter()
|
||||
.filter_map(|(k, v)| a.insert(k.clone(), v).map(|_| k.clone()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// read an scss file, replace all environment variable references within it and
|
||||
/// then parse it into css.
|
||||
pub fn parse_scss_from_file(path: &Path) -> Result<String> {
|
||||
|
|
|
@ -43,6 +43,9 @@ impl AttrValue {
|
|||
self.0.iter().filter_map(|x| x.as_var_ref())
|
||||
}
|
||||
|
||||
/// resolve partially.
|
||||
/// If a var-ref links to another var-ref, that other var-ref is used.
|
||||
/// If a referenced variable is not found in the given hashmap, returns the var-ref unchanged.
|
||||
pub fn resolve_one_level(self, variables: &HashMap<VarName, AttrValue>) -> AttrValue {
|
||||
self.into_iter()
|
||||
.flat_map(|entry| match entry {
|
||||
|
@ -55,6 +58,9 @@ impl AttrValue {
|
|||
.collect()
|
||||
}
|
||||
|
||||
/// resolve fully.
|
||||
/// As the variables here have to be primitive values,
|
||||
/// this enforces that var-refs are not linking to other variables.
|
||||
pub fn resolve_fully(self, variables: &HashMap<VarName, PrimitiveValue>) -> Result<PrimitiveValue> {
|
||||
self.into_iter()
|
||||
.map(|element| match element {
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
use crate::{
|
||||
config::{element, window_definition::WindowName},
|
||||
eww_state::*,
|
||||
print_result_err,
|
||||
value::{AttrName, AttrValue, VarName},
|
||||
};
|
||||
use crate::{config::window_definition::WindowName, eww_state::*, print_result_err, value::AttrName};
|
||||
use anyhow::*;
|
||||
use gtk::prelude::*;
|
||||
use itertools::Itertools;
|
||||
|
||||
use std::{collections::HashMap, process::Command};
|
||||
use std::process::Command;
|
||||
use widget_definitions::*;
|
||||
|
||||
pub mod widget_definitions;
|
||||
pub mod widget_node;
|
||||
|
||||
const CMD_STRING_PLACEHODLER: &str = "{}";
|
||||
|
||||
|
@ -27,71 +23,11 @@ pub(self) fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
|
|||
print_result_err!(format!("executing command {}", &cmd), command_result);
|
||||
}
|
||||
|
||||
struct BuilderArgs<'a, 'b, 'c, 'd, 'e> {
|
||||
struct BuilderArgs<'a, 'b, 'c, 'd> {
|
||||
eww_state: &'a mut EwwState,
|
||||
local_env: &'b HashMap<VarName, AttrValue>,
|
||||
widget: &'c element::WidgetUse,
|
||||
widget: &'b widget_node::Generic,
|
||||
unhandled_attrs: Vec<&'c AttrName>,
|
||||
window_name: &'d WindowName,
|
||||
widget_definitions: &'e HashMap<String, element::WidgetDefinition>,
|
||||
}
|
||||
|
||||
/// Generate a [gtk::Widget] from a [element::WidgetUse].
|
||||
/// The widget_use may be using a builtin widget, or a custom
|
||||
/// [element::WidgetDefinition].
|
||||
///
|
||||
/// Also registers all the necessary state-change handlers in the eww_state.
|
||||
///
|
||||
/// This may return `Err` in case there was an actual error while parsing or
|
||||
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
|
||||
/// widget name.
|
||||
pub fn widget_use_to_gtk_widget(
|
||||
widget_definitions: &HashMap<String, element::WidgetDefinition>,
|
||||
eww_state: &mut EwwState,
|
||||
window_name: &WindowName,
|
||||
local_env: &HashMap<VarName, AttrValue>,
|
||||
widget: &element::WidgetUse,
|
||||
) -> Result<gtk::Widget> {
|
||||
let builtin_gtk_widget = build_builtin_gtk_widget(widget_definitions, eww_state, window_name, local_env, widget)?;
|
||||
|
||||
let gtk_widget = if let Some(builtin_gtk_widget) = builtin_gtk_widget {
|
||||
builtin_gtk_widget
|
||||
} else if let Some(def) = widget_definitions.get(widget.name.as_str()) {
|
||||
// let mut local_env = local_env.clone();
|
||||
|
||||
// the attributes that are set on the widget need to be resolved as far as
|
||||
// possible. If an attribute is a variable reference, it must either reference a
|
||||
// variable in the current local_env, or in the global state. As we are building
|
||||
// widgets from the outer most to the most nested, we can resolve attributes at
|
||||
// every step. This way, any definition that is affected by changes in the
|
||||
// eww_state will be directly linked to the eww_state's value. Example:
|
||||
// foo="{{in_eww_state}}" => attr_in_child="{{foo}}" =>
|
||||
// attr_in_nested_child="{{attr_in_child}}" will be resolved step by step. This
|
||||
// code will first resolve attr_in_child to directly be
|
||||
// attr_in_child={{in_eww_state}}. then, in the widget_use_to_gtk_widget call of
|
||||
// that child element, attr_in_nested_child will again be resolved to point to
|
||||
// the value of attr_in_child, and thus: attr_in_nested_child="{{in_eww_state}}"
|
||||
let resolved_widget_attr_env = widget
|
||||
.attrs
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|(attr_name, attr_value)| (VarName(attr_name.0), attr_value.resolve_one_level(local_env)))
|
||||
.collect();
|
||||
|
||||
let custom_widget = widget_use_to_gtk_widget(
|
||||
widget_definitions,
|
||||
eww_state,
|
||||
window_name,
|
||||
&resolved_widget_attr_env,
|
||||
&def.structure,
|
||||
)?;
|
||||
custom_widget.get_style_context().add_class(widget.name.as_str());
|
||||
custom_widget
|
||||
} else {
|
||||
bail!("unknown widget: '{}'", &widget.name);
|
||||
};
|
||||
|
||||
Ok(gtk_widget)
|
||||
}
|
||||
|
||||
/// build a [`gtk::Widget`] out of a [`element::WidgetUse`] that uses a
|
||||
|
@ -104,19 +40,15 @@ pub fn widget_use_to_gtk_widget(
|
|||
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
|
||||
/// widget name.
|
||||
fn build_builtin_gtk_widget(
|
||||
widget_definitions: &HashMap<String, element::WidgetDefinition>,
|
||||
eww_state: &mut EwwState,
|
||||
window_name: &WindowName,
|
||||
local_env: &HashMap<VarName, AttrValue>,
|
||||
widget: &element::WidgetUse,
|
||||
widget: &widget_node::Generic,
|
||||
) -> Result<Option<gtk::Widget>> {
|
||||
let mut bargs = BuilderArgs {
|
||||
eww_state,
|
||||
local_env,
|
||||
widget,
|
||||
window_name,
|
||||
unhandled_attrs: widget.attrs.keys().collect(),
|
||||
widget_definitions,
|
||||
};
|
||||
let gtk_widget = match widget_to_gtk_widget(&mut bargs) {
|
||||
Ok(Some(gtk_widget)) => gtk_widget,
|
||||
|
@ -136,8 +68,7 @@ fn build_builtin_gtk_widget(
|
|||
if let Some(gtk_widget) = gtk_widget.dynamic_cast_ref::<gtk::Container>() {
|
||||
resolve_container_attrs(&mut bargs, >k_widget);
|
||||
for child in &widget.children {
|
||||
let child_widget = widget_use_to_gtk_widget(widget_definitions, bargs.eww_state, window_name, local_env, child);
|
||||
let child_widget = child_widget.with_context(|| {
|
||||
let child_widget = child.render(bargs.eww_state, window_name).with_context(|| {
|
||||
format!(
|
||||
"{}error while building child '{:#?}' of '{}'",
|
||||
widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(),
|
||||
|
@ -193,7 +124,6 @@ macro_rules! resolve_block {
|
|||
if let Ok(attr_map) = attr_map {
|
||||
$args.eww_state.resolve(
|
||||
$args.window_name,
|
||||
$args.local_env,
|
||||
attr_map,
|
||||
::glib::clone!(@strong $gtk_widget => move |attrs| {
|
||||
$(
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
use super::{run_command, BuilderArgs};
|
||||
use crate::{config, eww_state, resolve_block, value::AttrValue};
|
||||
use crate::{config, eww_state, resolve_block, value::AttrValue, widgets::widget_node};
|
||||
use anyhow::*;
|
||||
use gtk::{prelude::*, ImageExt};
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
// TODO figure out how to
|
||||
// TODO https://developer.gnome.org/gtk3/stable/GtkFixed.html
|
||||
|
@ -40,7 +40,7 @@ pub(super) fn resolve_widget_attrs(bargs: &mut BuilderArgs, gtk_widget: >k::Wi
|
|||
if let Ok(visible) = bargs
|
||||
.widget
|
||||
.get_attr("visible")
|
||||
.and_then(|v| bargs.eww_state.resolve_once(bargs.local_env, v)?.as_bool())
|
||||
.and_then(|v| bargs.eww_state.resolve_once(v)?.as_bool())
|
||||
{
|
||||
connect_first_map(gtk_widget, move |w| {
|
||||
if visible {
|
||||
|
@ -410,7 +410,6 @@ fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
|
|||
|
||||
// TODO these clones here are dumdum
|
||||
let window_name = bargs.window_name.clone();
|
||||
let widget_definitions = bargs.widget_definitions.clone();
|
||||
resolve_block!(bargs, gtk_widget, {
|
||||
// @prop content - inline Eww XML that will be rendered as a widget.
|
||||
prop(content: as_string) {
|
||||
|
@ -418,13 +417,9 @@ fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
|
|||
if !content.is_empty() {
|
||||
let document = roxmltree::Document::parse(&content).map_err(|e| anyhow!("Failed to parse eww xml literal: {:?}", e))?;
|
||||
let content_widget_use = config::element::WidgetUse::from_xml_node(document.root_element().into())?;
|
||||
let child_widget = super::widget_use_to_gtk_widget(
|
||||
&widget_definitions,
|
||||
&mut eww_state::EwwState::default(),
|
||||
&window_name,
|
||||
&std::collections::HashMap::new(),
|
||||
&content_widget_use,
|
||||
)?;
|
||||
|
||||
let widget_node = &*widget_node::generate_generic_widget_node(&HashMap::new(), &HashMap::new(), content_widget_use)?;
|
||||
let child_widget = widget_node.render( &mut eww_state::EwwState::default(), &window_name)?;
|
||||
gtk_widget.add(&child_widget);
|
||||
child_widget.show();
|
||||
}
|
||||
|
|
162
src/widgets/widget_node.rs
Normal file
162
src/widgets/widget_node.rs
Normal file
|
@ -0,0 +1,162 @@
|
|||
use crate::{
|
||||
config::{
|
||||
element::{WidgetDefinition, WidgetUse},
|
||||
WindowName,
|
||||
},
|
||||
eww_state::EwwState,
|
||||
value::{AttrName, AttrValue, VarName},
|
||||
};
|
||||
use anyhow::*;
|
||||
use dyn_clone;
|
||||
use std::collections::HashMap;
|
||||
pub trait WidgetNode: std::fmt::Debug + dyn_clone::DynClone + Send + Sync {
|
||||
fn get_name(&self) -> &str;
|
||||
fn get_text_pos(&self) -> Option<&roxmltree::TextPos>;
|
||||
|
||||
/// Generate a [gtk::Widget] from a [element::WidgetUse].
|
||||
///
|
||||
/// Also registers all the necessary state-change handlers in the eww_state.
|
||||
///
|
||||
/// This may return `Err` in case there was an actual error while parsing or
|
||||
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
|
||||
/// widget name.
|
||||
fn render(&self, eww_state: &mut EwwState, window_name: &WindowName) -> Result<gtk::Widget>;
|
||||
}
|
||||
|
||||
dyn_clone::clone_trait_object!(WidgetNode);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserDefined {
|
||||
name: String,
|
||||
text_pos: Option<roxmltree::TextPos>,
|
||||
content: Box<dyn WidgetNode>,
|
||||
}
|
||||
|
||||
impl WidgetNode for UserDefined {
|
||||
fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn get_text_pos(&self) -> Option<&roxmltree::TextPos> {
|
||||
self.text_pos.as_ref()
|
||||
}
|
||||
|
||||
fn render(&self, eww_state: &mut EwwState, window_name: &WindowName) -> Result<gtk::Widget> {
|
||||
self.content.render(eww_state, window_name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Generic {
|
||||
pub name: String,
|
||||
pub text_pos: Option<roxmltree::TextPos>,
|
||||
pub children: Vec<Box<dyn WidgetNode>>,
|
||||
pub attrs: HashMap<AttrName, AttrValue>,
|
||||
}
|
||||
|
||||
impl Generic {
|
||||
pub fn get_attr(&self, key: &str) -> Result<&AttrValue> {
|
||||
self.attrs
|
||||
.get(key)
|
||||
.context(format!("attribute '{}' missing from use of '{}'", key, &self.name))
|
||||
}
|
||||
|
||||
/// returns all the variables that are referenced in this widget
|
||||
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
|
||||
self.attrs.iter().flat_map(|(_, value)| value.var_refs())
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetNode for Generic {
|
||||
fn get_name(&self) -> &str {
|
||||
&self.name
|
||||
}
|
||||
|
||||
fn get_text_pos(&self) -> Option<&roxmltree::TextPos> {
|
||||
self.text_pos.as_ref()
|
||||
}
|
||||
|
||||
fn render(&self, eww_state: &mut EwwState, window_name: &WindowName) -> Result<gtk::Widget> {
|
||||
Ok(crate::widgets::build_builtin_gtk_widget(eww_state, window_name, &self)?
|
||||
.with_context(|| format!("Unknown widget '{}'", self.get_name()))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_generic_widget_node(
|
||||
defs: &HashMap<String, WidgetDefinition>,
|
||||
local_env: &HashMap<VarName, AttrValue>,
|
||||
w: WidgetUse,
|
||||
) -> Result<Box<dyn WidgetNode>> {
|
||||
if let Some(def) = defs.get(&w.name) {
|
||||
ensure!(w.children.is_empty(), "User-defined widgets cannot be given children.");
|
||||
|
||||
let new_local_env = w
|
||||
.attrs
|
||||
.into_iter()
|
||||
.map(|(name, value)| (VarName(name.0), value.resolve_one_level(local_env)))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let content = generate_generic_widget_node(defs, &new_local_env, def.structure.clone())?;
|
||||
Ok(Box::new(UserDefined {
|
||||
name: w.name,
|
||||
text_pos: w.text_pos,
|
||||
content,
|
||||
}))
|
||||
} else {
|
||||
Ok(Box::new(Generic {
|
||||
name: w.name,
|
||||
text_pos: w.text_pos,
|
||||
attrs: w
|
||||
.attrs
|
||||
.into_iter()
|
||||
.map(|(name, value)| (name, value.resolve_one_level(local_env)))
|
||||
.collect(),
|
||||
children: w
|
||||
.children
|
||||
.into_iter()
|
||||
.map(|child| generate_generic_widget_node(defs, local_env, child))
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::config::xml_ext::*;
|
||||
use maplit::hashmap;
|
||||
use pretty_assertions::assert_eq;
|
||||
#[test]
|
||||
fn test_generic_generate() {
|
||||
let w_def1 = {
|
||||
let input = r#"<def name="foo"><box><box>{{nested1}}{{raw1}}</box></box></def>"#;
|
||||
let document = roxmltree::Document::parse(input).unwrap();
|
||||
let xml = XmlNode::from(document.root_element().clone());
|
||||
WidgetDefinition::from_xml_element(&xml.as_element().unwrap()).unwrap()
|
||||
};
|
||||
let w_def2 = {
|
||||
let input = r#"<def name="bar"><box><foo nested1="{{nested2}}" raw1="raw value"/></box></def>"#;
|
||||
let document = roxmltree::Document::parse(input).unwrap();
|
||||
let xml = XmlNode::from(document.root_element().clone());
|
||||
WidgetDefinition::from_xml_element(&xml.as_element().unwrap()).unwrap()
|
||||
};
|
||||
let w_use = {
|
||||
let input = r#"<bar nested2="{{in_root}}"/>"#;
|
||||
let document = roxmltree::Document::parse(input).unwrap();
|
||||
let xml = XmlNode::from(document.root_element().clone());
|
||||
WidgetUse::from_xml_node(xml).unwrap()
|
||||
};
|
||||
|
||||
let generic = generate_generic_widget_node(
|
||||
&hashmap! { "foo".to_string() => w_def1, "bar".to_string() => w_def2 },
|
||||
&HashMap::new(),
|
||||
w_use,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// TODO actually implement this test ._.
|
||||
|
||||
dbg!(&generic);
|
||||
// panic!("REEEEEEEEEE")
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue