Skip to main content

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}