Skip to main content

lgui_core/core/view/declarative/
primitives.rs

1use super::element::{location_hash, stable_hash};
2use super::*;
3
4pub fn group(rect: UiRect) -> Element {
5    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| {
6        UiElement::group(cx.id, rect).children(cx.children)
7    })
8}
9
10pub fn compositing_layer(rect: UiRect, spec: CompositingLayerSpec) -> Element {
11    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| {
12        UiElement::compositing_layer(cx.id, rect, spec).children(cx.children)
13    })
14}
15
16/// Creates a retained compositing layer whose application-owned animation state updates only
17/// composition properties. Its static children are reused between frames.
18#[track_caller]
19pub fn animated_compositing_layer<T>(
20    rect: UiRect,
21    configure: impl FnOnce(&mut T) + 'static,
22) -> Element
23where
24    T: CompositingLayerAnimation,
25{
26    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| {
27        let (_, spec, wants_frame) = cx
28            .context
29            .compositing_layer_animation_mut(&cx.id, configure);
30        if wants_frame {
31            cx.context.hook_updates().request_frame();
32        }
33        UiElement::compositing_layer(cx.id, rect, spec).children(cx.children)
34    })
35}
36
37pub fn text(
38    rect: UiRect,
39    value: impl Into<std::borrow::Cow<'static, str>>,
40    style: TextStyle,
41) -> Element {
42    let value = value.into();
43    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| UiElement::text(cx.id, rect, value, style))
44}
45
46pub fn content_text(value: impl Into<Cow<'static, str>>) -> Element {
47    let value = value.into();
48    let height = 18;
49    let width = (value.chars().count() as i32 * 9).max(1);
50    let rect = UiRect::new(0.0, 0.0, width as f32, height as f32);
51    text(rect, value, TextStyle::new(Color::WHITE, 16.0, 400))
52        .with_layout(LayoutSpec::Fixed(Size::new(width as f32, height as f32)))
53}
54
55pub fn fragment(content: impl IntoElementContent) -> Fragment {
56    let mut children = Vec::new();
57    content.append_to(&mut children);
58    Fragment { children }
59}
60
61/// Provides a typed context value while its declarative descendants are compiled.
62///
63/// The provider is itself a retained component boundary. This keeps the provider stack
64/// active for lazily compiled child components and gives context dependency tracking a
65/// stable owner independent of the rendering backend.
66#[track_caller]
67pub fn context_provider<T>(value: T, content: impl IntoElementContent + 'static) -> Element
68where
69    T: Clone + PartialEq + 'static,
70{
71    let mut children = Vec::new();
72    content.append_to(&mut children);
73    component(value, move |_cx, value| {
74        let value = value.clone();
75        Element::new(move |mut cx: ElementRenderCx<'_, '_, '_>| {
76            let _provider =
77                cx.context
78                    .contexts()
79                    .provide(cx.component_id, value, cx.context.component_tree());
80            let children = cx.compile_deferred_children();
81            let bounds = children
82                .iter()
83                .map(ui_element_paint_bounds)
84                .reduce(UiRect::union)
85                .unwrap_or_else(|| UiRect::new(0.0, 0.0, 0.0, 0.0));
86            UiElement::group(cx.id, cx.context.viewport())
87                .paint_bounds(bounds)
88                .children(children)
89        })
90        .defer_children_compile()
91        .children(children)
92    })
93}
94
95fn ui_element_paint_bounds(element: &UiElement) -> UiRect {
96    element
97        .children_ref()
98        .iter()
99        .map(ui_element_paint_bounds)
100        .fold(element.node().paint_bounds, UiRect::union)
101}
102
103pub fn ellipse(rect: UiRect, style: VisualStyle) -> Element {
104    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| UiElement::ellipse(cx.id, rect, style))
105}
106
107pub fn glow(rect: UiRect, color: Color, alpha: u8) -> Element {
108    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| UiElement::glow(cx.id, rect, color, alpha))
109}
110
111pub fn line(rect: UiRect, style: VisualStyle) -> Element {
112    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| UiElement::line(cx.id, rect, style))
113}
114
115pub fn overlay(rect: UiRect, style: OverlayStyle) -> Element {
116    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| UiElement::overlay(cx.id, rect, style))
117}
118
119pub fn clip(rect: UiRect, offset_x: f32, offset_y: f32) -> Element {
120    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| {
121        UiElement::clip(cx.id, rect, offset_x, offset_y).children(cx.children)
122    })
123}
124
125pub fn clip_path(rect: UiRect, path: UiPath) -> Element {
126    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| {
127        UiElement::clip_path(cx.id, rect, path).children(cx.children)
128    })
129}
130
131pub fn path(rect: UiRect, path: UiPath, style: PathStyle) -> Element {
132    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| UiElement::path(cx.id, rect, path, style))
133}
134
135pub fn precompiled(element: UiElement) -> Element {
136    Element::new(move |_| element)
137}
138
139/// Declares a real component boundary whose hooks, children and compiled output are retained.
140/// Unchanged props and clean dependencies reuse the committed output without executing `render`.
141#[track_caller]
142pub fn component<P, F>(props: P, render: F) -> Element
143where
144    P: PartialEq + 'static,
145    F: for<'a, 'ctx> FnOnce(&mut RenderCx<'a, 'ctx>, &P) -> Element + 'static,
146{
147    let location = Location::caller();
148    let callsite = location_hash(location);
149    Element::new(move |cx: ElementRenderCx<'_, '_, '_>| {
150        let identity = stable_hash(cx.id.as_str());
151        let component_id = cx.context.component_tree().keyed_child(
152            cx.component_id,
153            callsite,
154            identity,
155            type_name::<F>(),
156        );
157        if cx.force_components {
158            cx.context.component_tree().mark_dirty(component_id);
159        }
160        if cx.context.component_tree().can_reuse(component_id, &props) {
161            cx.context.preserve_component_state_scope(cx.scope);
162            return cx
163                .context
164                .component_tree()
165                .reuse_output(component_id)
166                .into_retained_boundary(component_id);
167        }
168
169        cx.context.contexts().begin_component(component_id);
170        let force_children = cx
171            .context
172            .component_tree()
173            .begin_component_execution(component_id);
174        let mut render_cx =
175            RenderCx::for_component(cx.scope, cx.context, component_id, force_children);
176        let _current_context = cx.context.contexts().enter_current(component_id);
177        let view = render(&mut render_cx, &props);
178        let output = render_cx
179            .compile(view)
180            .claim_component_owner(component_id)
181            .component_boundary(component_id);
182        cx.context
183            .component_tree()
184            .commit_output(component_id, props, output.clone());
185        output
186    })
187}