feat(config): Allow custom widgets to make use of children (#317)

This commit is contained in:
ElKowar 2021-10-23 14:10:07 +02:00 committed by GitHub
parent 0aaaa2c2b8
commit 48bfb1e0c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 68 additions and 5 deletions

View file

@ -112,10 +112,7 @@ pub fn generate_generic_widget_node(
w: WidgetUse, w: WidgetUse,
) -> AstResult<Box<dyn WidgetNode>> { ) -> AstResult<Box<dyn WidgetNode>> {
if let Some(def) = defs.get(&w.name) { if let Some(def) = defs.get(&w.name) {
if !w.children.is_empty() { let children_span = w.children_span();
return Err(AstError::TooManyNodes(w.children_span(), 0).note("User-defined widgets cannot be given children."));
}
let mut new_local_env = w let mut new_local_env = w
.attrs .attrs
.attrs .attrs
@ -129,7 +126,9 @@ pub fn generate_generic_widget_node(
new_local_env.entry(var_name).or_insert_with(|| SimplExpr::literal(expected.span, String::new())); new_local_env.entry(var_name).or_insert_with(|| SimplExpr::literal(expected.span, String::new()));
} }
let content = generate_generic_widget_node(defs, &new_local_env, def.widget.clone())?; let definition_content = replace_children_placeholder_in(children_span, def.widget.clone(), &w.children)?;
let content = generate_generic_widget_node(defs, &new_local_env, definition_content)?;
Ok(Box::new(UserDefined { name: w.name, span: w.span, content })) Ok(Box::new(UserDefined { name: w.name, span: w.span, content }))
} else { } else {
Ok(Box::new(Generic { Ok(Box::new(Generic {
@ -151,3 +150,33 @@ pub fn generate_generic_widget_node(
})) }))
} }
} }
/// Replaces all the `children` placeholders in the given [`widget`](w) using the provided [`children`](provided_children).
fn replace_children_placeholder_in(use_span: Span, mut w: WidgetUse, provided_children: &[WidgetUse]) -> AstResult<WidgetUse> {
// Take the current children from the widget and replace them with an empty vector that we will now add widgets to again.
let child_count = w.children.len();
let widget_children = std::mem::replace(&mut w.children, Vec::with_capacity(child_count));
for mut child in widget_children.into_iter() {
if child.name == "children" {
// Note that we use `primitive_optional` here, meaning that the value for `nth` must be static.
// We'll be able to make this dynamic after the state management structure rework
if let Some(nth) = child.attrs.primitive_optional::<usize, _>("nth")? {
// If a single child is referenced, push that single widget into the children
let selected_child: &WidgetUse = provided_children
.get(nth)
.ok_or_else(|| AstError::MissingNode(use_span).context_label(child.span, "required here"))?;
w.children.push(selected_child.clone());
} else {
// otherwise append all provided children
w.children.append(&mut provided_children.to_vec());
}
} else {
// If this isn't a `children`-node, then recursively go into it and replace the children there.
// If there are no children referenced in there, this will append the widget unchanged.
let child = replace_children_placeholder_in(use_span, child, provided_children)?;
w.children.push(child);
}
}
Ok(w)
}

View file

@ -82,6 +82,8 @@ impl Attributes {
} }
} }
/// Retrieve a required attribute from the set which _must not_ reference any variables,
/// and is thus known to be static.
pub fn primitive_required<T, E>(&mut self, key: &str) -> Result<T, AstError> pub fn primitive_required<T, E>(&mut self, key: &str) -> Result<T, AstError>
where where
E: std::error::Error + 'static + Sync + Send, E: std::error::Error + 'static + Sync + Send,
@ -95,6 +97,8 @@ impl Attributes {
.map_err(|e| AttrError::Other(ast.span(), Box::new(e)))?) .map_err(|e| AttrError::Other(ast.span(), Box::new(e)))?)
} }
/// Retrieve an optional attribute from the set which _must not_ reference any variables,
/// and is thus known to be static.
pub fn primitive_optional<T, E>(&mut self, key: &str) -> Result<Option<T>, AstError> pub fn primitive_optional<T, E>(&mut self, key: &str) -> Result<Option<T>, AstError>
where where
E: std::error::Error + 'static + Sync + Send, E: std::error::Error + 'static + Sync + Send,

View file

@ -127,6 +127,36 @@ To then use our widget, we call it just like we would use any other built-in wid
As you may have noticed, we are using a couple predefined widgets here. These are all listed and explained in the [widgets chapter](widgets.md). As you may have noticed, we are using a couple predefined widgets here. These are all listed and explained in the [widgets chapter](widgets.md).
### Rendering children in your widgets
(Note that this feature is currently considered **unstable**. The API might change, and there might be bugs here. If you encounter any, please do report them.)
As your configuration grows, you might want to improve the structure of you config by factoring out functionality into basic reusable widgets.
Eww allows you to create custom wrapper widgets that can themselves take children, just like some of the built-in widgets like `box` or `button` can.
For this, use the `children` placeholder:
```lisp
(defwidget labeled-container [name]
(box :class "container"
name
(children)))
```
Now you can use this widget as expected:
```lisp
(labeled-container :name "foo"
(button :onclick "notify-send hey ho"
"click me"))
```
You can also create more complex structure by referring to specific children with the `nth`-attribute:
```lisp
(defwidget two-boxes []
(box
(box :class "first" (children :nth 0))
(box :class "second" (children :nth 1))))
```
**NOTE**: It is currently not possible to dynamically change which child is shown.
This means that `nth` needs to be set to a static value, and cannot refer to a variable.
## Adding dynamic content ## Adding dynamic content