Skip to main content

tern_sdk/ui/
node.rs

1//! The dynamic node every builder produces, the view of a surface's three
2//! regions, and the macros that give every kind's builder the common props
3//! and handlers.
4
5use std::{fmt, sync::Arc};
6
7use serde::Serialize;
8use serde_json::{Map, Value};
9
10use crate::{
11	Error,
12	wire::{Action, Change, Edit, Event},
13};
14
15/// A routed event turned into a program message (or not, when it doesn't
16/// fit the handler).
17pub type Handler<M> = Arc<dyn Fn(&Event) -> Option<M> + Send + Sync>;
18
19/// What a handler answers to.
20#[derive(Clone, Debug, PartialEq, Eq)]
21pub enum Trigger {
22	/// `actions.click` (set to `"click"` unless the node names it).
23	Click,
24	/// `actions.dblclick` (set to `"dblclick"` unless the node names it).
25	Dblclick,
26	/// A context-menu action, added to `actions.menu`.
27	Menu(String),
28	/// `action` events with this `act`.
29	Action(String),
30	/// Events of this `ev` (`toggle`, `select`, `change`, …).
31	Event(&'static str),
32}
33
34/// A node: a kind, props, children and handlers. It has no id until it is
35/// reconciled. Every kind's builder converts into it.
36pub struct Node<M = ()> {
37	/// The kind.
38	pub(crate) kind:     String,
39	/// Props.
40	pub(crate) props:    Map<String, Value>,
41	/// Children in order.
42	pub(crate) children: Vec<Self>,
43	/// Handlers, in the order they were attached.
44	pub(crate) handlers: Vec<(Trigger, Handler<M>)>,
45	/// Slot props (`head`, `end`, `foot`) given a node: the child keyed by
46	/// the slot's name, sent as that child's id.
47	pub(crate) slots:    Vec<&'static str>,
48}
49
50impl<M> Clone for Node<M> {
51	fn clone(&self) -> Self {
52		Self {
53			kind:     self.kind.clone(),
54			props:    self.props.clone(),
55			children: self.children.clone(),
56			handlers: self.handlers.clone(),
57			slots:    self.slots.clone(),
58		}
59	}
60}
61
62impl<M> fmt::Debug for Node<M> {
63	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64		f.debug_struct("Node")
65			.field("kind", &self.kind)
66			.field("props", &self.props)
67			.field("children", &self.children)
68			.field("handlers", &self.handlers.iter().map(|(t, _)| t).collect::<Vec<_>>())
69			.finish()
70	}
71}
72
73impl<M> Node<M> {
74	/// A node of any kind, without props.
75	pub fn new(kind: impl Into<String>) -> Self {
76		Self {
77			kind:     kind.into(),
78			props:    Map::new(),
79			children: Vec::new(),
80			handlers: Vec::new(),
81			slots:    Vec::new(),
82		}
83	}
84
85	/// The kind.
86	pub fn kind(&self) -> &str {
87		&self.kind
88	}
89
90	/// The props.
91	pub const fn props(&self) -> &Map<String, Value> {
92		&self.props
93	}
94
95	/// The children.
96	pub fn child_nodes(&self) -> &[Self] {
97		&self.children
98	}
99
100	/// A prop as text: a string, or spans joined.
101	pub(crate) fn text_prop(&self, key: &str) -> Option<String> {
102		match self.props.get(key)? {
103			Value::String(s) => Some(s.clone()),
104			Value::Array(spans) => Some(spans_text(spans)),
105			_ => None,
106		}
107	}
108
109	/// Sets a prop; `null` removes it.
110	pub(crate) fn set(&mut self, key: &str, value: &impl Serialize) {
111		match serde_json::to_value(value) {
112			Ok(Value::Null) => {
113				self.props.remove(key);
114			},
115			Ok(value) => {
116				self.props.insert(key.to_owned(), value);
117			},
118			Err(err) => tracing::warn!(key, %err, "prop not serializable; left unset"),
119		}
120	}
121
122	/// Puts `node` in slot `slot` (`head`, `end`, `foot`): the child keyed
123	/// `slot`, replacing an earlier one, with the prop sent as its id.
124	pub(crate) fn set_slot(&mut self, slot: &'static str, mut node: Self) {
125		node.props.insert("key".into(), Value::String(slot.into()));
126		self.props.remove(slot);
127		self
128			.children
129			.retain(|c| c.props.get("key").and_then(Value::as_str) != Some(slot));
130		self.children.push(node);
131		if !self.slots.contains(&slot) {
132			self.slots.push(slot);
133		}
134	}
135
136	/// Clears slot `slot` (a plain prop value replaces the node).
137	pub(crate) fn clear_slot(&mut self, slot: &str) {
138		if let Some(at) = self.slots.iter().position(|s| *s == slot) {
139			self.slots.remove(at);
140			self
141				.children
142				.retain(|c| c.props.get("key").and_then(Value::as_str) != Some(slot));
143		}
144	}
145
146	/// Sets the text of a kind that takes `text` or `spans`.
147	pub(crate) fn set_text(&mut self, text: crate::ui::Text) {
148		match text {
149			crate::ui::Text::Plain(s) => {
150				self.props.remove("spans");
151				self.props.insert("text".into(), Value::String(s));
152			},
153			crate::ui::Text::Spans(spans) => {
154				self.props.remove("text");
155				self.set("spans", &spans);
156			},
157		}
158	}
159
160	/// Builds a node from its wire form `{k, p?, c?}` (no handlers).
161	///
162	/// # Errors
163	/// [`Error::InvalidView`] when it isn't an object with a string `k`, or
164	/// `p`/`c` have the wrong shape.
165	pub fn from_json(value: &Value) -> Result<Self, Error> {
166		let Some(obj) = value.as_object() else {
167			return Err(Error::InvalidView(format!("a node must be an object, got {value}")));
168		};
169		let Some(kind) = obj.get("k").and_then(Value::as_str) else {
170			return Err(Error::InvalidView("a node needs a string `k`".into()));
171		};
172		let props = match obj.get("p") {
173			None | Some(Value::Null) => Map::new(),
174			Some(Value::Object(p)) => p.clone(),
175			Some(other) => {
176				return Err(Error::InvalidView(format!("`p` must be an object, got {other}")));
177			},
178		};
179		let children = match obj.get("c") {
180			None | Some(Value::Null) => Vec::new(),
181			Some(Value::Array(c)) => c.iter().map(Self::from_json).collect::<Result<_, _>>()?,
182			Some(other) => return Err(Error::InvalidView(format!("`c` must be a list, got {other}"))),
183		};
184		Ok(Self { children, props, ..Self::new(kind) })
185	}
186
187	/// The node's wire form without ids `{k, p?, c?}` (handlers dropped).
188	pub fn to_json(&self) -> Value {
189		let mut obj = Map::new();
190		obj.insert("k".into(), Value::String(self.kind.clone()));
191		if !self.props.is_empty() {
192			obj.insert("p".into(), Value::Object(self.props.clone()));
193		}
194		if !self.children.is_empty() {
195			obj.insert("c".into(), Value::Array(self.children.iter().map(Self::to_json).collect()));
196		}
197		Value::Object(obj)
198	}
199}
200
201/// Joins the `t` of spans (strings count as themselves).
202pub fn spans_text(spans: &[Value]) -> String {
203	spans
204		.iter()
205		.filter_map(|s| match s {
206			Value::String(t) => Some(t.as_str()),
207			Value::Object(o) => o.get("t").and_then(Value::as_str),
208			_ => None,
209		})
210		.collect()
211}
212
213/// A list of nodes is a `col` of them (how a region given as a list is
214/// wrapped).
215impl<M> From<Vec<Self>> for Node<M> {
216	fn from(children: Vec<Self>) -> Self {
217		Self { children, ..Self::new("col") }
218	}
219}
220
221/// An array of nodes is a `col` of them.
222impl<M, C: Into<Self>, const N: usize> From<[C; N]> for Node<M> {
223	fn from(children: [C; N]) -> Self {
224		Self::from(children.into_iter().map(Into::into).collect::<Vec<Self>>())
225	}
226}
227
228/// Gives access to the node a builder wraps.
229pub trait AsNode<M> {
230	/// The node.
231	fn as_node(&mut self) -> &mut Node<M>;
232}
233
234impl<M> AsNode<M> for Node<M> {
235	fn as_node(&mut self) -> &mut Self {
236		self
237	}
238}
239
240/// A surface's view: the root of each region (`main`, `dock`, `layer`).
241/// A region left out is not shown.
242///
243/// A node given for a region is the region root itself; a list (a `Vec` or
244/// an array) is wrapped in a `col` root. Tern draws a region's children into
245/// an element of its own and ignores the root's kind and props (but
246/// `role`), so a root should be a container. A bare node or list converts
247/// into a view as `main`'s children:
248///
249/// ```
250/// use tern_sdk::ui::{self, View};
251///
252/// let view: View = ui::md("**hi**").into();
253/// let root = view.regions()[0].unwrap();
254/// assert_eq!((root.kind(), root.child_nodes()[0].kind()), ("col", "md"));
255/// ```
256pub struct View<M = ()> {
257	/// The flowing document.
258	pub(crate) main:  Option<Node<M>>,
259	/// Sticky at the pane's bottom while live.
260	pub(crate) dock:  Option<Node<M>>,
261	/// Overlays.
262	pub(crate) layer: Option<Node<M>>,
263}
264
265impl<M> Default for View<M> {
266	fn default() -> Self {
267		Self { main: None, dock: None, layer: None }
268	}
269}
270
271impl<M> Clone for View<M> {
272	fn clone(&self) -> Self {
273		Self { main: self.main.clone(), dock: self.dock.clone(), layer: self.layer.clone() }
274	}
275}
276
277impl<M> fmt::Debug for View<M> {
278	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279		f.debug_struct("View")
280			.field("main", &self.main)
281			.field("dock", &self.dock)
282			.field("layer", &self.layer)
283			.finish()
284	}
285}
286
287impl<M> View<M> {
288	/// The empty view.
289	pub fn new() -> Self {
290		Self::default()
291	}
292
293	/// Sets `main`, the flowing document: a root node, or a list of
294	/// children wrapped in a `col`.
295	pub fn main(mut self, region: impl Into<Node<M>>) -> Self {
296		self.main = Some(region.into());
297		self
298	}
299
300	/// Sets `dock`, sticky at the pane's bottom: a root node, or a list.
301	pub fn dock(mut self, region: impl Into<Node<M>>) -> Self {
302		self.dock = Some(region.into());
303		self
304	}
305
306	/// Sets `layer`, the overlays: a root node, or a list.
307	pub fn layer(mut self, region: impl Into<Node<M>>) -> Self {
308		self.layer = Some(region.into());
309		self
310	}
311
312	/// The regions in order `main`, `dock`, `layer`.
313	pub const fn regions(&self) -> [Option<&Node<M>>; 3] {
314		[self.main.as_ref(), self.dock.as_ref(), self.layer.as_ref()]
315	}
316
317	/// Builds a view from its wire form `{main?, dock?, layer?}` (`null` is
318	/// the empty view; a region given as a list is wrapped in a `col`).
319	///
320	/// # Errors
321	/// [`Error::InvalidView`] for an unknown region or a malformed node.
322	pub fn from_json(value: &Value) -> Result<Self, Error> {
323		let mut view = Self::new();
324		match value {
325			Value::Null => {},
326			Value::Object(obj) => {
327				for (name, node) in obj {
328					let node = match node {
329						Value::Null => continue,
330						Value::Array(list) => Node::from(
331							list
332								.iter()
333								.map(Node::from_json)
334								.collect::<Result<Vec<_>, _>>()?,
335						),
336						other => Node::from_json(other)?,
337					};
338					match name.as_str() {
339						"main" => view.main = Some(node),
340						"dock" => view.dock = Some(node),
341						"layer" => view.layer = Some(node),
342						other => return Err(Error::InvalidView(format!("unknown region {other:?}"))),
343					}
344				}
345			},
346			other => {
347				return Err(Error::InvalidView(format!(
348					"expected {{main?, dock?, layer?}}, got {other}"
349				)));
350			},
351		}
352		Ok(view)
353	}
354}
355
356/// A bare node is `main`'s one child.
357impl<M> From<Node<M>> for View<M> {
358	fn from(node: Node<M>) -> Self {
359		Self::new().main(vec![node])
360	}
361}
362
363/// A list of nodes is `main`'s children.
364impl<M> From<Vec<Node<M>>> for View<M> {
365	fn from(children: Vec<Node<M>>) -> Self {
366		Self::new().main(children)
367	}
368}
369
370/// An array of nodes is `main`'s children.
371impl<M, C: Into<Node<M>>, const N: usize> From<[C; N]> for View<M> {
372	fn from(children: [C; N]) -> Self {
373		Self::new().main(children)
374	}
375}
376
377/// Wraps a message as a handler that answers events of one shape.
378pub fn msg_handler<M: Clone + Send + Sync + 'static>(msg: M) -> Handler<M> {
379	Arc::new(move |_| Some(msg.clone()))
380}
381
382/// The handler of an `action` event, from its payload.
383pub fn action_handler<M, F>(f: F) -> Handler<M>
384where
385	F: Fn(&Action) -> M + Send + Sync + 'static,
386{
387	Arc::new(move |e| match e {
388		Event::Action(a) => Some(f(a)),
389		_ => None,
390	})
391}
392
393/// The handler of a `toggle` event: the new `collapsed` (not `open` in the
394/// events that say `open`).
395pub fn toggle_handler<M, F>(f: F) -> Handler<M>
396where
397	F: Fn(bool) -> M + Send + Sync + 'static,
398{
399	Arc::new(move |e| match e {
400		Event::Toggle(t) => Some(f(t.open.map_or(t.collapsed, |open| !open))),
401		_ => None,
402	})
403}
404
405/// The handler of a `select` (or `activate`) event, from its item.
406pub fn item_handler<M, F>(f: F, activate: bool) -> Handler<M>
407where
408	F: Fn(&str) -> M + Send + Sync + 'static,
409{
410	Arc::new(move |e| match (e, activate) {
411		(Event::Select(s), false) | (Event::Activate(s), true) => Some(f(&s.item)),
412		_ => None,
413	})
414}
415
416/// The handler of a `change` event.
417pub fn change_handler<M, F>(f: F) -> Handler<M>
418where
419	F: Fn(&Change) -> M + Send + Sync + 'static,
420{
421	Arc::new(move |e| match e {
422		Event::Change(c) => Some(f(c)),
423		_ => None,
424	})
425}
426
427/// The handler of an `edit` event.
428pub fn edit_handler<M, F>(f: F) -> Handler<M>
429where
430	F: Fn(&Edit) -> M + Send + Sync + 'static,
431{
432	Arc::new(move |e| match e {
433		Event::Edit(ed) => Some(f(ed)),
434		_ => None,
435	})
436}
437
438/// The handler of a `send` event, from its text.
439pub fn send_handler<M, F>(f: F) -> Handler<M>
440where
441	F: Fn(&str) -> M + Send + Sync + 'static,
442{
443	Arc::new(move |e| match e {
444		Event::Send(s) => Some(f(&s.text)),
445		_ => None,
446	})
447}
448
449/// The props every node takes, except `title`, `max` and `actions`.
450macro_rules! base_methods {
451	() => {
452		/// Identity among siblings: keeps the node's id (and Tern's view
453		/// state) across reorders. A string, or an integer.
454		pub fn key(mut self, key: impl Into<$crate::ui::NodeKey>) -> Self {
455			$crate::ui::node::AsNode::as_node(&mut self).set("key", &key.into());
456			self
457		}
458
459		/// A name of yours, exposed to CSS as `data-role`.
460		pub fn role(mut self, role: impl Into<String>) -> Self {
461			$crate::ui::node::AsNode::as_node(&mut self).set("role", &role.into());
462			self
463		}
464
465		/// The semantic color of the node's chrome.
466		pub fn tone(mut self, tone: impl Into<$crate::ui::Tone>) -> Self {
467			$crate::ui::node::AsNode::as_node(&mut self).set("tone", &tone.into());
468			self
469		}
470
471		/// Keeps the node mounted but not laid out.
472		pub fn hidden(mut self, hidden: bool) -> Self {
473			$crate::ui::node::AsNode::as_node(&mut self).set("hidden", &hidden);
474			self
475		}
476
477		/// A transient selection mark drawn over the node.
478		pub fn mark(mut self, mark: impl Into<$crate::ui::Mark>) -> Self {
479			$crate::ui::node::AsNode::as_node(&mut self).set("mark", &mark.into());
480			self
481		}
482
483		/// The accessible name of a node that shows no text.
484		pub fn aria(mut self, aria: impl Into<String>) -> Self {
485			$crate::ui::node::AsNode::as_node(&mut self).set("aria", &aria.into());
486			self
487		}
488
489		/// A floating card of `card`'s tree shown while the pointer rests on
490		/// the node, in place of `title`. Sent id-less (ids, keys and handlers
491		/// inside are ignored); an older Tern ignores it.
492		pub fn hovercard<N>(mut self, card: impl Into<$crate::ui::Node<N>>) -> Self {
493			let card = card.into().to_json();
494			$crate::ui::node::AsNode::as_node(&mut self).set("hovercard", &card);
495			self
496		}
497
498		/// A link: ⌘-click opens it; `copy` and `open` use it.
499		pub fn href(mut self, href: impl Into<String>) -> Self {
500			$crate::ui::node::AsNode::as_node(&mut self).set("href", &href.into());
501			self
502		}
503
504		/// Flex grow inside a `row` or `col`.
505		pub fn grow(mut self, grow: f64) -> Self {
506			$crate::ui::node::AsNode::as_node(&mut self).set("grow", &grow);
507			self
508		}
509
510		/// Flex shrink inside a `row` or `col`.
511		pub fn shrink(mut self, shrink: f64) -> Self {
512			$crate::ui::node::AsNode::as_node(&mut self).set("shrink", &shrink);
513			self
514		}
515
516		/// Flex basis: a fraction of the parent, or the content's size.
517		pub fn basis(mut self, basis: impl Into<$crate::ui::Basis>) -> Self {
518			$crate::ui::node::AsNode::as_node(&mut self).set("basis", &basis.into());
519			self
520		}
521
522		/// Sets any prop untyped; `null` removes it.
523		pub fn prop(mut self, name: &str, value: impl ::serde::Serialize) -> Self {
524			$crate::ui::node::AsNode::as_node(&mut self).set(name, &value);
525			self
526		}
527	};
528}
529
530/// The common `title` prop (a tooltip).
531macro_rules! title_method {
532	() => {
533		/// A tooltip.
534		pub fn title(mut self, title: impl Into<String>) -> Self {
535			$crate::ui::node::AsNode::as_node(&mut self).set("title", &title.into());
536			self
537		}
538	};
539}
540
541/// The common `min` prop.
542macro_rules! min_method {
543	() => {
544		/// Lower size bounds.
545		pub fn min(mut self, min: $crate::ui::Bound) -> Self {
546			$crate::ui::node::AsNode::as_node(&mut self).set("min", &min);
547			self
548		}
549	};
550}
551
552/// The common `max` prop.
553macro_rules! max_method {
554	() => {
555		/// Upper size bounds (a `max.h` also clips the node).
556		pub fn max(mut self, max: $crate::ui::Bound) -> Self {
557			$crate::ui::node::AsNode::as_node(&mut self).set("max", &max);
558			self
559		}
560	};
561}
562
563/// The common `actions` prop and the pointer handlers.
564macro_rules! actions_methods {
565	() => {
566		/// What a pointer does, by action name.
567		pub fn actions(mut self, actions: $crate::ui::Actions) -> Self {
568			$crate::ui::node::AsNode::as_node(&mut self).set("actions", &actions);
569			self
570		}
571
572		/// Sends `msg` on a click (sets `actions.click` to `"click"` unless
573		/// the node names its click action itself).
574		pub fn on_click(mut self, msg: M) -> Self
575		where
576			M: Clone + Send + Sync + 'static,
577		{
578			$crate::ui::node::AsNode::as_node(&mut self)
579				.handlers
580				.push(($crate::ui::node::Trigger::Click, $crate::ui::node::msg_handler(msg)));
581			self
582		}
583
584		/// Sends `msg` on a double click (sets `actions.dblclick` to
585		/// `"dblclick"` unless the node names it).
586		pub fn on_dblclick(mut self, msg: M) -> Self
587		where
588			M: Clone + Send + Sync + 'static,
589		{
590			$crate::ui::node::AsNode::as_node(&mut self)
591				.handlers
592				.push(($crate::ui::node::Trigger::Dblclick, $crate::ui::node::msg_handler(msg)));
593			self
594		}
595
596		/// Adds `act` to the context menu and sends `msg` when it is picked.
597		pub fn on_menu(mut self, act: impl Into<String>, msg: M) -> Self
598		where
599			M: Clone + Send + Sync + 'static,
600		{
601			$crate::ui::node::AsNode::as_node(&mut self)
602				.handlers
603				.push((
604					$crate::ui::node::Trigger::Menu(act.into()),
605					$crate::ui::node::msg_handler(msg),
606				));
607			self
608		}
609	};
610}
611
612/// Handlers of the events a node sends.
613macro_rules! event_methods {
614	() => {
615		/// Sends `msg` on `action` events whose action is `act` (`name` or
616		/// `name=value`).
617		pub fn on_action(mut self, act: impl Into<String>, msg: M) -> Self
618		where
619			M: Clone + Send + Sync + 'static,
620		{
621			$crate::ui::node::AsNode::as_node(&mut self)
622				.handlers
623				.push((
624					$crate::ui::node::Trigger::Action(act.into()),
625					$crate::ui::node::msg_handler(msg),
626				));
627			self
628		}
629
630		/// Maps `action` events whose action is `act` to a message.
631		pub fn on_action_with(
632			mut self,
633			act: impl Into<String>,
634			f: impl Fn(&$crate::wire::Action) -> M + Send + Sync + 'static,
635		) -> Self {
636			$crate::ui::node::AsNode::as_node(&mut self)
637				.handlers
638				.push((
639					$crate::ui::node::Trigger::Action(act.into()),
640					$crate::ui::node::action_handler(f),
641				));
642			self
643		}
644
645		/// Maps `toggle` events (the new `collapsed`; a menu, select or trace
646		/// line that opened is not collapsed) to a message.
647		pub fn on_toggle(mut self, f: impl Fn(bool) -> M + Send + Sync + 'static) -> Self {
648			$crate::ui::node::AsNode::as_node(&mut self)
649				.handlers
650				.push((
651					$crate::ui::node::Trigger::Event("toggle"),
652					$crate::ui::node::toggle_handler(f),
653				));
654			self
655		}
656
657		/// Maps `select` events (the item) to a message. On a list's item the
658		/// item's own handler runs first.
659		pub fn on_select(mut self, f: impl Fn(&str) -> M + Send + Sync + 'static) -> Self {
660			$crate::ui::node::AsNode::as_node(&mut self)
661				.handlers
662				.push((
663					$crate::ui::node::Trigger::Event("select"),
664					$crate::ui::node::item_handler(f, false),
665				));
666			self
667		}
668
669		/// Maps `activate` events (the item) to a message.
670		pub fn on_activate(mut self, f: impl Fn(&str) -> M + Send + Sync + 'static) -> Self {
671			$crate::ui::node::AsNode::as_node(&mut self)
672				.handlers
673				.push((
674					$crate::ui::node::Trigger::Event("activate"),
675					$crate::ui::node::item_handler(f, true),
676				));
677			self
678		}
679
680		/// Maps `change` events to a message.
681		pub fn on_change(
682			mut self,
683			f: impl Fn(&$crate::wire::Change) -> M + Send + Sync + 'static,
684		) -> Self {
685			$crate::ui::node::AsNode::as_node(&mut self)
686				.handlers
687				.push((
688					$crate::ui::node::Trigger::Event("change"),
689					$crate::ui::node::change_handler(f),
690				));
691			self
692		}
693
694		/// Sends `msg` on `focus` events (a click asked for the keys).
695		pub fn on_focus(mut self, msg: M) -> Self
696		where
697			M: Clone + Send + Sync + 'static,
698		{
699			$crate::ui::node::AsNode::as_node(&mut self)
700				.handlers
701				.push(($crate::ui::node::Trigger::Event("focus"), $crate::ui::node::msg_handler(msg)));
702			self
703		}
704
705		/// Maps `edit` events (native editing) to a message.
706		pub fn on_edit(
707			mut self,
708			f: impl Fn(&$crate::wire::Edit) -> M + Send + Sync + 'static,
709		) -> Self {
710			$crate::ui::node::AsNode::as_node(&mut self)
711				.handlers
712				.push(($crate::ui::node::Trigger::Event("edit"), $crate::ui::node::edit_handler(f)));
713			self
714		}
715
716		/// Sends `msg` on `undo` events.
717		pub fn on_undo(mut self, msg: M) -> Self
718		where
719			M: Clone + Send + Sync + 'static,
720		{
721			$crate::ui::node::AsNode::as_node(&mut self)
722				.handlers
723				.push(($crate::ui::node::Trigger::Event("undo"), $crate::ui::node::msg_handler(msg)));
724			self
725		}
726
727		/// Maps `send` events (the text to submit) to a message.
728		pub fn on_send(mut self, f: impl Fn(&str) -> M + Send + Sync + 'static) -> Self {
729			$crate::ui::node::AsNode::as_node(&mut self)
730				.handlers
731				.push(($crate::ui::node::Trigger::Event("send"), $crate::ui::node::send_handler(f)));
732			self
733		}
734	};
735}
736
737/// Children of a container kind.
738macro_rules! children_methods {
739	() => {
740		/// Adds a child.
741		pub fn child(mut self, child: impl Into<$crate::ui::Node<M>>) -> Self {
742			$crate::ui::node::AsNode::as_node(&mut self)
743				.children
744				.push(child.into());
745			self
746		}
747
748		/// Adds children.
749		pub fn children<C: Into<$crate::ui::Node<M>>>(
750			mut self,
751			children: impl IntoIterator<Item = C>,
752		) -> Self {
753			$crate::ui::node::AsNode::as_node(&mut self)
754				.children
755				.extend(children.into_iter().map(Into::into));
756			self
757		}
758	};
759}
760
761/// Typed props: `method(Type) = "key";` sets prop `key` from
762/// `impl Into<Type>`.
763macro_rules! props {
764	($($(#[$m:meta])* $method:ident($ty:ty) = $key:literal;)*) => {
765		$(
766			$(#[$m])*
767			pub fn $method(mut self, value: impl Into<$ty>) -> Self {
768				let value: $ty = value.into();
769				$crate::ui::node::AsNode::as_node(&mut self).set($key, &value);
770				self
771			}
772		)*
773	};
774}
775
776/// Defines a kind's builder type wrapping a [`Node`].
777macro_rules! kind_type {
778	($(#[$m:meta])* $name:ident) => {
779		$(#[$m])*
780		pub struct $name<M = ()>(pub(crate) $crate::ui::Node<M>);
781
782		impl<M> Clone for $name<M> {
783			fn clone(&self) -> Self {
784				Self(self.0.clone())
785			}
786		}
787
788		impl<M> ::std::fmt::Debug for $name<M> {
789			fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
790				self.0.fmt(f)
791			}
792		}
793
794		impl<M> From<$name<M>> for $crate::ui::Node<M> {
795			fn from(n: $name<M>) -> Self {
796				n.0
797			}
798		}
799
800		/// A bare node is `main`'s one child.
801		impl<M> From<$name<M>> for $crate::ui::View<M> {
802			fn from(n: $name<M>) -> Self {
803				Self::from(n.0)
804			}
805		}
806
807		impl<M> $crate::ui::node::AsNode<M> for $name<M> {
808			fn as_node(&mut self) -> &mut $crate::ui::Node<M> {
809				&mut self.0
810			}
811		}
812	};
813}
814
815pub(crate) use actions_methods;
816pub(crate) use base_methods;
817pub(crate) use children_methods;
818pub(crate) use event_methods;
819pub(crate) use kind_type;
820pub(crate) use max_method;
821pub(crate) use min_method;
822pub(crate) use props;
823pub(crate) use title_method;
824
825impl<M> Node<M> {
826	base_methods!();
827
828	min_method!();
829
830	title_method!();
831
832	max_method!();
833
834	actions_methods!();
835
836	event_methods!();
837
838	children_methods!();
839}