221 lines
8.1 KiB
Rust
221 lines
8.1 KiB
Rust
use crate::{
|
|
config::{element, window_definition::WindowName},
|
|
eww_state::*,
|
|
value::{AttrName, AttrValue, VarName},
|
|
};
|
|
use anyhow::*;
|
|
use gtk::prelude::*;
|
|
use itertools::Itertools;
|
|
|
|
use std::{collections::HashMap, process::Command};
|
|
use widget_definitions::*;
|
|
|
|
pub mod widget_definitions;
|
|
|
|
const CMD_STRING_PLACEHODLER: &str = "{}";
|
|
|
|
/// Run a command that was provided as an attribute. This command may use a
|
|
/// placeholder ('{}') which will be replaced by the value provided as [`arg`]
|
|
pub fn run_command<T: std::fmt::Display>(cmd: &str, arg: T) {
|
|
let cmd = cmd.replace(CMD_STRING_PLACEHODLER, &format!("{}", arg));
|
|
if let Err(e) = Command::new("/bin/sh").arg("-c").arg(cmd).output() {
|
|
eprintln!("{}", e);
|
|
}
|
|
}
|
|
|
|
struct BuilderArgs<'a, 'b, 'c, 'd, 'e> {
|
|
eww_state: &'a mut EwwState,
|
|
local_env: &'b HashMap<VarName, AttrValue>,
|
|
widget: &'c element::WidgetUse,
|
|
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
|
|
/// **builtin widget**. User defined widgets are handled by
|
|
/// [widget_use_to_gtk_widget].
|
|
///
|
|
/// Also registers all the necessary 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 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,
|
|
) -> 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,
|
|
result => {
|
|
return result.with_context(|| {
|
|
anyhow!(
|
|
"{}Error building widget {}",
|
|
bargs.widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(),
|
|
bargs.widget.name,
|
|
)
|
|
})
|
|
}
|
|
};
|
|
|
|
// run resolve functions for superclasses such as range, orientable, and 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(|| {
|
|
format!(
|
|
"{}error while building child '{:#?}' of '{}'",
|
|
widget.text_pos.map(|x| format!("{} |", x)).unwrap_or_default(),
|
|
&child,
|
|
>k_widget.get_widget_name()
|
|
)
|
|
})?;
|
|
gtk_widget.add(&child_widget);
|
|
child_widget.show();
|
|
}
|
|
}
|
|
|
|
gtk_widget.dynamic_cast_ref().map(|w| resolve_range_attrs(&mut bargs, &w));
|
|
gtk_widget
|
|
.dynamic_cast_ref()
|
|
.map(|w| resolve_orientable_attrs(&mut bargs, &w));
|
|
resolve_widget_attrs(&mut bargs, >k_widget);
|
|
|
|
if !bargs.unhandled_attrs.is_empty() {
|
|
eprintln!(
|
|
"{}WARN: Unknown attribute used in {}: {}",
|
|
widget.text_pos.map(|x| format!("{} | ", x)).unwrap_or_default(),
|
|
widget.name,
|
|
bargs.unhandled_attrs.iter().map(|x| x.to_string()).join(", ")
|
|
)
|
|
}
|
|
|
|
Ok(Some(gtk_widget))
|
|
}
|
|
|
|
#[macro_export]
|
|
macro_rules! resolve_block {
|
|
($args:ident, $gtk_widget:ident, {
|
|
$(
|
|
prop( $( $attr_name:ident : $typecast_func:ident $(= $default:expr)?),*) $code:block
|
|
),+ $(,)?
|
|
}) => {
|
|
$({
|
|
$(
|
|
$args.unhandled_attrs.retain(|a| &a.0 != &::std::stringify!($attr_name).replace('_', "-"));
|
|
)*
|
|
|
|
let attr_map: Result<_> = try {
|
|
::maplit::hashmap! {
|
|
$(
|
|
crate::value::AttrName(::std::stringify!($attr_name).to_owned()) =>
|
|
resolve_block!(@get_value $args, &::std::stringify!($attr_name).replace('_', "-"), $(= $default)?)
|
|
),*
|
|
}
|
|
};
|
|
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| {
|
|
$(
|
|
let $attr_name = attrs.get( ::std::stringify!($attr_name) ).context("something went terribly wrong....")?.$typecast_func()?;
|
|
)*
|
|
$code
|
|
Ok(())
|
|
})
|
|
);
|
|
}
|
|
})+
|
|
};
|
|
|
|
(@get_value $args:ident, $name:expr, = $default:expr) => {
|
|
$args.widget.get_attr($name).cloned().unwrap_or(AttrValue::from_primitive($default))
|
|
};
|
|
|
|
(@get_value $args:ident, $name:expr,) => {
|
|
$args.widget.get_attr($name)?.clone()
|
|
}
|
|
}
|
|
|
|
#[allow(unused)]
|
|
macro_rules! log_errors {
|
|
($body:expr) => {{
|
|
let result = try { $body };
|
|
if let Err(e) = result {
|
|
eprintln!("WARN: {}", e);
|
|
}
|
|
}};
|
|
}
|