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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dyn-clone"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee2626afccd7561a06cf1367e2950c4718ea04565e20fb5029b6c7d8ad09abcf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "either"
|
name = "either"
|
||||||
version = "1.6.1"
|
version = "1.6.1"
|
||||||
|
@ -258,6 +264,7 @@ dependencies = [
|
||||||
"bincode",
|
"bincode",
|
||||||
"debug_stub_derive",
|
"debug_stub_derive",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
|
"dyn-clone",
|
||||||
"extend",
|
"extend",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
|
|
@ -55,6 +55,8 @@ futures-core = "0.3"
|
||||||
futures-util = "0.3"
|
futures-util = "0.3"
|
||||||
tokio-util = "0.6"
|
tokio-util = "0.6"
|
||||||
|
|
||||||
|
dyn-clone = "1.0"
|
||||||
|
|
||||||
|
|
||||||
[target.'cfg(target_os="linux")'.dependencies]
|
[target.'cfg(target_os="linux")'.dependencies]
|
||||||
inotify = "0.9"
|
inotify = "0.9"
|
||||||
|
|
20
src/app.rs
20
src/app.rs
|
@ -3,8 +3,7 @@ use crate::{
|
||||||
config::{window_definition::WindowName, AnchorPoint, WindowStacking},
|
config::{window_definition::WindowName, AnchorPoint, WindowStacking},
|
||||||
display_backend, eww_state,
|
display_backend, eww_state,
|
||||||
script_var_handler::*,
|
script_var_handler::*,
|
||||||
value::{AttrValue, Coords, NumWithUnit, PrimitiveValue, VarName},
|
value::{Coords, NumWithUnit, PrimitiveValue, VarName},
|
||||||
widgets,
|
|
||||||
};
|
};
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use debug_stub_derive::*;
|
use debug_stub_derive::*;
|
||||||
|
@ -67,7 +66,7 @@ pub enum DaemonCommand {
|
||||||
PrintWindows(DaemonResponseSender),
|
PrintWindows(DaemonResponseSender),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct EwwWindow {
|
pub struct EwwWindow {
|
||||||
pub name: WindowName,
|
pub name: WindowName,
|
||||||
pub definition: config::EwwWindowDefinition,
|
pub definition: config::EwwWindowDefinition,
|
||||||
|
@ -105,13 +104,14 @@ impl App {
|
||||||
DaemonCommand::NoOp => {}
|
DaemonCommand::NoOp => {}
|
||||||
DaemonCommand::UpdateVars(mappings) => {
|
DaemonCommand::UpdateVars(mappings) => {
|
||||||
for (var_name, new_value) in 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) => {
|
DaemonCommand::ReloadConfigAndCss(sender) => {
|
||||||
let mut errors = Vec::new();
|
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 {
|
match config_result {
|
||||||
Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)),
|
Ok(new_config) => self.handle_command(DaemonCommand::UpdateConfig(new_config)),
|
||||||
Err(e) => errors.push(e),
|
Err(e) => errors.push(e),
|
||||||
|
@ -211,7 +211,7 @@ impl App {
|
||||||
gtk::main_quit();
|
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)
|
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();
|
let mut window_def = self.eww_config.get_window(window_name)?.clone();
|
||||||
window_def.geometry = window_def.geometry.override_if_given(anchor, pos, size);
|
window_def.geometry = window_def.geometry.override_if_given(anchor, pos, size);
|
||||||
|
|
||||||
let root_widget = widgets::widget_use_to_gtk_widget(
|
let root_widget = window_def.widget.render(&mut self.eww_state, window_name)?;
|
||||||
&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,
|
|
||||||
)?;
|
|
||||||
root_widget.get_style_context().add_class(&window_name.to_string());
|
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));
|
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)]
|
#[derive(Debug, Clone, Default)]
|
||||||
|
@ -106,17 +101,6 @@ impl WidgetUse {
|
||||||
self.text_pos = Some(text_pos);
|
self.text_pos = Some(text_pos);
|
||||||
self
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -9,22 +9,87 @@ use crate::{
|
||||||
use super::{
|
use super::{
|
||||||
element::WidgetDefinition,
|
element::WidgetDefinition,
|
||||||
xml_ext::{XmlElement, XmlNode},
|
xml_ext::{XmlElement, XmlNode},
|
||||||
EwwWindowDefinition, ScriptVar, WindowName,
|
EwwWindowDefinition, RawEwwWindowDefinition, ScriptVar, WindowName,
|
||||||
};
|
};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
/// Eww configuration structure.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
/// Structure to hold the eww config
|
|
||||||
pub struct EwwConfig {
|
pub struct EwwConfig {
|
||||||
widgets: HashMap<String, WidgetDefinition>,
|
|
||||||
windows: HashMap<WindowName, EwwWindowDefinition>,
|
windows: HashMap<WindowName, EwwWindowDefinition>,
|
||||||
initial_variables: HashMap<VarName, PrimitiveValue>,
|
initial_variables: HashMap<VarName, PrimitiveValue>,
|
||||||
script_vars: HashMap<VarName, ScriptVar>,
|
script_vars: HashMap<VarName, ScriptVar>,
|
||||||
pub filepath: PathBuf,
|
pub filepath: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EwwConfig {
|
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 config_path = eww_config.filepath.clone();
|
||||||
let log_conflict = |what: &str, conflict: &str, included_path: &std::path::PathBuf| {
|
let log_conflict = |what: &str, conflict: &str, included_path: &std::path::PathBuf| {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
|
@ -37,16 +102,16 @@ impl EwwConfig {
|
||||||
};
|
};
|
||||||
|
|
||||||
for included_config in includes {
|
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)
|
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)
|
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)
|
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)
|
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_node = XmlNode::from(document.root_element());
|
||||||
let root_element = root_node.as_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()))?;
|
.with_context(|| format!("Error parsing eww config file {}", path.as_ref().display()))?;
|
||||||
|
|
||||||
let parsed_includes = included_paths
|
let parsed_includes = included_paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|included_path| EwwConfig::read_from_file(included_path))
|
.map(|included_path| Self::read_from_file(included_path))
|
||||||
.collect::<Result<Vec<_>>>()
|
.collect::<Result<Vec<_>>>()
|
||||||
.with_context(|| format!("Included in {}", path.as_ref().display()))?;
|
.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")?
|
.context("Failed to merge included files into parent configuration file")?
|
||||||
};
|
};
|
||||||
result.with_context(|| format!("Failed to load eww config file {}", path.as_ref().display()))
|
result.with_context(|| format!("Failed to load eww config file {}", path.as_ref().display()))
|
||||||
|
@ -83,7 +148,7 @@ impl EwwConfig {
|
||||||
.child_elements()
|
.child_elements()
|
||||||
.map(|child| {
|
.map(|child| {
|
||||||
crate::ensure_xml_tag_is!(child, "file");
|
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<_>>>()?,
|
.collect::<Result<Vec<_>>>()?,
|
||||||
None => Default::default(),
|
None => Default::default(),
|
||||||
|
@ -106,7 +171,7 @@ impl EwwConfig {
|
||||||
Some(tag) => tag
|
Some(tag) => tag
|
||||||
.child_elements()
|
.child_elements()
|
||||||
.map(|child| {
|
.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())
|
format!("Error parsing window definition at {}:{}", path.display(), &child.text_pos())
|
||||||
})?;
|
})?;
|
||||||
Ok((def.name.to_owned(), def))
|
Ok((def.name.to_owned(), def))
|
||||||
|
@ -120,7 +185,7 @@ impl EwwConfig {
|
||||||
None => Default::default(),
|
None => Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let config = EwwConfig {
|
let config = RawEwwConfig {
|
||||||
widgets: definitions,
|
widgets: definitions,
|
||||||
windows,
|
windows,
|
||||||
initial_variables,
|
initial_variables,
|
||||||
|
@ -129,45 +194,6 @@ impl EwwConfig {
|
||||||
};
|
};
|
||||||
Ok((config, included_paths))
|
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>)> {
|
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() {
|
for node in xml.child_elements() {
|
||||||
match node.tag_name() {
|
match node.tag_name() {
|
||||||
"var" => {
|
"var" => {
|
||||||
let var_name = VarName(node.attr("name")?.to_owned());
|
|
||||||
let value = node
|
let value = node
|
||||||
.only_child()
|
.only_child()
|
||||||
.map(|c| c.as_text_or_sourcecode())
|
.map(|c| c.as_text_or_sourcecode())
|
||||||
.unwrap_or_else(|_| String::new());
|
.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" => {
|
"script-var" => {
|
||||||
let script_var = ScriptVar::from_xml_element(node)?;
|
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))
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::config::{EwwConfig, XmlNode};
|
use crate::config::{RawEwwConfig, XmlNode};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -274,13 +275,13 @@ mod test {
|
||||||
|
|
||||||
let document1 = roxmltree::Document::parse(&input1).unwrap();
|
let document1 = roxmltree::Document::parse(&input1).unwrap();
|
||||||
let document2 = roxmltree::Document::parse(input2).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()
|
.unwrap()
|
||||||
.0;
|
.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()
|
.unwrap()
|
||||||
.0;
|
.0;
|
||||||
let base_config = EwwConfig {
|
let base_config = RawEwwConfig {
|
||||||
widgets: HashMap::new(),
|
widgets: HashMap::new(),
|
||||||
windows: HashMap::new(),
|
windows: HashMap::new(),
|
||||||
initial_variables: HashMap::new(),
|
initial_variables: HashMap::new(),
|
||||||
|
@ -288,7 +289,7 @@ mod test {
|
||||||
filepath: "test_path".into(),
|
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.widgets.len(), 2);
|
||||||
assert_eq!(merged_config.windows.len(), 2);
|
assert_eq!(merged_config.windows.len(), 2);
|
||||||
|
|
|
@ -41,7 +41,9 @@ impl ScriptVar {
|
||||||
|
|
||||||
pub fn initial_value(&self) -> Result<PrimitiveValue> {
|
pub fn initial_value(&self) -> Result<PrimitiveValue> {
|
||||||
match self {
|
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())),
|
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 anyhow::*;
|
||||||
use derive_more::*;
|
use derive_more::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use smart_default::SmartDefault;
|
use smart_default::SmartDefault;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault)]
|
/// Full window-definition containing the fully expanded widget tree.
|
||||||
pub enum Side {
|
/// **Use this** rather than `[RawEwwWindowDefinition]`.
|
||||||
#[default]
|
#[derive(Debug, Clone)]
|
||||||
Top,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Bottom,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
|
||||||
pub struct StrutDefinition {
|
|
||||||
pub side: Side,
|
|
||||||
pub dist: NumWithUnit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
|
||||||
pub struct EwwWindowDefinition {
|
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 name: WindowName,
|
||||||
pub geometry: EwwWindowGeometry,
|
pub geometry: EwwWindowGeometry,
|
||||||
pub stacking: WindowStacking,
|
pub stacking: WindowStacking,
|
||||||
|
@ -32,7 +46,7 @@ pub struct EwwWindowDefinition {
|
||||||
pub focusable: bool,
|
pub focusable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EwwWindowDefinition {
|
impl RawEwwWindowDefinition {
|
||||||
pub fn from_xml_element(xml: &XmlElement) -> Result<Self> {
|
pub fn from_xml_element(xml: &XmlElement) -> Result<Self> {
|
||||||
ensure_xml_tag_is!(xml, "window");
|
ensure_xml_tag_is!(xml, "window");
|
||||||
let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default();
|
let stacking: WindowStacking = xml.parse_optional_attr("stacking")?.unwrap_or_default();
|
||||||
|
@ -44,11 +58,11 @@ impl EwwWindowDefinition {
|
||||||
let struts: Option<StrutDefinition> = xml
|
let struts: Option<StrutDefinition> = xml
|
||||||
.child("reserve")
|
.child("reserve")
|
||||||
.ok()
|
.ok()
|
||||||
.map(parse_strut_definition)
|
.map(StrutDefinition::from_xml_element)
|
||||||
.transpose()
|
.transpose()
|
||||||
.context("Failed to parse <reserve>")?;
|
.context("Failed to parse <reserve>")?;
|
||||||
|
|
||||||
Ok(EwwWindowDefinition {
|
Ok(RawEwwWindowDefinition {
|
||||||
name: WindowName(xml.attr("name")?.to_owned()),
|
name: WindowName(xml.attr("name")?.to_owned()),
|
||||||
geometry: match xml.child("geometry") {
|
geometry: match xml.child("geometry") {
|
||||||
Ok(node) => EwwWindowGeometry::from_xml_element(node)?,
|
Ok(node) => EwwWindowGeometry::from_xml_element(node)?,
|
||||||
|
@ -61,30 +75,45 @@ impl EwwWindowDefinition {
|
||||||
struts: struts.unwrap_or_default(),
|
struts: struts.unwrap_or_default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// returns all the variables that are referenced in this window
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, smart_default::SmartDefault)]
|
||||||
pub fn referenced_vars(&self) -> impl Iterator<Item = &VarName> {
|
pub enum Side {
|
||||||
self.widget.referenced_vars()
|
#[default]
|
||||||
|
Top,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Bottom,
|
||||||
|
}
|
||||||
|
impl std::str::FromStr for Side {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Side> {
|
||||||
|
match s {
|
||||||
|
"l" | "left" => Ok(Side::Left),
|
||||||
|
"r" | "right" => Ok(Side::Right),
|
||||||
|
"t" | "top" => Ok(Side::Top),
|
||||||
|
"b" | "bottom" => Ok(Side::Bottom),
|
||||||
|
_ => Err(anyhow!(
|
||||||
|
"Failed to parse {} as valid side. Must be one of \"left\", \"right\", \"top\", \"bottom\"",
|
||||||
|
s
|
||||||
|
)),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_strut_definition(xml: XmlElement) -> Result<StrutDefinition> {
|
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
|
||||||
Ok(StrutDefinition {
|
pub struct StrutDefinition {
|
||||||
side: parse_side(xml.attr("side")?)?,
|
pub side: Side,
|
||||||
dist: xml.attr("distance")?.parse()?,
|
pub dist: NumWithUnit,
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_side(s: &str) -> Result<Side> {
|
impl StrutDefinition {
|
||||||
match s {
|
pub fn from_xml_element(xml: XmlElement) -> Result<Self> {
|
||||||
"l" | "left" => Ok(Side::Left),
|
Ok(StrutDefinition {
|
||||||
"r" | "right" => Ok(Side::Right),
|
side: xml.attr("side")?.parse()?,
|
||||||
"t" | "top" => Ok(Side::Top),
|
dist: xml.attr("distance")?.parse()?,
|
||||||
"b" | "bottom" => Ok(Side::Bottom),
|
})
|
||||||
_ => Err(anyhow!(
|
|
||||||
"Failed to parse {} as valid side. Must be one of \"left\", \"right\", \"top\", \"bottom\"",
|
|
||||||
s
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use crate::value::{AttrValue, PrimitiveValue};
|
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.
|
/// a gtk widget. These are created and initialized in EwwState::resolve.
|
||||||
pub struct StateChangeHandler {
|
pub struct StateChangeHandler {
|
||||||
func: Box<dyn Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static>,
|
func: Box<dyn Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static>,
|
||||||
unresolved_values: Vec<(AttrName, AttrValue)>,
|
unresolved_values: HashMap<AttrName, AttrValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StateChangeHandler {
|
impl StateChangeHandler {
|
||||||
|
@ -24,19 +24,16 @@ impl StateChangeHandler {
|
||||||
fn run_with_state(&self, state: &HashMap<VarName, PrimitiveValue>) {
|
fn run_with_state(&self, state: &HashMap<VarName, PrimitiveValue>) {
|
||||||
let resolved_attrs = self
|
let resolved_attrs = self
|
||||||
.unresolved_values
|
.unresolved_values
|
||||||
.iter()
|
.clone()
|
||||||
.cloned()
|
.into_iter()
|
||||||
.map(|(attr_name, value)| Ok((attr_name, value.resolve_fully(state)?)))
|
.map(|(attr_name, value)| Ok((attr_name, value.resolve_fully(state)?)))
|
||||||
.collect::<Result<_>>();
|
.collect::<Result<_>>();
|
||||||
|
|
||||||
match resolved_attrs {
|
match resolved_attrs {
|
||||||
Ok(resolved_attrs) => {
|
Ok(resolved_attrs) => {
|
||||||
let result: Result<_> = (self.func)(resolved_attrs);
|
crate::print_result_err!("while updating UI based after state change", &(self.func)(resolved_attrs))
|
||||||
crate::print_result_err!("while updating UI based after state change", &result);
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
eprintln!("Error while resolving attributes: {:?}", err);
|
|
||||||
}
|
}
|
||||||
|
Err(err) => eprintln!("Error while resolving attributes: {:?}", err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,73 +97,56 @@ impl EwwState {
|
||||||
|
|
||||||
/// Update the value of a variable, running all registered
|
/// Update the value of a variable, running all registered
|
||||||
/// [StateChangeHandler]s.
|
/// [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);
|
self.variables_state.insert(key.clone(), value);
|
||||||
|
|
||||||
let handlers = self
|
// run all of the handlers
|
||||||
.windows
|
self.windows
|
||||||
.values()
|
.values()
|
||||||
.filter_map(|window_state| window_state.state_change_handlers.get(&key))
|
.filter_map(|window_state| window_state.state_change_handlers.get(&key))
|
||||||
.flatten();
|
.flatten()
|
||||||
|
.for_each(|handler| handler.run_with_state(&self.variables_state));
|
||||||
for handler in handlers {
|
|
||||||
handler.run_with_state(&self.variables_state)
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// resolves a value if possible, using the current eww_state
|
/// Look up a single variable in the eww state, returning an `Err` when the value is not found.
|
||||||
/// Expects there to be at max one level of nesting var_refs from local-env.
|
pub fn lookup(&self, var_name: &VarName) -> Result<&PrimitiveValue> {
|
||||||
/// This means that no elements in the local_env may be var-refs into the
|
self.variables_state
|
||||||
/// local_env again, but only into the global state.
|
.get(var_name)
|
||||||
pub fn resolve_once<'a>(
|
.with_context(|| format!("Unknown variable '{}' referenced", var_name))
|
||||||
&'a self,
|
}
|
||||||
local_env: &'a HashMap<VarName, AttrValue>,
|
|
||||||
value: &'a AttrValue,
|
/// resolves a value if possible, using the current eww_state.
|
||||||
) -> Result<PrimitiveValue> {
|
pub fn resolve_once<'a>(&'a self, value: &'a AttrValue) -> Result<PrimitiveValue> {
|
||||||
value
|
value
|
||||||
.iter()
|
.iter()
|
||||||
.map(|element| match element {
|
.map(|element| match element {
|
||||||
AttrValueElement::Primitive(primitive) => Ok(primitive.clone()),
|
AttrValueElement::Primitive(primitive) => Ok(primitive.clone()),
|
||||||
AttrValueElement::VarRef(var_name) => self
|
AttrValueElement::VarRef(var_name) => self.lookup(var_name).cloned(),
|
||||||
.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)),
|
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve takes a function that applies a set of fully resolved attribute
|
/// 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
|
/// values to it's gtk widget.
|
||||||
/// 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<F: Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static + Clone>(
|
pub fn resolve<F: Fn(HashMap<AttrName, PrimitiveValue>) -> Result<()> + 'static + Clone>(
|
||||||
&mut self,
|
&mut self,
|
||||||
window_name: &WindowName,
|
window_name: &WindowName,
|
||||||
local_env: &HashMap<VarName, AttrValue>,
|
|
||||||
required_attributes: HashMap<AttrName, AttrValue>,
|
required_attributes: HashMap<AttrName, AttrValue>,
|
||||||
set_value: F,
|
set_value: F,
|
||||||
) {
|
) {
|
||||||
let handler = StateChangeHandler {
|
let handler = StateChangeHandler {
|
||||||
func: Box::new(set_value),
|
func: Box::new(set_value),
|
||||||
unresolved_values: required_attributes
|
unresolved_values: required_attributes,
|
||||||
.into_iter()
|
|
||||||
.map(|(attr_name, attr_value)| (attr_name, attr_value.resolve_one_level(local_env)))
|
|
||||||
.collect(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
handler.run_with_state(&self.variables_state);
|
handler.run_with_state(&self.variables_state);
|
||||||
|
|
||||||
// only store the handler if at least one variable is being used
|
// only store the handler if at least one variable is being used
|
||||||
if handler.used_variables().next().is_some() {
|
if handler.used_variables().next().is_some() {
|
||||||
let window_state = self
|
self.windows
|
||||||
.windows
|
|
||||||
.entry(window_name.clone())
|
.entry(window_name.clone())
|
||||||
.or_insert_with(EwwWindowState::default);
|
.or_insert_with(EwwWindowState::default)
|
||||||
window_state.put_handler(handler);
|
.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
|
/// read an scss file, replace all environment variable references within it and
|
||||||
/// then parse it into css.
|
/// then parse it into css.
|
||||||
pub fn parse_scss_from_file(path: &Path) -> Result<String> {
|
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())
|
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 {
|
pub fn resolve_one_level(self, variables: &HashMap<VarName, AttrValue>) -> AttrValue {
|
||||||
self.into_iter()
|
self.into_iter()
|
||||||
.flat_map(|entry| match entry {
|
.flat_map(|entry| match entry {
|
||||||
|
@ -55,6 +58,9 @@ impl AttrValue {
|
||||||
.collect()
|
.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> {
|
pub fn resolve_fully(self, variables: &HashMap<VarName, PrimitiveValue>) -> Result<PrimitiveValue> {
|
||||||
self.into_iter()
|
self.into_iter()
|
||||||
.map(|element| match element {
|
.map(|element| match element {
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
use crate::{
|
use crate::{config::window_definition::WindowName, eww_state::*, print_result_err, value::AttrName};
|
||||||
config::{element, window_definition::WindowName},
|
|
||||||
eww_state::*,
|
|
||||||
print_result_err,
|
|
||||||
value::{AttrName, AttrValue, VarName},
|
|
||||||
};
|
|
||||||
use anyhow::*;
|
use anyhow::*;
|
||||||
use gtk::prelude::*;
|
use gtk::prelude::*;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use std::{collections::HashMap, process::Command};
|
use std::process::Command;
|
||||||
use widget_definitions::*;
|
use widget_definitions::*;
|
||||||
|
|
||||||
pub mod widget_definitions;
|
pub mod widget_definitions;
|
||||||
|
pub mod widget_node;
|
||||||
|
|
||||||
const CMD_STRING_PLACEHODLER: &str = "{}";
|
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);
|
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,
|
eww_state: &'a mut EwwState,
|
||||||
local_env: &'b HashMap<VarName, AttrValue>,
|
widget: &'b widget_node::Generic,
|
||||||
widget: &'c element::WidgetUse,
|
|
||||||
unhandled_attrs: Vec<&'c AttrName>,
|
unhandled_attrs: Vec<&'c AttrName>,
|
||||||
window_name: &'d WindowName,
|
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
|
/// 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
|
/// resolving the widget, Or `Ok(None)` if the widget_use just didn't match any
|
||||||
/// widget name.
|
/// widget name.
|
||||||
fn build_builtin_gtk_widget(
|
fn build_builtin_gtk_widget(
|
||||||
widget_definitions: &HashMap<String, element::WidgetDefinition>,
|
|
||||||
eww_state: &mut EwwState,
|
eww_state: &mut EwwState,
|
||||||
window_name: &WindowName,
|
window_name: &WindowName,
|
||||||
local_env: &HashMap<VarName, AttrValue>,
|
widget: &widget_node::Generic,
|
||||||
widget: &element::WidgetUse,
|
|
||||||
) -> Result<Option<gtk::Widget>> {
|
) -> Result<Option<gtk::Widget>> {
|
||||||
let mut bargs = BuilderArgs {
|
let mut bargs = BuilderArgs {
|
||||||
eww_state,
|
eww_state,
|
||||||
local_env,
|
|
||||||
widget,
|
widget,
|
||||||
window_name,
|
window_name,
|
||||||
unhandled_attrs: widget.attrs.keys().collect(),
|
unhandled_attrs: widget.attrs.keys().collect(),
|
||||||
widget_definitions,
|
|
||||||
};
|
};
|
||||||
let gtk_widget = match widget_to_gtk_widget(&mut bargs) {
|
let gtk_widget = match widget_to_gtk_widget(&mut bargs) {
|
||||||
Ok(Some(gtk_widget)) => gtk_widget,
|
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>() {
|
if let Some(gtk_widget) = gtk_widget.dynamic_cast_ref::<gtk::Container>() {
|
||||||
resolve_container_attrs(&mut bargs, >k_widget);
|
resolve_container_attrs(&mut bargs, >k_widget);
|
||||||
for child in &widget.children {
|
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.render(bargs.eww_state, window_name).with_context(|| {
|
||||||
let child_widget = child_widget.with_context(|| {
|
|
||||||
format!(
|
format!(
|
||||||
"{}error while building child '{:#?}' of '{}'",
|
"{}error while building child '{:#?}' of '{}'",
|
||||||
widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(),
|
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 {
|
if let Ok(attr_map) = attr_map {
|
||||||
$args.eww_state.resolve(
|
$args.eww_state.resolve(
|
||||||
$args.window_name,
|
$args.window_name,
|
||||||
$args.local_env,
|
|
||||||
attr_map,
|
attr_map,
|
||||||
::glib::clone!(@strong $gtk_widget => move |attrs| {
|
::glib::clone!(@strong $gtk_widget => move |attrs| {
|
||||||
$(
|
$(
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use super::{run_command, BuilderArgs};
|
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 anyhow::*;
|
||||||
use gtk::{prelude::*, ImageExt};
|
use gtk::{prelude::*, ImageExt};
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
|
|
||||||
// TODO figure out how to
|
// TODO figure out how to
|
||||||
// TODO https://developer.gnome.org/gtk3/stable/GtkFixed.html
|
// 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
|
if let Ok(visible) = bargs
|
||||||
.widget
|
.widget
|
||||||
.get_attr("visible")
|
.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| {
|
connect_first_map(gtk_widget, move |w| {
|
||||||
if visible {
|
if visible {
|
||||||
|
@ -410,7 +410,6 @@ fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
|
||||||
|
|
||||||
// TODO these clones here are dumdum
|
// TODO these clones here are dumdum
|
||||||
let window_name = bargs.window_name.clone();
|
let window_name = bargs.window_name.clone();
|
||||||
let widget_definitions = bargs.widget_definitions.clone();
|
|
||||||
resolve_block!(bargs, gtk_widget, {
|
resolve_block!(bargs, gtk_widget, {
|
||||||
// @prop content - inline Eww XML that will be rendered as a widget.
|
// @prop content - inline Eww XML that will be rendered as a widget.
|
||||||
prop(content: as_string) {
|
prop(content: as_string) {
|
||||||
|
@ -418,13 +417,9 @@ fn build_gtk_literal(bargs: &mut BuilderArgs) -> Result<gtk::Box> {
|
||||||
if !content.is_empty() {
|
if !content.is_empty() {
|
||||||
let document = roxmltree::Document::parse(&content).map_err(|e| anyhow!("Failed to parse eww xml literal: {:?}", e))?;
|
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 content_widget_use = config::element::WidgetUse::from_xml_node(document.root_element().into())?;
|
||||||
let child_widget = super::widget_use_to_gtk_widget(
|
|
||||||
&widget_definitions,
|
let widget_node = &*widget_node::generate_generic_widget_node(&HashMap::new(), &HashMap::new(), content_widget_use)?;
|
||||||
&mut eww_state::EwwState::default(),
|
let child_widget = widget_node.render( &mut eww_state::EwwState::default(), &window_name)?;
|
||||||
&window_name,
|
|
||||||
&std::collections::HashMap::new(),
|
|
||||||
&content_widget_use,
|
|
||||||
)?;
|
|
||||||
gtk_widget.add(&child_widget);
|
gtk_widget.add(&child_widget);
|
||||||
child_widget.show();
|
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