fret_authoring/lib.rs
1//! Shared authoring contracts for ecosystem-level UI frontends.
2//!
3//! This crate defines small, policy-light traits that allow ecosystem crates to expose authoring
4//! helpers without coupling to a specific frontend (e.g. `fret-imui`).
5//!
6//! Design constraints:
7//! - Keep dependencies minimal (`fret-ui` / `fret-core` only).
8//! - Do not introduce a second UI runtime: authoring must compile down to the declarative element
9//! taxonomy mounted into `UiTree` (ADR 0028).
10
11use std::hash::Hash;
12
13use fret_core::Rect;
14use fret_ui::element::AnyElement;
15use fret_ui::{ElementContext, UiHost};
16
17/// Minimal interaction result for immediate-style authoring helpers.
18///
19/// This type is intentionally small and ecosystem-friendly. Higher-level authoring facades may
20/// extend or wrap it with richer signals (drag lifecycle, click variants, etc.), but this core
21/// contract should remain stable once third-party widget crates depend on it.
22#[derive(Debug, Clone, Copy, Default)]
23pub struct Response {
24 pub hovered: bool,
25 pub pressed: bool,
26 pub focused: bool,
27 pub clicked: bool,
28 pub changed: bool,
29 pub rect: Option<Rect>,
30}
31
32impl Response {
33 pub fn clicked(self) -> bool {
34 self.clicked
35 }
36
37 pub fn changed(self) -> bool {
38 self.changed
39 }
40}
41#[cfg(feature = "query")]
42pub mod query;
43#[cfg(feature = "selector")]
44pub mod selector;
45
46#[cfg(feature = "query")]
47pub use query::UiWriterQueryExt;
48#[cfg(feature = "selector")]
49pub use selector::UiWriterSelectorExt;
50
51/// Minimal authoring surface for immediate-style composition.
52///
53/// This is intended for ecosystem crates that want to expose helpers that work across multiple
54/// authoring frontends while staying policy-light.
55pub trait UiWriter<H: UiHost> {
56 /// Execute a closure with access to the underlying element context (escape hatch).
57 ///
58 /// This avoids leaking `ElementContext`'s lifetime parameter through the trait surface while
59 /// still enabling advanced integrations when needed.
60 fn with_cx_mut<R>(&mut self, f: impl FnOnce(&mut ElementContext<'_, H>) -> R) -> R;
61
62 /// Append a single element to the current output list.
63 fn add(&mut self, element: AnyElement);
64
65 /// Append an iterator of elements to the current output list.
66 fn extend<I>(&mut self, elements: I)
67 where
68 I: IntoIterator<Item = AnyElement>,
69 {
70 for element in elements {
71 self.add(element);
72 }
73 }
74
75 /// Embed an existing declarative builder into the current output list.
76 fn mount<I>(&mut self, f: impl FnOnce(&mut ElementContext<'_, H>) -> I)
77 where
78 I: IntoIterator<Item = AnyElement>,
79 {
80 let elements: Vec<AnyElement> = self.with_cx_mut(|cx| f(cx).into_iter().collect());
81 self.extend(elements);
82 }
83
84 /// Create a keyed identity scope.
85 ///
86 /// This delegates to the runtime's canonical keyed identity mechanism (`ElementContext::keyed`)
87 /// so hashing and stable ID rules remain consistent across frontends.
88 fn keyed<K: Hash, R>(&mut self, key: K, f: impl FnOnce(&mut ElementContext<'_, H>) -> R) -> R {
89 self.with_cx_mut(|cx| cx.keyed(key, f))
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::UiWriter;
96 use fret_ui::UiHost;
97 use fret_ui::element::AnyElement;
98
99 // Compile-level smoke: this crate exists primarily to define shared signatures across
100 // ecosystem crates. The body is never executed; it only ensures the surface stays usable.
101 #[allow(dead_code)]
102 fn writer_surface_smoke<H: UiHost>(ui: &mut impl UiWriter<H>) {
103 ui.with_cx_mut(|_cx| ());
104 ui.extend(Vec::<AnyElement>::new());
105 ui.mount(|_cx| Vec::<AnyElement>::new());
106 ui.keyed("key", |_cx| ());
107 ui.keyed(123_u64, |_cx| ());
108 }
109
110 #[test]
111 fn ui_writer_compiles() {}
112}