diff --git a/crates/eww/src/widgets/widget_node.rs b/crates/eww/src/widgets/widget_node.rs index db6bdf5..5378336 100644 --- a/crates/eww/src/widgets/widget_node.rs +++ b/crates/eww/src/widgets/widget_node.rs @@ -112,10 +112,7 @@ pub fn generate_generic_widget_node( w: WidgetUse, ) -> AstResult> { if let Some(def) = defs.get(&w.name) { - if !w.children.is_empty() { - return Err(AstError::TooManyNodes(w.children_span(), 0).note("User-defined widgets cannot be given children.")); - } - + let children_span = w.children_span(); let mut new_local_env = w .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())); } - 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 })) } else { 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 { + // 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::("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) +} diff --git a/crates/yuck/src/config/attributes.rs b/crates/yuck/src/config/attributes.rs index dad6570..b3809c0 100644 --- a/crates/yuck/src/config/attributes.rs +++ b/crates/yuck/src/config/attributes.rs @@ -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(&mut self, key: &str) -> Result where E: std::error::Error + 'static + Sync + Send, @@ -95,6 +97,8 @@ impl Attributes { .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(&mut self, key: &str) -> Result, AstError> where E: std::error::Error + 'static + Sync + Send, diff --git a/docs/src/configuration.md b/docs/src/configuration.md index c921756..e9c2af7 100644 --- a/docs/src/configuration.md +++ b/docs/src/configuration.md @@ -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). +### 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