Skip to main content

gpui_rhai/
renderer.rs

1use std::cell::RefCell;
2use std::collections::{BTreeMap, BTreeSet};
3use std::rc::Rc;
4use std::sync::{Arc, OnceLock};
5use std::time::Instant;
6
7use gpui::{
8    AlignSelf as GpuiAlignSelf, AnyElement, App, Background, Bounds, BoxShadow, ClickEvent,
9    ContentMask, Context, CursorStyle, Div, Element, ElementId, FocusHandle, FontFallbacks,
10    FontFeatures, FontStyle, FontWeight, GlobalElementId, HighlightStyle, Img, InspectorElementId,
11    InteractiveElement, IntoElement, LayoutId, Modifiers, MouseButton, MouseDownEvent,
12    MouseMoveEvent, MouseUpEvent, ParentElement, Pixels, Point, Render, ScrollHandle,
13    ScrollWheelEvent, SharedString, Stateful, StatefulInteractiveElement, Styled, StyledText,
14    TextAlign, Window, auto, div, img, linear_color_stop, linear_gradient, point, px, relative,
15    rems, rgba,
16};
17
18use crate::overlay_element::{ScriptLayerElement, ScriptOverlayElement, WindowOverlayCoordinator};
19use crate::slot_runtime::NodeSlotRuntime;
20use crate::virtual_list_element::VirtualListEntityElement;
21use crate::{
22    Align, AssetRegistry, ColorValue, CursorKind, DisplayMode, EventPropagation, EventResponse,
23    FlexDirection, FlexWrapMode, FontSlant, HitTestBehavior, ImageSourceSpec, InteractionState,
24    Justify, LayoutLength, Length, MotionKey, MotionProperty, NodeId, OverflowMode,
25    OverlayNodeSpec, PositionMode, PrimitiveRegistry, PseudoState, RadiusToken, RetainedUiTree,
26    Rgba8, ScriptCallback, SignedLength, SpacingToken, Style, StyleProperties, TextAlignMode,
27    TextDirection, UiEventHandler, UiNode, UiNodeKind, UiValue, WhiteSpaceMode,
28};
29
30type DispatchFn = dyn Fn(
31    ScriptCallback,
32    UiValue,
33    Option<crate::GeometryBounds>,
34    &mut Window,
35    &mut App,
36) -> EventResponse;
37type NativeDispatchFn = dyn Fn(
38    crate::NativeHandlerRef,
39    String,
40    UiValue,
41    Option<crate::GeometryBounds>,
42    &mut Window,
43    &mut App,
44) -> EventResponse;
45type SignalWriteFn = dyn Fn(
46    Vec<(crate::NativeSignal, crate::SignalValue)>,
47    &mut App,
48) -> Result<bool, crate::SignalError>;
49type SignalReadFn =
50    dyn Fn(&crate::NativeSignal, &App) -> Result<crate::SignalValue, crate::SignalError>;
51type ElementBoundsFn = dyn Fn(&crate::ElementRef, &App) -> Option<crate::GeometryBounds>;
52type CanvasLocalPointFn = dyn Fn(&crate::ElementRef, (f64, f64), &App) -> Option<(f64, f64)>;
53
54#[derive(Clone)]
55pub struct NodeEventDispatcher {
56    script: Rc<DispatchFn>,
57    native: Rc<NativeDispatchFn>,
58    signal_write: Rc<SignalWriteFn>,
59    signal_read: Rc<SignalReadFn>,
60    element_bounds: Rc<ElementBoundsFn>,
61    canvas_bounds: Rc<ElementBoundsFn>,
62    canvas_local_point: Rc<CanvasLocalPointFn>,
63}
64
65impl NodeEventDispatcher {
66    #[must_use]
67    pub fn new<R>(
68        dispatch: impl Fn(
69            ScriptCallback,
70            UiValue,
71            Option<crate::GeometryBounds>,
72            &mut Window,
73            &mut App,
74        ) -> R
75        + 'static,
76    ) -> Self
77    where
78        R: Into<EventResponse>,
79    {
80        Self {
81            script: Rc::new(move |callback, payload, target, window, app| {
82                dispatch(callback, payload, target, window, app).into()
83            }),
84            native: Rc::new(|_, _, _, _, _, _| EventResponse::new().stop()),
85            signal_write: Rc::new(|updates, _| {
86                updates.first().map_or(Ok(false), |(signal, _)| {
87                    Err(crate::SignalError::Stale(signal.id().clone()))
88                })
89            }),
90            signal_read: Rc::new(|signal, _| Err(crate::SignalError::Stale(signal.id().clone()))),
91            element_bounds: Rc::new(|_, _| None),
92            canvas_bounds: Rc::new(|_, _| None),
93            canvas_local_point: Rc::new(|_, _, _| None),
94        }
95    }
96
97    #[must_use]
98    pub fn with_native<R>(
99        mut self,
100        dispatch: impl Fn(
101            crate::NativeHandlerRef,
102            String,
103            UiValue,
104            Option<crate::GeometryBounds>,
105            &mut Window,
106            &mut App,
107        ) -> R
108        + 'static,
109    ) -> Self
110    where
111        R: Into<EventResponse>,
112    {
113        self.native = Rc::new(move |handler, event, payload, target, window, app| {
114            dispatch(handler, event, payload, target, window, app).into()
115        });
116        self
117    }
118
119    pub(crate) fn with_signal_write(
120        mut self,
121        write: impl Fn(
122            Vec<(crate::NativeSignal, crate::SignalValue)>,
123            &mut App,
124        ) -> Result<bool, crate::SignalError>
125        + 'static,
126    ) -> Self {
127        self.signal_write = Rc::new(write);
128        self
129    }
130
131    pub(crate) fn with_signal_read(
132        mut self,
133        read: impl Fn(&crate::NativeSignal, &App) -> Result<crate::SignalValue, crate::SignalError>
134        + 'static,
135    ) -> Self {
136        self.signal_read = Rc::new(read);
137        self
138    }
139
140    pub(crate) fn with_element_bounds(
141        mut self,
142        read: impl Fn(&crate::ElementRef, &App) -> Option<crate::GeometryBounds> + 'static,
143    ) -> Self {
144        self.element_bounds = Rc::new(read);
145        self
146    }
147
148    pub(crate) fn with_canvas_local_point(
149        mut self,
150        read: impl Fn(&crate::ElementRef, (f64, f64), &App) -> Option<(f64, f64)> + 'static,
151    ) -> Self {
152        self.canvas_local_point = Rc::new(read);
153        self
154    }
155
156    pub(crate) fn with_canvas_bounds(
157        mut self,
158        read: impl Fn(&crate::ElementRef, &App) -> Option<crate::GeometryBounds> + 'static,
159    ) -> Self {
160        self.canvas_bounds = Rc::new(read);
161        self
162    }
163
164    pub(crate) fn canvas_bounds(
165        &self,
166        reference: &crate::ElementRef,
167        app: &App,
168    ) -> Option<crate::GeometryBounds> {
169        (self.canvas_bounds)(reference, app)
170    }
171
172    pub(crate) fn dispatch(
173        &self,
174        callback: ScriptCallback,
175        payload: UiValue,
176        target: Option<crate::GeometryBounds>,
177        window: &mut Window,
178        cx: &mut App,
179    ) -> EventResponse {
180        (self.script)(callback, payload, target, window, cx)
181    }
182
183    pub(crate) fn dispatch_native(
184        &self,
185        handler: crate::NativeHandlerRef,
186        event: String,
187        payload: UiValue,
188        target: Option<crate::GeometryBounds>,
189        window: &mut Window,
190        app: &mut App,
191    ) -> EventResponse {
192        (self.native)(handler, event, payload, target, window, app)
193    }
194
195    pub(crate) fn write_signal(
196        &self,
197        signal: crate::NativeSignal,
198        value: crate::SignalValue,
199        app: &mut App,
200    ) -> Result<bool, crate::SignalError> {
201        self.write_signals(vec![(signal, value)], app)
202    }
203
204    pub(crate) fn write_signals(
205        &self,
206        updates: Vec<(crate::NativeSignal, crate::SignalValue)>,
207        app: &mut App,
208    ) -> Result<bool, crate::SignalError> {
209        if updates.is_empty() {
210            return Ok(false);
211        }
212        (self.signal_write)(updates, app)
213    }
214
215    pub(crate) fn read_signal(
216        &self,
217        signal: &crate::NativeSignal,
218        app: &App,
219    ) -> Result<crate::SignalValue, crate::SignalError> {
220        (self.signal_read)(signal, app)
221    }
222
223    pub(crate) fn element_bounds(
224        &self,
225        reference: &crate::ElementRef,
226        app: &App,
227    ) -> Option<crate::GeometryBounds> {
228        (self.element_bounds)(reference, app)
229    }
230
231    pub(crate) fn canvas_local_point(
232        &self,
233        reference: &crate::ElementRef,
234        point: (f64, f64),
235        app: &App,
236    ) -> Option<(f64, f64)> {
237        (self.canvas_local_point)(reference, point, app)
238    }
239}
240
241fn dispatch_ui_event(
242    handler: &UiEventHandler,
243    event: &str,
244    payload: UiValue,
245    target: Option<crate::GeometryBounds>,
246    window: &mut Window,
247    app: &mut App,
248    script_dispatcher: Option<&NodeEventDispatcher>,
249) -> EventResponse {
250    match handler {
251        UiEventHandler::Script(callback) => script_dispatcher.map_or_else(
252            || EventResponse::new().stop(),
253            |dispatcher| dispatcher.dispatch(callback.clone(), payload, target, window, app),
254        ),
255        UiEventHandler::Host(callback) => callback.invoke(payload, window, app),
256        UiEventHandler::Native(reference) => script_dispatcher.map_or_else(
257            || EventResponse::new().stop(),
258            |dispatcher| {
259                dispatcher.dispatch_native(
260                    reference.clone(),
261                    event.to_owned(),
262                    payload,
263                    target,
264                    window,
265                    app,
266                )
267            },
268        ),
269    }
270}
271
272fn dispatch_ui_handlers(
273    bindings: &[crate::UiEventBinding],
274    event: &str,
275    payload: &UiValue,
276    target: Option<crate::GeometryBounds>,
277    window: &mut Window,
278    app: &mut App,
279    script_dispatcher: Option<&NodeEventDispatcher>,
280) -> EventResponse {
281    dispatch_ui_handler_phases(
282        bindings,
283        event,
284        &[crate::EventPhase::Target],
285        payload,
286        EventRoute::new(target, script_dispatcher),
287        window,
288        app,
289    )
290}
291
292#[derive(Clone, Copy)]
293struct EventRoute<'a> {
294    target: Option<crate::GeometryBounds>,
295    dispatcher: Option<&'a NodeEventDispatcher>,
296}
297
298impl<'a> EventRoute<'a> {
299    const fn new(
300        target: Option<crate::GeometryBounds>,
301        dispatcher: Option<&'a NodeEventDispatcher>,
302    ) -> Self {
303        Self { target, dispatcher }
304    }
305}
306
307fn dispatch_ui_handler_phases(
308    bindings: &[crate::UiEventBinding],
309    event: &str,
310    phases: &[crate::EventPhase],
311    payload: &UiValue,
312    route: EventRoute<'_>,
313    window: &mut Window,
314    app: &mut App,
315) -> EventResponse {
316    let mut combined = EventResponse::new();
317    for phase in phases {
318        let mut stop_route = false;
319        for binding in bindings.iter().filter(|binding| binding.phase() == *phase) {
320            let response = dispatch_ui_event(
321                binding.handler(),
322                event,
323                payload.clone(),
324                route.target,
325                window,
326                app,
327                route.dispatcher,
328            );
329            combined.merge(response);
330            if matches!(
331                response.propagation(),
332                crate::PropagationControl::StopImmediate
333            ) {
334                return combined;
335            }
336            stop_route |= matches!(response.propagation(), crate::PropagationControl::Stop);
337        }
338        if stop_route {
339            return combined;
340        }
341    }
342    combined
343}
344
345fn apply_event_response(response: EventResponse, window: &mut Window, app: &mut App) {
346    if response.default_prevented() {
347        window.prevent_default();
348    }
349    if response.stops_propagation() {
350        app.stop_propagation();
351    }
352}
353
354fn apply_pointer_response(
355    response: EventResponse,
356    node: Option<NodeId>,
357    pointer_id: u64,
358    captures: &crate::PointerCaptureRegistry,
359    window: &mut Window,
360    app: &mut App,
361) {
362    match response.pointer_capture() {
363        crate::PointerCaptureDirective::Capture => {
364            if let Some(node) = node {
365                captures.capture(pointer_id, node);
366            }
367        }
368        crate::PointerCaptureDirective::Release => {
369            captures.release(pointer_id);
370        }
371        crate::PointerCaptureDirective::None => {}
372    }
373    apply_event_response(response, window, app);
374}
375
376fn key_handler_bindings(node: &UiNode) -> BTreeMap<String, (Vec<crate::UiEventBinding>, UiValue)> {
377    if is_disabled(node) {
378        return BTreeMap::new();
379    }
380    node.handlers()
381        .iter()
382        .filter_map(|(event, bindings)| {
383            event.strip_prefix("key:").map(|key| {
384                (
385                    key.to_owned(),
386                    (
387                        bindings.clone(),
388                        node.handler_payload(event)
389                            .cloned()
390                            .unwrap_or(UiValue::Null),
391                    ),
392                )
393            })
394        })
395        .collect()
396}
397
398fn node_scrollable(node: &UiNode) -> bool {
399    matches!(node.style().base.overflow_x, Some(OverflowMode::Scroll))
400        || matches!(node.style().base.overflow_y, Some(OverflowMode::Scroll))
401}
402
403fn scroll_handles_for_node(
404    tree: Option<&RetainedUiTree>,
405    node: Option<NodeId>,
406    handles: &BTreeMap<NodeId, ScrollHandle>,
407) -> Vec<ScrollHandle> {
408    let (Some(tree), Some(mut node)) = (tree, node) else {
409        return Vec::new();
410    };
411    let mut resolved = Vec::new();
412    while let Some(retained) = tree.node(node) {
413        if retained.scrollable()
414            && let Some(handle) = handles.get(&node)
415        {
416            resolved.push(handle.clone());
417        }
418        let Some(parent) = retained.parent() else {
419            break;
420        };
421        node = parent;
422    }
423    resolved
424}
425
426fn node_has_raw_pointer_handlers(node: &UiNode) -> bool {
427    ["pointer_down", "pointer_up", "pointer_move", "wheel"]
428        .into_iter()
429        .any(|event| !node.event_handlers(event).is_empty())
430}
431
432#[derive(Clone)]
433struct PointerPayloadContext {
434    node: Option<NodeId>,
435    geometry: crate::GeometryRegistry,
436    canvas: Option<crate::CanvasScene>,
437    scroll_handles: Vec<ScrollHandle>,
438}
439
440#[derive(Clone)]
441struct EventTargetContext {
442    node: Option<NodeId>,
443    geometry: crate::GeometryRegistry,
444}
445
446impl EventTargetContext {
447    fn new(node: Option<NodeId>, geometry: crate::GeometryRegistry) -> Self {
448        Self { node, geometry }
449    }
450
451    fn snapshot(&self) -> Option<crate::GeometryBounds> {
452        self.node
453            .and_then(|node| self.geometry.get(node))
454            .map(|geometry| geometry.visual)
455    }
456}
457
458impl PointerPayloadContext {
459    fn target_bounds(&self) -> Option<crate::GeometryBounds> {
460        self.node
461            .and_then(|node| self.geometry.get(node))
462            .map(|geometry| geometry.visual)
463    }
464
465    fn new(
466        node: &UiNode,
467        retained_id: Option<NodeId>,
468        geometry: crate::GeometryRegistry,
469        scroll_handles: Vec<ScrollHandle>,
470    ) -> Self {
471        let canvas = match node.kind() {
472            UiNodeKind::Canvas { scene } => Some(scene.clone()),
473            _ => None,
474        };
475        Self {
476            node: retained_id,
477            geometry,
478            canvas,
479            scroll_handles,
480        }
481    }
482
483    fn retained(
484        node: NodeId,
485        geometry: crate::GeometryRegistry,
486        canvas: Option<crate::CanvasScene>,
487        scroll_handles: Vec<ScrollHandle>,
488    ) -> Self {
489        Self {
490            node: Some(node),
491            geometry,
492            canvas,
493            scroll_handles,
494        }
495    }
496
497    fn enrich(&self, payload: UiValue) -> UiValue {
498        let UiValue::Map(mut payload) = payload else {
499            return payload;
500        };
501        let geometry = self.node.and_then(|node| self.geometry.get(node));
502        payload.insert(
503            "target".to_owned(),
504            geometry.map_or(UiValue::Null, |geometry| geometry.visual.into_value()),
505        );
506        let window = payload.get("window").and_then(value_point);
507        let local = geometry.and_then(|geometry| {
508            window.map(|(x, y)| (x - geometry.visual.x, y - geometry.visual.y))
509        });
510        if let Some((x, y)) = local {
511            payload.insert("local".to_owned(), logical_point_value(x, y));
512            let offset = self
513                .scroll_handles
514                .iter()
515                .map(ScrollHandle::offset)
516                .fold(point(px(0.0), px(0.0)), |total, offset| {
517                    point(total.x + offset.x, total.y + offset.y)
518                });
519            payload.insert(
520                "content".to_owned(),
521                logical_point_value(x - f64::from(offset.x), y - f64::from(offset.y)),
522            );
523            payload.insert(
524                "canvas_key".to_owned(),
525                self.canvas
526                    .as_ref()
527                    .and_then(|scene| {
528                        let geometry = self
529                            .node
530                            .and_then(|node| self.geometry.canvas_drawable(node))
531                            .or(geometry)?;
532                        let (window_x, window_y) = window?;
533                        let transform = self
534                            .node
535                            .map(|node| self.geometry.canvas_transform(node))
536                            .unwrap_or_default();
537                        scene.hit_test_presented(
538                            window_x - geometry.visual.x,
539                            window_y - geometry.visual.y,
540                            geometry.layout.width,
541                            geometry.layout.height,
542                            transform,
543                        )
544                    })
545                    .map_or(UiValue::Null, |key| UiValue::String(key.to_owned())),
546            );
547        }
548        UiValue::Map(payload)
549    }
550}
551
552fn value_point(value: &UiValue) -> Option<(f64, f64)> {
553    let UiValue::Map(point) = value else {
554        return None;
555    };
556    match (point.get("x"), point.get("y")) {
557        (Some(UiValue::Float(x)), Some(UiValue::Float(y))) => Some((*x, *y)),
558        _ => None,
559    }
560}
561
562fn logical_point_value(x: f64, y: f64) -> UiValue {
563    UiValue::Map(BTreeMap::from([
564        ("x".to_owned(), UiValue::Float(x)),
565        ("y".to_owned(), UiValue::Float(y)),
566    ]))
567}
568
569fn apply_scroll_behavior(
570    mut element: Stateful<Div>,
571    node: &UiNode,
572    retained_id: Option<NodeId>,
573    handles: &BTreeMap<NodeId, ScrollHandle>,
574    anchors: &BTreeMap<NodeId, gpui::ScrollAnchor>,
575) -> Stateful<Div> {
576    let scrolls_x = matches!(node.style().base.overflow_x, Some(OverflowMode::Scroll));
577    let scrolls_y = matches!(node.style().base.overflow_y, Some(OverflowMode::Scroll));
578    if scrolls_x {
579        element = element.overflow_x_scroll();
580    }
581    if scrolls_y {
582        element = element.overflow_y_scroll();
583    }
584    // GPUI translates an unsupported wheel axis onto the one scrollable axis
585    // by default. Ordinary one-axis UI containers promise a stricter contract;
586    // two-axis canvases retain GPUI's native gesture handling.
587    if scrolls_x ^ scrolls_y {
588        element = element.restrict_scroll_to_axis();
589    }
590    if let Some(handle) = retained_id.and_then(|node| handles.get(&node)) {
591        element = element.track_scroll(handle);
592    }
593    if let Some(anchor) = retained_id.and_then(|node| anchors.get(&node)) {
594        element = element.anchor_scroll(Some(anchor.clone()));
595    }
596    element
597}
598
599fn apply_raw_pointer_handlers(
600    element: Stateful<Div>,
601    node: &UiNode,
602    dispatcher: Option<&NodeEventDispatcher>,
603    retained_id: Option<NodeId>,
604    captures: &crate::PointerCaptureRegistry,
605    geometry: &crate::GeometryRegistry,
606    scroll_handles: Vec<ScrollHandle>,
607) -> Stateful<Div> {
608    let payload = PointerPayloadContext::new(node, retained_id, geometry.clone(), scroll_handles);
609    let element =
610        apply_pointer_down_handlers(element, node, dispatcher, retained_id, captures, &payload);
611    let element =
612        apply_pointer_up_handlers(element, node, dispatcher, retained_id, captures, &payload);
613    apply_pointer_motion_handlers(
614        element,
615        node,
616        dispatcher.cloned(),
617        retained_id,
618        captures.clone(),
619        &payload,
620    )
621}
622
623fn apply_hover_handler(
624    element: Stateful<Div>,
625    hover: Option<(Vec<crate::UiEventBinding>, Option<UiValue>)>,
626    dispatcher: Option<NodeEventDispatcher>,
627    target: EventTargetContext,
628) -> Stateful<Div> {
629    element.on_hover(move |hovered, window, cx| {
630        if let Some((bindings, value)) = &hover {
631            let payload = value.as_ref().map_or_else(
632                || UiValue::Bool(*hovered),
633                |value| {
634                    UiValue::Map(BTreeMap::from([
635                        ("hovered".to_owned(), UiValue::Bool(*hovered)),
636                        ("value".to_owned(), value.clone()),
637                    ]))
638                },
639            );
640            let response = dispatch_ui_handlers(
641                bindings,
642                "hover_change",
643                &payload,
644                target.snapshot(),
645                window,
646                cx,
647                dispatcher.as_ref(),
648            );
649            apply_event_response(response, window, cx);
650        }
651    })
652}
653
654fn apply_motion_trigger_handlers(
655    mut element: Stateful<Div>,
656    node: Option<NodeId>,
657    bindings: &[crate::MotionProgressBinding],
658    geometry: &crate::GeometryRegistry,
659) -> Stateful<Div> {
660    let Some(node) = node else {
661        return element;
662    };
663    if bindings
664        .iter()
665        .any(|binding| binding.driver == crate::MotionProgressDriver::Hover)
666    {
667        let geometry = geometry.clone();
668        element = element.on_hover(move |hovered, window, _| {
669            geometry.set_motion_trigger(node, crate::MotionProgressDriver::Hover, *hovered);
670            window.request_animation_frame();
671        });
672    }
673    if bindings
674        .iter()
675        .any(|binding| binding.driver == crate::MotionProgressDriver::Press)
676    {
677        let down_geometry = geometry.clone();
678        element = element.on_any_mouse_down(move |_, window, _| {
679            down_geometry.set_motion_trigger(node, crate::MotionProgressDriver::Press, true);
680            window.request_animation_frame();
681        });
682        for button in MouseButton::all() {
683            let up_geometry = geometry.clone();
684            element = element.on_mouse_up(button, move |_, window, _| {
685                up_geometry.set_motion_trigger(node, crate::MotionProgressDriver::Press, false);
686                window.request_animation_frame();
687            });
688        }
689    }
690    element
691}
692
693fn apply_pointer_down_handlers(
694    mut element: Stateful<Div>,
695    node: &UiNode,
696    dispatcher: Option<&NodeEventDispatcher>,
697    retained_id: Option<NodeId>,
698    captures: &crate::PointerCaptureRegistry,
699    payload_context: &PointerPayloadContext,
700) -> Stateful<Div> {
701    let pointer_down = node.event_handlers("pointer_down").to_vec();
702    if pointer_down
703        .iter()
704        .any(|binding| binding.phase() == crate::EventPhase::Capture)
705    {
706        let bindings = pointer_down.clone();
707        let dispatcher = dispatcher.cloned();
708        let captures = captures.clone();
709        let payload_context = (*payload_context).clone();
710        element = element.capture_any_mouse_down(move |event, window, app| {
711            let payload = payload_context.enrich(mouse_down_payload(event));
712            let response = dispatch_ui_handler_phases(
713                &bindings,
714                "pointer_down",
715                &[crate::EventPhase::Capture],
716                &payload,
717                EventRoute::new(payload_context.target_bounds(), dispatcher.as_ref()),
718                window,
719                app,
720            );
721            apply_pointer_response(response, retained_id, 0, &captures, window, app);
722        });
723    }
724    if pointer_down.iter().any(|binding| {
725        matches!(
726            binding.phase(),
727            crate::EventPhase::Target | crate::EventPhase::Bubble
728        )
729    }) {
730        let bindings = pointer_down;
731        let dispatcher = dispatcher.cloned();
732        let captures = captures.clone();
733        let payload_context = (*payload_context).clone();
734        element = element.on_any_mouse_down(move |event, window, app| {
735            let payload = payload_context.enrich(mouse_down_payload(event));
736            let response = dispatch_ui_handler_phases(
737                &bindings,
738                "pointer_down",
739                &[crate::EventPhase::Target, crate::EventPhase::Bubble],
740                &payload,
741                EventRoute::new(payload_context.target_bounds(), dispatcher.as_ref()),
742                window,
743                app,
744            );
745            apply_pointer_response(response, retained_id, 0, &captures, window, app);
746        });
747    }
748    element
749}
750
751fn apply_pointer_up_handlers(
752    mut element: Stateful<Div>,
753    node: &UiNode,
754    dispatcher: Option<&NodeEventDispatcher>,
755    retained_id: Option<NodeId>,
756    captures: &crate::PointerCaptureRegistry,
757    payload_context: &PointerPayloadContext,
758) -> Stateful<Div> {
759    let pointer_up = node.event_handlers("pointer_up").to_vec();
760    if pointer_up
761        .iter()
762        .any(|binding| binding.phase() == crate::EventPhase::Capture)
763    {
764        let bindings = pointer_up.clone();
765        let dispatcher = dispatcher.cloned();
766        let captures = captures.clone();
767        let payload_context = (*payload_context).clone();
768        element = element.capture_any_mouse_up(move |event, window, app| {
769            let payload = payload_context.enrich(mouse_up_payload(event));
770            let response = dispatch_ui_handler_phases(
771                &bindings,
772                "pointer_up",
773                &[crate::EventPhase::Capture],
774                &payload,
775                EventRoute::new(payload_context.target_bounds(), dispatcher.as_ref()),
776                window,
777                app,
778            );
779            apply_pointer_response(response, retained_id, 0, &captures, window, app);
780            captures.release(0);
781        });
782    }
783    if pointer_up.iter().any(|binding| {
784        matches!(
785            binding.phase(),
786            crate::EventPhase::Target | crate::EventPhase::Bubble
787        )
788    }) {
789        for button in MouseButton::all() {
790            let bindings = pointer_up.clone();
791            let dispatcher = dispatcher.cloned();
792            let captures = captures.clone();
793            let payload_context = (*payload_context).clone();
794            element = element.on_mouse_up(button, move |event, window, app| {
795                let payload = payload_context.enrich(mouse_up_payload(event));
796                let response = dispatch_ui_handler_phases(
797                    &bindings,
798                    "pointer_up",
799                    &[crate::EventPhase::Target, crate::EventPhase::Bubble],
800                    &payload,
801                    EventRoute::new(payload_context.target_bounds(), dispatcher.as_ref()),
802                    window,
803                    app,
804                );
805                apply_pointer_response(response, retained_id, 0, &captures, window, app);
806                captures.release(0);
807            });
808        }
809    }
810    element
811}
812
813fn apply_pointer_motion_handlers(
814    mut element: Stateful<Div>,
815    node: &UiNode,
816    dispatcher: Option<NodeEventDispatcher>,
817    retained_id: Option<NodeId>,
818    captures: crate::PointerCaptureRegistry,
819    payload_context: &PointerPayloadContext,
820) -> Stateful<Div> {
821    let pointer_move = node.event_handlers("pointer_move").to_vec();
822    if !pointer_move.is_empty() {
823        let dispatcher = dispatcher.clone();
824        let captures = captures.clone();
825        let payload_context = (*payload_context).clone();
826        let last_pointer = Rc::new(RefCell::new(None::<(crate::LogicalPoint, f64)>));
827        element = element.on_mouse_move(move |event, window, app| {
828            let payload = pointer_motion_payload(
829                mouse_move_payload(event),
830                logical_point(event.position),
831                &last_pointer,
832            );
833            let payload = payload_context.enrich(payload);
834            let response = dispatch_ui_handler_phases(
835                &pointer_move,
836                "pointer_move",
837                &[crate::EventPhase::Target, crate::EventPhase::Bubble],
838                &payload,
839                EventRoute::new(payload_context.target_bounds(), dispatcher.as_ref()),
840                window,
841                app,
842            );
843            apply_pointer_response(response, retained_id, 0, &captures, window, app);
844        });
845    }
846
847    let wheel = node.event_handlers("wheel").to_vec();
848    if !wheel.is_empty() {
849        let payload_context = (*payload_context).clone();
850        element = element.on_scroll_wheel(move |event, window, app| {
851            let payload = payload_context.enrich(wheel_payload(event));
852            let response = dispatch_ui_handler_phases(
853                &wheel,
854                "wheel",
855                &[crate::EventPhase::Target, crate::EventPhase::Bubble],
856                &payload,
857                EventRoute::new(payload_context.target_bounds(), dispatcher.as_ref()),
858                window,
859                app,
860            );
861            apply_pointer_response(response, retained_id, 0, &captures, window, app);
862        });
863    }
864    element
865}
866
867fn pointer_motion_payload(
868    payload: UiValue,
869    position: crate::LogicalPoint,
870    last: &Rc<RefCell<Option<(crate::LogicalPoint, f64)>>>,
871) -> UiValue {
872    let UiValue::Map(mut payload) = payload else {
873        return payload;
874    };
875    let timestamp = event_timestamp_ms();
876    let previous = last.borrow_mut().replace((position, timestamp));
877    let (movement, velocity) = previous.map_or_else(
878        || {
879            (
880                crate::LogicalPoint::default(),
881                crate::LogicalPoint::default(),
882            )
883        },
884        |(previous, previous_timestamp)| {
885            let movement = crate::LogicalPoint {
886                x: position.x - previous.x,
887                y: position.y - previous.y,
888            };
889            let elapsed = (timestamp - previous_timestamp).max(0.001) / 1_000.0;
890            (
891                movement,
892                crate::LogicalPoint {
893                    x: movement.x / elapsed,
894                    y: movement.y / elapsed,
895                },
896            )
897        },
898    );
899    payload.insert(
900        "movement".to_owned(),
901        logical_point_value(movement.x, movement.y),
902    );
903    payload.insert(
904        "velocity".to_owned(),
905        logical_point_value(velocity.x, velocity.y),
906    );
907    payload.insert("timestamp_ms".to_owned(), UiValue::Float(timestamp));
908    UiValue::Map(payload)
909}
910
911fn mouse_down_payload(event: &MouseDownEvent) -> UiValue {
912    pointer_payload(
913        event.position,
914        Some(event.button),
915        vec![event.button],
916        event.modifiers,
917        event.click_count,
918        false,
919    )
920}
921
922fn mouse_up_payload(event: &MouseUpEvent) -> UiValue {
923    mouse_up_payload_with_capture(event, false)
924}
925
926fn mouse_up_payload_with_capture(event: &MouseUpEvent, captured: bool) -> UiValue {
927    pointer_payload(
928        event.position,
929        Some(event.button),
930        Vec::new(),
931        event.modifiers,
932        event.click_count,
933        captured,
934    )
935}
936
937fn mouse_move_payload(event: &MouseMoveEvent) -> UiValue {
938    mouse_move_payload_with_capture(event, false)
939}
940
941fn mouse_move_payload_with_capture(event: &MouseMoveEvent, captured: bool) -> UiValue {
942    pointer_payload(
943        event.position,
944        None,
945        event.pressed_button.into_iter().collect(),
946        event.modifiers,
947        0,
948        captured,
949    )
950}
951
952fn pointer_payload(
953    position: Point<Pixels>,
954    button: Option<MouseButton>,
955    buttons: Vec<MouseButton>,
956    modifiers: Modifiers,
957    click_count: usize,
958    captured: bool,
959) -> UiValue {
960    let position = logical_point(position);
961    crate::PointerEventData {
962        pointer_id: 0,
963        pointer_type: "mouse".to_owned(),
964        window: position,
965        local: position,
966        content: position,
967        movement: crate::LogicalPoint::default(),
968        velocity: crate::LogicalPoint::default(),
969        button: button.map(mouse_button_name),
970        buttons: buttons.into_iter().map(mouse_button_name).collect(),
971        modifiers: event_modifiers(modifiers),
972        click_count,
973        timestamp_ms: event_timestamp_ms(),
974        captured,
975        target: None,
976    }
977    .into_value()
978}
979
980fn wheel_payload(event: &ScrollWheelEvent) -> UiValue {
981    let position = logical_point(event.position);
982    let delta = event.delta.pixel_delta(px(16.0));
983    crate::WheelEventData {
984        window: position,
985        local: position,
986        content: position,
987        delta: logical_point(delta),
988        precise: event.delta.precise(),
989        modifiers: event_modifiers(event.modifiers),
990        timestamp_ms: event_timestamp_ms(),
991        target: None,
992    }
993    .into_value()
994}
995
996fn logical_point(point: Point<Pixels>) -> crate::LogicalPoint {
997    crate::LogicalPoint {
998        x: f64::from(point.x),
999        y: f64::from(point.y),
1000    }
1001}
1002
1003fn event_modifiers(modifiers: Modifiers) -> crate::EventModifiers {
1004    crate::EventModifiers {
1005        control: modifiers.control,
1006        alt: modifiers.alt,
1007        shift: modifiers.shift,
1008        platform: modifiers.platform,
1009        function: modifiers.function,
1010    }
1011}
1012
1013fn mouse_button_name(button: MouseButton) -> String {
1014    match button {
1015        MouseButton::Left => "left",
1016        MouseButton::Right => "right",
1017        MouseButton::Middle => "middle",
1018        MouseButton::Navigate(gpui::NavigationDirection::Back) => "back",
1019        MouseButton::Navigate(gpui::NavigationDirection::Forward) => "forward",
1020    }
1021    .to_owned()
1022}
1023
1024fn event_timestamp_ms() -> f64 {
1025    static START: OnceLock<Instant> = OnceLock::new();
1026    START.get_or_init(Instant::now).elapsed().as_secs_f64() * 1_000.0
1027}
1028
1029#[allow(clippy::too_many_arguments)]
1030pub(crate) fn pointer_capture_router_element(
1031    child: AnyElement,
1032    view_id: &str,
1033    tree: &RetainedUiTree,
1034    dispatcher: &NodeEventDispatcher,
1035    captures: &crate::PointerCaptureRegistry,
1036    geometry: &crate::GeometryRegistry,
1037    scroll_handles: &BTreeMap<NodeId, ScrollHandle>,
1038    interactions: crate::interaction::WindowInteractionCoordinator,
1039) -> AnyElement {
1040    PointerCaptureRouterElement {
1041        child: Some(child),
1042        interactions,
1043        routes: Some(PointerCaptureRoutes {
1044            view_id: view_id.to_owned(),
1045            move_handlers: retained_handlers(tree, "pointer_move"),
1046            up_handlers: retained_handlers(tree, "pointer_up"),
1047            dispatcher: dispatcher.clone(),
1048            captures: captures.clone(),
1049            payload_contexts: tree
1050                .nodes()
1051                .map(|node| {
1052                    (
1053                        node.id(),
1054                        PointerPayloadContext::retained(
1055                            node.id(),
1056                            geometry.clone(),
1057                            node.canvas_scene().cloned(),
1058                            scroll_handles_for_node(Some(tree), Some(node.id()), scroll_handles),
1059                        ),
1060                    )
1061                })
1062                .collect(),
1063        }),
1064    }
1065    .into_any_element()
1066}
1067
1068struct PointerCaptureRoutes {
1069    view_id: String,
1070    move_handlers: BTreeMap<NodeId, Vec<crate::UiEventBinding>>,
1071    up_handlers: BTreeMap<NodeId, Vec<crate::UiEventBinding>>,
1072    dispatcher: NodeEventDispatcher,
1073    captures: crate::PointerCaptureRegistry,
1074    payload_contexts: BTreeMap<NodeId, PointerPayloadContext>,
1075}
1076
1077impl PointerCaptureRoutes {
1078    fn register(self, interactions: &crate::interaction::WindowInteractionCoordinator) {
1079        let Self {
1080            view_id,
1081            move_handlers,
1082            up_handlers,
1083            dispatcher,
1084            captures,
1085            payload_contexts,
1086        } = self;
1087        let move_dispatcher = dispatcher.clone();
1088        let up_dispatcher = dispatcher;
1089        let move_captures = captures.clone();
1090        let up_captures = captures;
1091        let move_payload_contexts = payload_contexts.clone();
1092        interactions.set_pointer_routes(
1093            view_id,
1094            move |event: &MouseMoveEvent, window, app| {
1095                let Some(node) = move_captures.captured(0) else {
1096                    return false;
1097                };
1098                let Some(bindings) = move_handlers.get(&node) else {
1099                    return false;
1100                };
1101                let payload = move_payload_contexts.get(&node).map_or_else(
1102                    || mouse_move_payload_with_capture(event, true),
1103                    |context| context.enrich(mouse_move_payload_with_capture(event, true)),
1104                );
1105                let target = move_payload_contexts
1106                    .get(&node)
1107                    .and_then(PointerPayloadContext::target_bounds);
1108                let response = dispatch_ui_handler_phases(
1109                    bindings,
1110                    "pointer_move",
1111                    &[crate::EventPhase::Target, crate::EventPhase::Bubble],
1112                    &payload,
1113                    EventRoute::new(target, Some(&move_dispatcher)),
1114                    window,
1115                    app,
1116                );
1117                apply_pointer_response(response, Some(node), 0, &move_captures, window, app);
1118                true
1119            },
1120            move |event: &MouseUpEvent, window, app| {
1121                let Some(node) = up_captures.captured(0) else {
1122                    return false;
1123                };
1124                if let Some(bindings) = up_handlers.get(&node) {
1125                    let payload = payload_contexts.get(&node).map_or_else(
1126                        || mouse_up_payload_with_capture(event, true),
1127                        |context| context.enrich(mouse_up_payload_with_capture(event, true)),
1128                    );
1129                    let target = payload_contexts
1130                        .get(&node)
1131                        .and_then(PointerPayloadContext::target_bounds);
1132                    let response = dispatch_ui_handler_phases(
1133                        bindings,
1134                        "pointer_up",
1135                        &[crate::EventPhase::Target, crate::EventPhase::Bubble],
1136                        &payload,
1137                        EventRoute::new(target, Some(&up_dispatcher)),
1138                        window,
1139                        app,
1140                    );
1141                    apply_pointer_response(response, Some(node), 0, &up_captures, window, app);
1142                }
1143                up_captures.release(0);
1144                true
1145            },
1146        );
1147    }
1148}
1149
1150struct PointerCaptureRouterElement {
1151    child: Option<AnyElement>,
1152    routes: Option<PointerCaptureRoutes>,
1153    interactions: crate::interaction::WindowInteractionCoordinator,
1154}
1155
1156impl Element for PointerCaptureRouterElement {
1157    type RequestLayoutState = AnyElement;
1158    type PrepaintState = ();
1159
1160    fn id(&self) -> Option<ElementId> {
1161        None
1162    }
1163
1164    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
1165        None
1166    }
1167
1168    fn request_layout(
1169        &mut self,
1170        _id: Option<&GlobalElementId>,
1171        _inspector_id: Option<&InspectorElementId>,
1172        window: &mut Window,
1173        cx: &mut App,
1174    ) -> (LayoutId, Self::RequestLayoutState) {
1175        let mut child = self.child.take().expect("pointer router renders once");
1176        let layout = child.request_layout(window, cx);
1177        (layout, child)
1178    }
1179
1180    fn prepaint(
1181        &mut self,
1182        _id: Option<&GlobalElementId>,
1183        _inspector_id: Option<&InspectorElementId>,
1184        _bounds: Bounds<Pixels>,
1185        child: &mut Self::RequestLayoutState,
1186        window: &mut Window,
1187        cx: &mut App,
1188    ) {
1189        child.prepaint(window, cx);
1190    }
1191
1192    fn paint(
1193        &mut self,
1194        _id: Option<&GlobalElementId>,
1195        _inspector_id: Option<&InspectorElementId>,
1196        _bounds: Bounds<Pixels>,
1197        child: &mut Self::RequestLayoutState,
1198        _prepaint: &mut Self::PrepaintState,
1199        window: &mut Window,
1200        cx: &mut App,
1201    ) {
1202        self.routes
1203            .take()
1204            .expect("pointer router paints once")
1205            .register(&self.interactions);
1206        child.paint(window, cx);
1207    }
1208}
1209
1210impl IntoElement for PointerCaptureRouterElement {
1211    type Element = Self;
1212
1213    fn into_element(self) -> Self::Element {
1214        self
1215    }
1216}
1217
1218fn retained_handlers(
1219    tree: &RetainedUiTree,
1220    event: &str,
1221) -> BTreeMap<NodeId, Vec<crate::UiEventBinding>> {
1222    tree.nodes()
1223        .filter_map(|node| {
1224            let handlers = node.event_handlers(event);
1225            (!handlers.is_empty()).then(|| (node.id(), handlers.to_vec()))
1226        })
1227        .collect()
1228}
1229
1230pub trait ColorResolver {
1231    fn resolve(&self, color: &ColorValue) -> Option<Rgba8>;
1232
1233    fn color_snapshot(&self) -> BTreeMap<String, Rgba8> {
1234        crate::primitive::RUNTIME_THEME_COLOR_TOKENS
1235            .iter()
1236            .filter_map(|token| {
1237                self.resolve(&ColorValue::Token((*token).to_owned()))
1238                    .map(|value| ((*token).to_owned(), value))
1239            })
1240            .collect()
1241    }
1242
1243    fn resolve_typography(&self, role: &str) -> Option<crate::ResolvedTypography> {
1244        default_typography(role)
1245    }
1246
1247    fn resolve_length(&self, length: Length) -> Option<Length> {
1248        match length {
1249            Length::Pixels(_) | Length::Rems(_) | Length::Relative(_) => Some(length),
1250            Length::ThemeSpacing(_) | Length::ThemeRadius(_) => None,
1251        }
1252    }
1253
1254    fn resolve_motion(&self) -> crate::ThemeMotion {
1255        crate::ThemeMotion::default()
1256    }
1257}
1258
1259fn default_typography(role: &str) -> Option<crate::ResolvedTypography> {
1260    let (size, line_height, weight) = match role {
1261        "caption" => (11.0, 16.0, 400),
1262        "body_small" => (12.0, 16.0, 400),
1263        "body" => (13.0, 18.0, 400),
1264        "subtitle" => (14.0, 20.0, 400),
1265        "title" => (16.0, 22.0, 700),
1266        "heading" => (18.0, 24.0, 700),
1267        "display" => (24.0, 32.0, 700),
1268        "display_large" => (28.0, 36.0, 700),
1269        _ => return None,
1270    };
1271    Some(crate::ResolvedTypography {
1272        family: None,
1273        fallbacks: Vec::new(),
1274        size: Length::Pixels(size),
1275        line_height: Length::Pixels(line_height),
1276        weight,
1277    })
1278}
1279
1280#[derive(Clone, Copy, Debug, Default)]
1281pub struct LiteralColorResolver;
1282
1283impl ColorResolver for LiteralColorResolver {
1284    fn resolve(&self, color: &ColorValue) -> Option<Rgba8> {
1285        match color {
1286            ColorValue::Literal(color) => Some(*color),
1287            ColorValue::Token(_) => None,
1288        }
1289    }
1290}
1291
1292#[derive(Clone, Debug, Default)]
1293pub(crate) struct OwnedColorResolver {
1294    tokens: BTreeMap<String, Rgba8>,
1295    spacing: BTreeMap<SpacingToken, Length>,
1296    radii: BTreeMap<RadiusToken, Length>,
1297    typography: BTreeMap<String, crate::ResolvedTypography>,
1298    motion: crate::ThemeMotion,
1299}
1300
1301impl OwnedColorResolver {
1302    fn capture(colors: &impl ColorResolver) -> Self {
1303        let spacing = crate::primitive::RUNTIME_THEME_SPACING_TOKENS
1304            .iter()
1305            .copied()
1306            .filter_map(|token| {
1307                colors
1308                    .resolve_length(Length::ThemeSpacing(token))
1309                    .map(|value| (token, value))
1310            })
1311            .collect();
1312        let radii = crate::primitive::RUNTIME_THEME_RADIUS_TOKENS
1313            .iter()
1314            .copied()
1315            .filter_map(|token| {
1316                colors
1317                    .resolve_length(Length::ThemeRadius(token))
1318                    .map(|value| (token, value))
1319            })
1320            .collect();
1321        let typography = crate::REQUIRED_TYPOGRAPHY
1322            .iter()
1323            .filter_map(|role| {
1324                colors
1325                    .resolve_typography(role)
1326                    .map(|value| ((*role).to_owned(), value))
1327            })
1328            .collect();
1329        Self {
1330            tokens: colors.color_snapshot(),
1331            spacing,
1332            radii,
1333            typography,
1334            motion: colors.resolve_motion(),
1335        }
1336    }
1337}
1338
1339impl ColorResolver for OwnedColorResolver {
1340    fn resolve(&self, color: &ColorValue) -> Option<Rgba8> {
1341        match color {
1342            ColorValue::Literal(color) => Some(*color),
1343            ColorValue::Token(token) => self.tokens.get(token).copied(),
1344        }
1345    }
1346
1347    fn resolve_length(&self, length: Length) -> Option<Length> {
1348        match length {
1349            Length::ThemeSpacing(token) => self.spacing.get(&token).copied(),
1350            Length::ThemeRadius(token) => self.radii.get(&token).copied(),
1351            Length::Pixels(_) | Length::Rems(_) | Length::Relative(_) => Some(length),
1352        }
1353    }
1354
1355    fn color_snapshot(&self) -> BTreeMap<String, Rgba8> {
1356        self.tokens.clone()
1357    }
1358
1359    fn resolve_motion(&self) -> crate::ThemeMotion {
1360        self.motion.clone()
1361    }
1362
1363    fn resolve_typography(&self, role: &str) -> Option<crate::ResolvedTypography> {
1364        self.typography.get(role).cloned()
1365    }
1366}
1367
1368/// Converts stable runtime nodes into short-lived GPUI elements.
1369#[derive(Clone, Copy, Debug, Default)]
1370pub struct GpuiNodeRenderer;
1371
1372#[derive(Clone, Copy)]
1373struct RenderEnvironment<'a, C> {
1374    now: Instant,
1375    clock: &'a crate::RuntimeClock,
1376    motion_preference: crate::MotionPreference,
1377    motion_quality: crate::MotionQuality,
1378    colors: &'a C,
1379    interaction: &'a InteractionState,
1380    primitives: &'a PrimitiveRegistry,
1381    dispatcher: Option<&'a NodeEventDispatcher>,
1382    assets: Option<&'a AssetRegistry>,
1383    overlays: &'a WindowOverlayCoordinator,
1384    interactions: &'a crate::interaction::WindowInteractionCoordinator,
1385    motions: &'a BTreeMap<MotionKey, f64>,
1386    signals: &'a crate::SignalRegistry,
1387    geometry: &'a crate::GeometryRegistry,
1388    pointer_capture: &'a crate::PointerCaptureRegistry,
1389    focus_handles: &'a BTreeMap<NodeId, FocusHandle>,
1390    scroll_handles: &'a BTreeMap<NodeId, ScrollHandle>,
1391    scroll_anchors: &'a BTreeMap<NodeId, gpui::ScrollAnchor>,
1392    virtual_requests: &'a crate::VirtualRequestRegistry,
1393    text_selection: &'a TextSelectionRegistry,
1394    host_focus: Option<&'a FocusHandle>,
1395    direction: TextDirection,
1396    locale: &'a str,
1397    number: Option<&'a crate::NumberMetadata>,
1398    ambient_text_color: Option<Rgba8>,
1399    view_id: &'a str,
1400    retained: Option<&'a RetainedUiTree>,
1401    retained_links: Option<&'a BTreeMap<NodeId, Vec<crate::RetainedChildLink>>>,
1402    semantics: Option<&'a crate::CommittedSemanticFrame>,
1403    a11y_active: bool,
1404}
1405
1406impl<C: ColorResolver> RenderEnvironment<'_, C> {
1407    fn with_resolved_text_color(&self, style: &StyleProperties) -> Self {
1408        Self {
1409            ambient_text_color: resolve_ambient_text_color(
1410                style,
1411                self.colors,
1412                self.ambient_text_color,
1413            ),
1414            ..*self
1415        }
1416    }
1417}
1418
1419pub(crate) struct WindowRenderResources<'a> {
1420    pub now: Instant,
1421    pub clock: &'a crate::RuntimeClock,
1422    pub motion_preference: crate::MotionPreference,
1423    pub motion_quality: crate::MotionQuality,
1424    pub assets: &'a AssetRegistry,
1425    pub dispatcher: &'a NodeEventDispatcher,
1426    pub overlays: &'a WindowOverlayCoordinator,
1427    pub interactions: &'a crate::interaction::WindowInteractionCoordinator,
1428    pub motions: &'a BTreeMap<MotionKey, f64>,
1429    pub signals: &'a crate::SignalRegistry,
1430    pub geometry: &'a crate::GeometryRegistry,
1431    pub pointer_capture: &'a crate::PointerCaptureRegistry,
1432    pub focus_handles: &'a BTreeMap<NodeId, FocusHandle>,
1433    pub scroll_handles: &'a BTreeMap<NodeId, ScrollHandle>,
1434    pub scroll_anchors: &'a BTreeMap<NodeId, gpui::ScrollAnchor>,
1435    pub virtual_requests: &'a crate::VirtualRequestRegistry,
1436    pub text_selection: &'a TextSelectionRegistry,
1437    pub host_focus: Option<&'a FocusHandle>,
1438    pub direction: TextDirection,
1439    pub locale: &'a str,
1440    pub number: Option<&'a crate::NumberMetadata>,
1441    pub ambient_text_color: Option<Rgba8>,
1442    pub root_path: &'a str,
1443    pub view_id: &'a str,
1444    pub semantics: &'a crate::CommittedSemanticFrame,
1445    pub a11y_active: bool,
1446}
1447
1448#[derive(Clone, Copy)]
1449pub(crate) struct RetainedSubtree<'a> {
1450    pub root: Option<NodeId>,
1451    pub links: &'a BTreeMap<NodeId, Vec<crate::RetainedChildLink>>,
1452}
1453
1454impl GpuiNodeRenderer {
1455    #[must_use]
1456    pub fn render(node: &UiNode) -> AnyElement {
1457        Self::render_with_primitives(
1458            node,
1459            &LiteralColorResolver,
1460            &InteractionState::default(),
1461            &PrimitiveRegistry::new(),
1462        )
1463    }
1464
1465    #[must_use]
1466    pub fn render_with(
1467        node: &UiNode,
1468        colors: &impl ColorResolver,
1469        interaction: &InteractionState,
1470    ) -> AnyElement {
1471        Self::render_with_primitives(node, colors, interaction, &PrimitiveRegistry::new())
1472    }
1473
1474    #[must_use]
1475    pub fn render_with_primitives(
1476        node: &UiNode,
1477        colors: &impl ColorResolver,
1478        interaction: &InteractionState,
1479        primitives: &PrimitiveRegistry,
1480    ) -> AnyElement {
1481        let overlays = WindowOverlayCoordinator::default();
1482        let interactions = crate::interaction::WindowInteractionCoordinator::default();
1483        let motions = BTreeMap::new();
1484        let signals = crate::SignalRegistry::new();
1485        let geometry = crate::GeometryRegistry::new();
1486        let pointer_capture = crate::PointerCaptureRegistry::new();
1487        let focus_handles = BTreeMap::new();
1488        let scroll_handles = BTreeMap::new();
1489        let scroll_anchors = BTreeMap::new();
1490        let virtual_requests = crate::VirtualRequestRegistry::new();
1491        let text_selection = TextSelectionRegistry::default();
1492        let environment = RenderEnvironment {
1493            now: Instant::now(),
1494            clock: &crate::RuntimeClock::default(),
1495            motion_preference: crate::MotionPreference::Normal,
1496            motion_quality: crate::MotionQuality::High,
1497            colors,
1498            interaction,
1499            primitives,
1500            dispatcher: None,
1501            assets: None,
1502            overlays: &overlays,
1503            interactions: &interactions,
1504            motions: &motions,
1505            signals: &signals,
1506            geometry: &geometry,
1507            pointer_capture: &pointer_capture,
1508            focus_handles: &focus_handles,
1509            scroll_handles: &scroll_handles,
1510            scroll_anchors: &scroll_anchors,
1511            virtual_requests: &virtual_requests,
1512            text_selection: &text_selection,
1513            host_focus: None,
1514            direction: TextDirection::LeftToRight,
1515            locale: "en",
1516            number: None,
1517            ambient_text_color: None,
1518            view_id: "standalone",
1519            retained: None,
1520            retained_links: None,
1521            semantics: None,
1522            a11y_active: false,
1523        };
1524        Self::render_internal(node, &environment, None, "root", None)
1525    }
1526
1527    #[must_use]
1528    pub fn render_retained_with_primitives(
1529        tree: &RetainedUiTree,
1530        colors: &impl ColorResolver,
1531        interaction: &InteractionState,
1532        primitives: &PrimitiveRegistry,
1533    ) -> AnyElement {
1534        match crate::CommittedSemanticFrame::from_retained(tree) {
1535            Ok(semantics) => Self::render_retained_with_committed_semantics(
1536                tree,
1537                &semantics,
1538                false,
1539                colors,
1540                interaction,
1541                primitives,
1542            ),
1543            Err(error) => div()
1544                .child(format!("Invalid accessibility semantics: {error}"))
1545                .into_any_element(),
1546        }
1547    }
1548
1549    fn render_retained_with_committed_semantics(
1550        tree: &RetainedUiTree,
1551        semantics: &crate::CommittedSemanticFrame,
1552        a11y_active: bool,
1553        colors: &impl ColorResolver,
1554        interaction: &InteractionState,
1555        primitives: &PrimitiveRegistry,
1556    ) -> AnyElement {
1557        let overlays = WindowOverlayCoordinator::default();
1558        let interactions = crate::interaction::WindowInteractionCoordinator::default();
1559        let motions = BTreeMap::new();
1560        let signals = crate::SignalRegistry::new();
1561        let geometry = crate::GeometryRegistry::new();
1562        let pointer_capture = crate::PointerCaptureRegistry::new();
1563        let focus_handles = BTreeMap::new();
1564        let scroll_handles = BTreeMap::new();
1565        let scroll_anchors = BTreeMap::new();
1566        let virtual_requests = crate::VirtualRequestRegistry::new();
1567        let text_selection = TextSelectionRegistry::default();
1568        let environment = RenderEnvironment {
1569            now: Instant::now(),
1570            clock: &crate::RuntimeClock::default(),
1571            motion_preference: crate::MotionPreference::Normal,
1572            motion_quality: crate::MotionQuality::High,
1573            colors,
1574            interaction,
1575            primitives,
1576            dispatcher: None,
1577            assets: None,
1578            overlays: &overlays,
1579            interactions: &interactions,
1580            motions: &motions,
1581            signals: &signals,
1582            geometry: &geometry,
1583            pointer_capture: &pointer_capture,
1584            focus_handles: &focus_handles,
1585            scroll_handles: &scroll_handles,
1586            scroll_anchors: &scroll_anchors,
1587            virtual_requests: &virtual_requests,
1588            text_selection: &text_selection,
1589            host_focus: None,
1590            direction: TextDirection::LeftToRight,
1591            locale: "en",
1592            number: None,
1593            ambient_text_color: None,
1594            view_id: "standalone",
1595            retained: Some(tree),
1596            retained_links: None,
1597            semantics: Some(semantics),
1598            a11y_active,
1599        };
1600        tree.root().map_or_else(
1601            || {
1602                div()
1603                    .child("Retained UI tree has no root")
1604                    .into_any_element()
1605            },
1606            |root| Self::render_internal(root, &environment, None, "root", tree.root_id()),
1607        )
1608    }
1609
1610    #[must_use]
1611    pub fn render_retained_with_dispatcher(
1612        tree: &RetainedUiTree,
1613        colors: &impl ColorResolver,
1614        interaction: &InteractionState,
1615        primitives: &PrimitiveRegistry,
1616        dispatcher: &NodeEventDispatcher,
1617    ) -> AnyElement {
1618        let semantics = match crate::CommittedSemanticFrame::from_retained(tree) {
1619            Ok(semantics) => semantics,
1620            Err(error) => {
1621                return div()
1622                    .child(format!("Invalid accessibility semantics: {error}"))
1623                    .into_any_element();
1624            }
1625        };
1626        let overlays = WindowOverlayCoordinator::default();
1627        let interactions = crate::interaction::WindowInteractionCoordinator::default();
1628        let motions = BTreeMap::new();
1629        let signals = crate::SignalRegistry::new();
1630        let geometry = crate::GeometryRegistry::new();
1631        let pointer_capture = crate::PointerCaptureRegistry::new();
1632        let focus_handles = BTreeMap::new();
1633        let scroll_handles = BTreeMap::new();
1634        let scroll_anchors = BTreeMap::new();
1635        let virtual_requests = crate::VirtualRequestRegistry::new();
1636        let text_selection = TextSelectionRegistry::default();
1637        let environment = RenderEnvironment {
1638            now: Instant::now(),
1639            clock: &crate::RuntimeClock::default(),
1640            motion_preference: crate::MotionPreference::Normal,
1641            motion_quality: crate::MotionQuality::High,
1642            colors,
1643            interaction,
1644            primitives,
1645            dispatcher: Some(dispatcher),
1646            assets: None,
1647            overlays: &overlays,
1648            interactions: &interactions,
1649            motions: &motions,
1650            signals: &signals,
1651            geometry: &geometry,
1652            pointer_capture: &pointer_capture,
1653            focus_handles: &focus_handles,
1654            scroll_handles: &scroll_handles,
1655            scroll_anchors: &scroll_anchors,
1656            virtual_requests: &virtual_requests,
1657            text_selection: &text_selection,
1658            host_focus: None,
1659            direction: TextDirection::LeftToRight,
1660            locale: "en",
1661            number: None,
1662            ambient_text_color: None,
1663            view_id: "standalone",
1664            retained: Some(tree),
1665            retained_links: None,
1666            semantics: Some(&semantics),
1667            a11y_active: false,
1668        };
1669        tree.root().map_or_else(
1670            || {
1671                div()
1672                    .child("Retained UI tree has no root")
1673                    .into_any_element()
1674            },
1675            |root| Self::render_internal(root, &environment, None, "root", tree.root_id()),
1676        )
1677    }
1678
1679    #[must_use]
1680    pub fn render_with_dispatcher(
1681        node: &UiNode,
1682        colors: &impl ColorResolver,
1683        interaction: &InteractionState,
1684        primitives: &PrimitiveRegistry,
1685        dispatcher: &NodeEventDispatcher,
1686    ) -> AnyElement {
1687        let overlays = WindowOverlayCoordinator::default();
1688        let interactions = crate::interaction::WindowInteractionCoordinator::default();
1689        let motions = BTreeMap::new();
1690        let signals = crate::SignalRegistry::new();
1691        let geometry = crate::GeometryRegistry::new();
1692        let pointer_capture = crate::PointerCaptureRegistry::new();
1693        let focus_handles = BTreeMap::new();
1694        let scroll_handles = BTreeMap::new();
1695        let scroll_anchors = BTreeMap::new();
1696        let virtual_requests = crate::VirtualRequestRegistry::new();
1697        let text_selection = TextSelectionRegistry::default();
1698        let environment = RenderEnvironment {
1699            now: Instant::now(),
1700            clock: &crate::RuntimeClock::default(),
1701            motion_preference: crate::MotionPreference::Normal,
1702            motion_quality: crate::MotionQuality::High,
1703            colors,
1704            interaction,
1705            primitives,
1706            dispatcher: Some(dispatcher),
1707            assets: None,
1708            overlays: &overlays,
1709            interactions: &interactions,
1710            motions: &motions,
1711            signals: &signals,
1712            geometry: &geometry,
1713            pointer_capture: &pointer_capture,
1714            focus_handles: &focus_handles,
1715            scroll_handles: &scroll_handles,
1716            scroll_anchors: &scroll_anchors,
1717            virtual_requests: &virtual_requests,
1718            text_selection: &text_selection,
1719            host_focus: None,
1720            direction: TextDirection::LeftToRight,
1721            locale: "en",
1722            number: None,
1723            ambient_text_color: None,
1724            view_id: "standalone",
1725            retained: None,
1726            retained_links: None,
1727            semantics: None,
1728            a11y_active: false,
1729        };
1730        Self::render_internal(node, &environment, None, "root", None)
1731    }
1732
1733    #[must_use]
1734    pub fn render_with_runtime(
1735        node: &UiNode,
1736        colors: &impl ColorResolver,
1737        interaction: &InteractionState,
1738        primitives: &PrimitiveRegistry,
1739        assets: &AssetRegistry,
1740        dispatcher: &NodeEventDispatcher,
1741    ) -> AnyElement {
1742        let overlays = WindowOverlayCoordinator::default();
1743        let interactions = crate::interaction::WindowInteractionCoordinator::default();
1744        let motions = BTreeMap::new();
1745        let signals = crate::SignalRegistry::new();
1746        let geometry = crate::GeometryRegistry::new();
1747        let pointer_capture = crate::PointerCaptureRegistry::new();
1748        let focus_handles = BTreeMap::new();
1749        let scroll_handles = BTreeMap::new();
1750        let scroll_anchors = BTreeMap::new();
1751        let virtual_requests = crate::VirtualRequestRegistry::new();
1752        let text_selection = TextSelectionRegistry::default();
1753        let resources = WindowRenderResources {
1754            now: Instant::now(),
1755            clock: &crate::RuntimeClock::default(),
1756            motion_preference: crate::MotionPreference::Normal,
1757            motion_quality: crate::MotionQuality::High,
1758            assets,
1759            dispatcher,
1760            overlays: &overlays,
1761            interactions: &interactions,
1762            motions: &motions,
1763            signals: &signals,
1764            geometry: &geometry,
1765            pointer_capture: &pointer_capture,
1766            focus_handles: &focus_handles,
1767            scroll_handles: &scroll_handles,
1768            scroll_anchors: &scroll_anchors,
1769            virtual_requests: &virtual_requests,
1770            text_selection: &text_selection,
1771            host_focus: None,
1772            direction: TextDirection::LeftToRight,
1773            locale: "en",
1774            number: None,
1775            ambient_text_color: None,
1776            root_path: "root",
1777            view_id: "standalone",
1778            semantics: &crate::CommittedSemanticFrame::default(),
1779            a11y_active: false,
1780        };
1781        Self::render_with_window_runtime(node, colors, interaction, primitives, &resources)
1782    }
1783
1784    pub(crate) fn render_with_window_runtime(
1785        node: &UiNode,
1786        colors: &impl ColorResolver,
1787        interaction: &InteractionState,
1788        primitives: &PrimitiveRegistry,
1789        resources: &WindowRenderResources<'_>,
1790    ) -> AnyElement {
1791        Self::render_subtree_with_window_runtime(
1792            node,
1793            colors,
1794            interaction,
1795            primitives,
1796            resources,
1797            resources.root_path,
1798        )
1799    }
1800
1801    pub(crate) fn render_retained_with_window_runtime(
1802        tree: &RetainedUiTree,
1803        colors: &impl ColorResolver,
1804        interaction: &InteractionState,
1805        primitives: &PrimitiveRegistry,
1806        resources: &WindowRenderResources<'_>,
1807    ) -> AnyElement {
1808        let environment = RenderEnvironment {
1809            now: resources.now,
1810            clock: resources.clock,
1811            motion_preference: resources.motion_preference,
1812            motion_quality: resources.motion_quality,
1813            colors,
1814            interaction,
1815            primitives,
1816            dispatcher: Some(resources.dispatcher),
1817            assets: Some(resources.assets),
1818            overlays: resources.overlays,
1819            interactions: resources.interactions,
1820            motions: resources.motions,
1821            signals: resources.signals,
1822            geometry: resources.geometry,
1823            pointer_capture: resources.pointer_capture,
1824            focus_handles: resources.focus_handles,
1825            scroll_handles: resources.scroll_handles,
1826            scroll_anchors: resources.scroll_anchors,
1827            virtual_requests: resources.virtual_requests,
1828            text_selection: resources.text_selection,
1829            host_focus: resources.host_focus,
1830            direction: resources.direction,
1831            locale: resources.locale,
1832            number: resources.number,
1833            ambient_text_color: resources.ambient_text_color,
1834            view_id: resources.view_id,
1835            retained: Some(tree),
1836            retained_links: None,
1837            semantics: Some(resources.semantics),
1838            a11y_active: resources.a11y_active,
1839        };
1840        tree.root().map_or_else(
1841            || {
1842                div()
1843                    .child("Retained UI tree has no root")
1844                    .into_any_element()
1845            },
1846            |root| {
1847                Self::render_internal(
1848                    root,
1849                    &environment,
1850                    None,
1851                    resources.root_path,
1852                    tree.root_id(),
1853                )
1854            },
1855        )
1856    }
1857
1858    pub(crate) fn render_subtree_with_window_runtime(
1859        node: &UiNode,
1860        colors: &impl ColorResolver,
1861        interaction: &InteractionState,
1862        primitives: &PrimitiveRegistry,
1863        resources: &WindowRenderResources<'_>,
1864        path: &str,
1865    ) -> AnyElement {
1866        let environment = RenderEnvironment {
1867            now: resources.now,
1868            clock: resources.clock,
1869            motion_preference: resources.motion_preference,
1870            motion_quality: resources.motion_quality,
1871            colors,
1872            interaction,
1873            primitives,
1874            dispatcher: Some(resources.dispatcher),
1875            assets: Some(resources.assets),
1876            overlays: resources.overlays,
1877            interactions: resources.interactions,
1878            motions: resources.motions,
1879            signals: resources.signals,
1880            geometry: resources.geometry,
1881            pointer_capture: resources.pointer_capture,
1882            focus_handles: resources.focus_handles,
1883            scroll_handles: resources.scroll_handles,
1884            scroll_anchors: resources.scroll_anchors,
1885            virtual_requests: resources.virtual_requests,
1886            text_selection: resources.text_selection,
1887            host_focus: resources.host_focus,
1888            direction: resources.direction,
1889            locale: resources.locale,
1890            number: resources.number,
1891            ambient_text_color: resources.ambient_text_color,
1892            view_id: resources.view_id,
1893            retained: None,
1894            retained_links: None,
1895            semantics: Some(resources.semantics),
1896            a11y_active: resources.a11y_active,
1897        };
1898        Self::render_internal(node, &environment, None, path, None)
1899    }
1900
1901    pub(crate) fn render_subtree_with_window_runtime_at(
1902        node: &UiNode,
1903        colors: &impl ColorResolver,
1904        interaction: &InteractionState,
1905        primitives: &PrimitiveRegistry,
1906        resources: &WindowRenderResources<'_>,
1907        path: &str,
1908        retained: RetainedSubtree<'_>,
1909    ) -> AnyElement {
1910        let environment = RenderEnvironment {
1911            now: resources.now,
1912            clock: resources.clock,
1913            motion_preference: resources.motion_preference,
1914            motion_quality: resources.motion_quality,
1915            colors,
1916            interaction,
1917            primitives,
1918            dispatcher: Some(resources.dispatcher),
1919            assets: Some(resources.assets),
1920            overlays: resources.overlays,
1921            interactions: resources.interactions,
1922            motions: resources.motions,
1923            signals: resources.signals,
1924            geometry: resources.geometry,
1925            pointer_capture: resources.pointer_capture,
1926            focus_handles: resources.focus_handles,
1927            scroll_handles: resources.scroll_handles,
1928            scroll_anchors: resources.scroll_anchors,
1929            virtual_requests: resources.virtual_requests,
1930            text_selection: resources.text_selection,
1931            host_focus: resources.host_focus,
1932            direction: resources.direction,
1933            locale: resources.locale,
1934            number: resources.number,
1935            ambient_text_color: resources.ambient_text_color,
1936            view_id: resources.view_id,
1937            retained: None,
1938            retained_links: Some(retained.links),
1939            semantics: Some(resources.semantics),
1940            a11y_active: resources.a11y_active,
1941        };
1942        Self::render_internal(node, &environment, None, path, retained.root)
1943    }
1944
1945    fn render_internal<C: ColorResolver>(
1946        node: &UiNode,
1947        environment: &RenderEnvironment<'_, C>,
1948        boundary_fallback: Option<&UiNode>,
1949        path: &str,
1950        retained_id: Option<NodeId>,
1951    ) -> AnyElement {
1952        if let Some(table) = render_table_layout(node, environment, path, retained_id) {
1953            return table;
1954        }
1955        let local_interaction = if is_disabled(node) {
1956            environment.interaction.clone().with(PseudoState::Disabled)
1957        } else {
1958            environment.interaction.clone()
1959        };
1960        let motion_path = retained_id.map_or_else(
1961            || path.to_owned(),
1962            |node| crate::motion::retained_node_path(path, node),
1963        );
1964        let mut animation = node_motion(environment.motions, &motion_path);
1965        if let Some(retained_id) = retained_id {
1966            for binding in node.progress_motions() {
1967                if let Some(value) = environment
1968                    .geometry
1969                    .motion_progress(retained_id, binding.property())
1970                {
1971                    animation.set(binding.property(), value);
1972                }
1973            }
1974        }
1975        let signals = node_signals(environment.signals, node);
1976        if matches!(node.kind(), UiNodeKind::Canvas { .. }) {
1977            animation = apply_canvas_signal_transform(animation, &signals);
1978            if let Some(retained_id) = retained_id {
1979                environment
1980                    .geometry
1981                    .update_canvas_transform(retained_id, animation.canvas_transform());
1982            }
1983        }
1984        let mut resolved_style = node.style().resolve(&local_interaction);
1985        apply_motion_dimensions(&mut resolved_style, animation);
1986        apply_signal_style(&mut resolved_style, &signals);
1987        normalize_text_content_layout(node, &mut resolved_style);
1988        let local_environment = environment.with_resolved_text_color(&resolved_style);
1989        let mut element = apply_style(
1990            div(),
1991            &resolved_style,
1992            environment.colors,
1993            environment.direction,
1994        );
1995        if matches!(
1996            node.kind(),
1997            UiNodeKind::VirtualCollection { spec } if spec.height.is_none()
1998        ) {
1999            element = element.flex_1().min_h(px(0.0));
2000        }
2001        let focus_handle =
2002            retained_id.and_then(|node| environment.focus_handles.get(&node).cloned());
2003        element =
2004            apply_node_focus_tracking(element, node, focus_handle.as_ref(), environment.primitives);
2005        if let Some(opacity) = signals.opacity.or(animation.opacity) {
2006            element = element.opacity(f64_to_f32(opacity.clamp(0.0, 1.0)));
2007        }
2008        if animation.clip_height.is_some() || resolved_style.clip == Some(true) {
2009            element = element.overflow_hidden();
2010        }
2011        let populated = Self::populate_with_interactions(
2012            element,
2013            node,
2014            &local_environment,
2015            boundary_fallback,
2016            path,
2017            retained_id,
2018            animation,
2019        );
2020        let translate_x = signals
2021            .translate_x
2022            .or(animation.translate_x)
2023            .or(resolved_style.translate_x);
2024        let translate_y = signals
2025            .translate_y
2026            .or(animation.translate_y)
2027            .or(resolved_style.translate_y);
2028        let element = translated(populated, translate_x, translate_y);
2029        let layout_motion = layout_motion_spec(node, environment.now);
2030        let progress_motions = node.progress_motions().to_vec();
2031        match retained_id {
2032            Some(node) => GeometryTrackedElement {
2033                child: Some(element),
2034                node,
2035                registry: environment.geometry.clone(),
2036                translate_x: translate_x.unwrap_or(0.0),
2037                translate_y: translate_y.unwrap_or(0.0),
2038                layout_motion,
2039                progress_motions,
2040                scroll_handles: scroll_handles_for_node(
2041                    environment.retained,
2042                    retained_id,
2043                    environment.scroll_handles,
2044                ),
2045                focus_handle,
2046                now: environment.now,
2047                motion_preference: environment.motion_preference,
2048            }
2049            .into_any_element(),
2050            None => element,
2051        }
2052    }
2053
2054    #[allow(clippy::too_many_lines)]
2055    fn populate_with_interactions<C: ColorResolver>(
2056        element: Div,
2057        node: &UiNode,
2058        environment: &RenderEnvironment<'_, C>,
2059        boundary_fallback: Option<&UiNode>,
2060        path: &str,
2061        retained_id: Option<NodeId>,
2062        motion: NodeMotionValues,
2063    ) -> AnyElement {
2064        let click = (!is_disabled(node) && !node.event_handlers("click").is_empty()).then(|| {
2065            (
2066                node.event_handlers("click").to_vec(),
2067                node.handler_payload("click")
2068                    .cloned()
2069                    .unwrap_or(UiValue::Null),
2070            )
2071        });
2072        let hover = (!node.event_handlers("hover_change").is_empty()).then(|| {
2073            (
2074                node.event_handlers("hover_change").to_vec(),
2075                node.handler_payload("hover_change").cloned(),
2076            )
2077        });
2078        let key_handlers = key_handler_bindings(node);
2079        let hit_test = resolved_hit_test(node, environment);
2080        let semantic = environment
2081            .a11y_active
2082            .then(|| {
2083                retained_id.and_then(|id| environment.semantics.and_then(|frame| frame.node(id)))
2084            })
2085            .flatten()
2086            .filter(|semantic| {
2087                crate::accessibility::native_role(&semantic.role)
2088                    .ok()
2089                    .flatten()
2090                    .is_some()
2091            })
2092            .cloned();
2093        let needs = u8::from(click.is_some())
2094            | u8::from(hover.is_some()) << 1
2095            | u8::from(!key_handlers.is_empty()) << 2
2096            | u8::from(hit_test.is_some()) << 3
2097            | u8::from(node.progress_motions().iter().any(|binding| {
2098                matches!(
2099                    binding.driver,
2100                    crate::MotionProgressDriver::Hover
2101                        | crate::MotionProgressDriver::Press
2102                        | crate::MotionProgressDriver::Focus
2103                )
2104            })) << 4
2105            | u8::from(semantic.is_some()) << 5;
2106        if !node_needs_interaction_wrapper(node, needs) {
2107            return Self::populate(
2108                element,
2109                node,
2110                environment,
2111                boundary_fallback,
2112                path,
2113                retained_id,
2114                motion,
2115            );
2116        }
2117
2118        let click_dispatcher = environment.dispatcher.cloned();
2119        let hover_dispatcher = environment.dispatcher.cloned();
2120        let keyboard_dispatcher = environment.dispatcher.cloned();
2121        let event_target = EventTargetContext::new(retained_id, environment.geometry.clone());
2122        let click_target = event_target.clone();
2123        let hover_target = event_target.clone();
2124        let keyboard_target = event_target;
2125        let keyboard_click = click.clone();
2126        let text_direction = environment.direction;
2127        let stable_id = interaction_element_id(retained_id, path);
2128        let debug_path = path.to_owned();
2129        let element = apply_pseudo_backgrounds(
2130            element
2131                .id(SharedString::from(stable_id))
2132                .debug_selector(move || debug_path.clone()),
2133            node.style(),
2134            environment.colors,
2135        );
2136        let element = apply_native_semantics(element, semantic.as_ref());
2137        let element = apply_primitive_accessibility_actions(
2138            element,
2139            node,
2140            retained_id,
2141            semantic.as_ref(),
2142            environment,
2143        );
2144        let element = apply_hit_test(element, hit_test);
2145        let element =
2146            apply_tab_behavior(element, node, click.is_some() || !key_handlers.is_empty());
2147        let element = apply_environment_scroll(element, node, retained_id, environment);
2148        let element = apply_motion_trigger_handlers(
2149            element,
2150            retained_id,
2151            node.progress_motions(),
2152            environment.geometry,
2153        );
2154        let element = if let Some((bindings, payload)) = click {
2155            element.on_click(move |event, window, cx| {
2156                if !matches!(event, ClickEvent::Mouse(_)) {
2157                    return;
2158                }
2159                let response = dispatch_ui_handlers(
2160                    &bindings,
2161                    "click",
2162                    &payload,
2163                    click_target.snapshot(),
2164                    window,
2165                    cx,
2166                    click_dispatcher.as_ref(),
2167                );
2168                apply_event_response(response, window, cx);
2169            })
2170        } else {
2171            element
2172        };
2173        let element = apply_hover_handler(element, hover, hover_dispatcher, hover_target);
2174        let element = if key_handlers.values().any(|(bindings, _)| {
2175            bindings
2176                .iter()
2177                .any(|binding| binding.phase() == crate::EventPhase::Capture)
2178        }) {
2179            let handlers = key_handlers.clone();
2180            let dispatcher = keyboard_dispatcher.clone();
2181            let target = keyboard_target.clone();
2182            element.capture_key_down(move |event, window, cx| {
2183                let key = logical_keyboard_key(event.keystroke.key.as_str(), text_direction);
2184                if let Some((bindings, payload)) = handlers.get(key) {
2185                    let response = dispatch_ui_handler_phases(
2186                        bindings,
2187                        "key",
2188                        &[crate::EventPhase::Capture],
2189                        payload,
2190                        EventRoute::new(target.snapshot(), dispatcher.as_ref()),
2191                        window,
2192                        cx,
2193                    );
2194                    apply_event_response(response, window, cx);
2195                }
2196            })
2197        } else {
2198            element
2199        };
2200        let element = element.on_key_down(move |event, window, cx| {
2201            let semantic_key = logical_keyboard_key(event.keystroke.key.as_str(), text_direction);
2202            let explicit = key_handlers.get(semantic_key);
2203            let semantic = explicit.or_else(|| {
2204                matches!(event.keystroke.key.as_str(), "enter" | "space")
2205                    .then_some(())
2206                    .and(keyboard_click.as_ref())
2207            });
2208            if let Some((bindings, payload)) = semantic {
2209                let phases = if explicit.is_some() {
2210                    &[crate::EventPhase::Target, crate::EventPhase::Bubble][..]
2211                } else {
2212                    // Preserve the existing click fallback, rather than also
2213                    // firing a click or changing mouse-click phase policy.
2214                    &[crate::EventPhase::Target][..]
2215                };
2216                let response = dispatch_ui_handler_phases(
2217                    bindings,
2218                    "key",
2219                    phases,
2220                    payload,
2221                    EventRoute::new(keyboard_target.snapshot(), keyboard_dispatcher.as_ref()),
2222                    window,
2223                    cx,
2224                );
2225                apply_event_response(response, window, cx);
2226            }
2227        });
2228        let element = apply_environment_raw_pointer(element, node, retained_id, environment);
2229        Self::populate(
2230            element,
2231            node,
2232            environment,
2233            boundary_fallback,
2234            path,
2235            retained_id,
2236            motion,
2237        )
2238    }
2239
2240    #[allow(clippy::too_many_lines)]
2241    fn populate<C: ColorResolver>(
2242        element: impl ParentElement + IntoElement,
2243        node: &UiNode,
2244        environment: &RenderEnvironment<'_, C>,
2245        boundary_fallback: Option<&UiNode>,
2246        path: &str,
2247        retained_id: Option<NodeId>,
2248        motion: NodeMotionValues,
2249    ) -> AnyElement {
2250        match node.kind() {
2251            UiNodeKind::Text { text } => {
2252                render_text_node(element, node, text.as_str(), environment, path, retained_id)
2253            }
2254            UiNodeKind::RichText { text, spans } => element
2255                .child(styled_text(
2256                    text.as_str(),
2257                    spans,
2258                    environment.colors,
2259                    environment.motions,
2260                    &retained_id.map_or_else(
2261                        || path.to_owned(),
2262                        |node| crate::motion::retained_node_path(path, node),
2263                    ),
2264                ))
2265                .into_any_element(),
2266            UiNodeKind::Canvas { scene } => render_canvas(
2267                element,
2268                scene,
2269                environment.colors,
2270                motion,
2271                retained_id,
2272                environment.geometry,
2273            ),
2274            UiNodeKind::Svg { source } => render_inline_svg(element, node, source, environment),
2275            UiNodeKind::Box { children } | UiNodeKind::Fragment { children } => {
2276                let children = render_flattened_children(
2277                    children,
2278                    environment,
2279                    boundary_fallback,
2280                    path,
2281                    retained_id,
2282                );
2283                let element = if let Some(crate::table_layout::TableLayout::Resolved { extent }) =
2284                    node.table_layout()
2285                {
2286                    let mut track = div()
2287                        .flex()
2288                        .flex_col()
2289                        .w(px(f64_to_f32(*extent)))
2290                        .min_w(px(f64_to_f32(*extent)))
2291                        .flex_shrink_0();
2292                    if node.style().base.flex_grow == Some(true) {
2293                        track = track.flex_1().min_h(px(0.0));
2294                    }
2295                    element.child(track.children(children))
2296                } else {
2297                    element.children(children)
2298                };
2299                decorate_scrollbars(element, node, environment, path, retained_id)
2300                    .into_any_element()
2301            }
2302            UiNodeKind::Custom { primitive } => element
2303                .child(
2304                    environment.primitives.element(
2305                        primitive.clone(),
2306                        retained_id.zip(node.key().map(|key| key.as_str().to_owned())),
2307                        environment
2308                            .primitives
2309                            .uses_primary_focus(&primitive.primitive)
2310                            .then(|| {
2311                                primitive_focus_owner(
2312                                    environment.retained,
2313                                    retained_id,
2314                                    environment.focus_handles,
2315                                )
2316                            })
2317                            .flatten(),
2318                        boundary_fallback.cloned(),
2319                        crate::primitive::PrimitiveWindowContext::new(
2320                            environment.dispatcher.cloned(),
2321                            environment.interactions.clone(),
2322                            scroll_handles_for_node(
2323                                environment.retained,
2324                                retained_id,
2325                                environment.scroll_handles,
2326                            ),
2327                            environment.view_id,
2328                        ),
2329                        crate::PrimitiveTheme::capture_with_environment(
2330                            environment.colors,
2331                            environment.direction,
2332                            environment.locale,
2333                            environment.number,
2334                            environment.clock.clone(),
2335                            environment.motion_preference,
2336                            environment.motion_quality,
2337                        ),
2338                    ),
2339                )
2340                .into_any_element(),
2341            UiNodeKind::Image { source } => render_image(element, source, environment),
2342            UiNodeKind::DirectionalImage {
2343                left_to_right,
2344                right_to_left,
2345            } => {
2346                let source = match environment.direction {
2347                    TextDirection::LeftToRight => left_to_right,
2348                    TextDirection::RightToLeft => right_to_left,
2349                };
2350                render_image(element, source, environment)
2351            }
2352            UiNodeKind::Overlay {
2353                trigger,
2354                content,
2355                spec,
2356            } => element
2357                .child(native_overlay_element(
2358                    node,
2359                    trigger,
2360                    content,
2361                    spec,
2362                    environment,
2363                    boundary_fallback,
2364                    (path, retained_id),
2365                ))
2366                .into_any_element(),
2367            UiNodeKind::Layer { content, spec } => render_layer_node(
2368                element,
2369                content,
2370                spec,
2371                environment,
2372                boundary_fallback,
2373                path,
2374                retained_id,
2375            ),
2376            UiNodeKind::VirtualCollection { spec } => {
2377                render_virtual_collection(element, spec, environment, path, retained_id)
2378            }
2379            UiNodeKind::ErrorBoundary { child, fallback } => element
2380                .child(Self::render_internal(
2381                    child,
2382                    environment,
2383                    Some(fallback),
2384                    &format!("{path}/boundary"),
2385                    retained_child_id(
2386                        environment.retained,
2387                        environment.retained_links,
2388                        retained_id,
2389                        "child",
2390                        0,
2391                    ),
2392                ))
2393                .into_any_element(),
2394        }
2395    }
2396}
2397
2398fn apply_node_focus_tracking(
2399    element: Div,
2400    node: &UiNode,
2401    handle: Option<&FocusHandle>,
2402    primitives: &PrimitiveRegistry,
2403) -> Div {
2404    let Some(handle) = handle else {
2405        return element;
2406    };
2407    if matches!(node.kind(), UiNodeKind::Custom { primitive }
2408        if primitives.uses_primary_focus(&primitive.primitive))
2409    {
2410        // The wrapper observes the native control's identity for focus paint,
2411        // but only the native control belongs in the tab sequence. The
2412        // primitive refreshes the shared handle's real tab-stop policy below.
2413        element.track_focus(&handle.clone().tab_stop(false))
2414    } else {
2415        element.track_focus(handle)
2416    }
2417}
2418
2419fn primitive_focus_owner(
2420    retained: Option<&RetainedUiTree>,
2421    retained_id: Option<NodeId>,
2422    focus_handles: &BTreeMap<NodeId, FocusHandle>,
2423) -> Option<FocusHandle> {
2424    let retained_id = retained_id?;
2425    if let Some(handle) = focus_handles.get(&retained_id) {
2426        return Some(handle.clone());
2427    }
2428    let retained = retained?;
2429    let mut cursor = Some(retained_id);
2430    while let Some(node_id) = cursor {
2431        let node = retained.node(node_id)?;
2432        if node.focus_styled()
2433            && let Some(handle) = focus_handles.get(&node_id)
2434        {
2435            return Some(handle.clone());
2436        }
2437        cursor = node.parent();
2438    }
2439    None
2440}
2441
2442pub(crate) fn render_motion_ghost(
2443    ghost: &crate::motion::MotionGhost,
2444    colors: &impl ColorResolver,
2445    primitives: &PrimitiveRegistry,
2446    resources: &WindowRenderResources<'_>,
2447) -> AnyElement {
2448    let child = GpuiNodeRenderer::render_subtree_with_window_runtime(
2449        &ghost.node,
2450        colors,
2451        &InteractionState::default(),
2452        primitives,
2453        resources,
2454        &ghost.path,
2455    );
2456    div()
2457        .absolute()
2458        .left(px(f64_to_f32(ghost.bounds.x)))
2459        .top(px(f64_to_f32(ghost.bounds.y)))
2460        .w(px(f64_to_f32(ghost.bounds.width)))
2461        .h(px(f64_to_f32(ghost.bounds.height)))
2462        .child(child)
2463        .into_any_element()
2464}
2465
2466fn resolve_ambient_text_color<C: ColorResolver>(
2467    style: &StyleProperties,
2468    colors: &C,
2469    inherited: Option<Rgba8>,
2470) -> Option<Rgba8> {
2471    style
2472        .text_color
2473        .as_ref()
2474        .and_then(|color| colors.resolve(color))
2475        .or(inherited)
2476}
2477
2478fn render_layer_node<C: ColorResolver>(
2479    element: impl ParentElement + IntoElement,
2480    content: &UiNode,
2481    spec: &crate::LayerNodeSpec,
2482    environment: &RenderEnvironment<'_, C>,
2483    boundary_fallback: Option<&UiNode>,
2484    path: &str,
2485    retained_id: Option<NodeId>,
2486) -> AnyElement {
2487    let content = GpuiNodeRenderer::render_internal(
2488        content,
2489        environment,
2490        boundary_fallback,
2491        &format!("{path}/content"),
2492        retained_child_id(
2493            environment.retained,
2494            environment.retained_links,
2495            retained_id,
2496            "content",
2497            0,
2498        ),
2499    );
2500    element
2501        .child(ScriptLayerElement::new(
2502            path,
2503            content,
2504            spec.clone(),
2505            environment.overlays.clone(),
2506            environment.view_id,
2507        ))
2508        .into_any_element()
2509}
2510
2511fn decorate_scrollbars<C, E>(
2512    element: E,
2513    node: &UiNode,
2514    environment: &RenderEnvironment<'_, C>,
2515    path: &str,
2516    retained_id: Option<NodeId>,
2517) -> E
2518where
2519    C: ColorResolver,
2520    E: ParentElement + IntoElement,
2521{
2522    let (Some(spec), Some(handle)) = (
2523        crate::ScrollbarSpec::from_node(node),
2524        retained_id.and_then(|node| environment.scroll_handles.get(&node)),
2525    ) else {
2526        return element;
2527    };
2528    let part_color = |part, token, fallback| {
2529        node.part_style(part)
2530            .and_then(|style| {
2531                style
2532                    .resolve(&InteractionState::default())
2533                    .background
2534                    .as_ref()
2535                    .and_then(|color| environment.colors.resolve(color))
2536            })
2537            .unwrap_or_else(|| semantic_color(environment.colors, token, fallback))
2538    };
2539    element.child(crate::scrollbar::ThemedScrollbar::new(
2540        format!("{path}/scrollbars"),
2541        crate::interaction::InteractionOwner::new(environment.view_id, format!("scrollbar:{path}")),
2542        environment.interactions.clone(),
2543        handle.clone(),
2544        spec,
2545        environment.direction,
2546        (
2547            part_color("scrollbar_track", "surface_raised", 0x0027_272aff),
2548            part_color("scrollbar_thumb", "text_muted", 0x0071_717aff),
2549            part_color("scrollbar_thumb_hover", "accent", 0x003b_82f6ff),
2550        ),
2551    ))
2552}
2553
2554fn normalize_text_content_layout(node: &UiNode, style: &mut StyleProperties) {
2555    if matches!(
2556        node.kind(),
2557        UiNodeKind::Text { .. } | UiNodeKind::RichText { .. }
2558    ) && style.display.is_none()
2559        && (style.align.is_some() || style.justify.is_some())
2560    {
2561        style.display = Some(DisplayMode::Flex);
2562    }
2563}
2564
2565fn render_text_node<C: ColorResolver>(
2566    element: impl ParentElement + IntoElement,
2567    node: &UiNode,
2568    text: &str,
2569    environment: &RenderEnvironment<'_, C>,
2570    path: &str,
2571    retained_id: Option<NodeId>,
2572) -> AnyElement {
2573    if !node_selectable(node) {
2574        return element.child(text.to_owned()).into_any_element();
2575    }
2576    let highlight = environment
2577        .colors
2578        .resolve(&ColorValue::Token("selection".to_owned()))
2579        .unwrap_or(Rgba8::from_rgba_hex(0x3b82_f655));
2580    let selection_id = retained_id.map_or_else(
2581        || path.to_owned(),
2582        |node| format!("{}:{node}", environment.view_id),
2583    );
2584    element
2585        .child(SelectableText::new(
2586            selection_id,
2587            retained_id,
2588            text,
2589            highlight,
2590            environment.text_selection.clone(),
2591            environment.host_focus.cloned(),
2592        ))
2593        .into_any_element()
2594}
2595
2596fn render_virtual_collection<C: ColorResolver>(
2597    element: impl ParentElement + IntoElement,
2598    spec: &crate::VirtualCollectionNodeSpec,
2599    environment: &RenderEnvironment<'_, C>,
2600    path: &str,
2601    retained_id: Option<NodeId>,
2602) -> AnyElement {
2603    element
2604        .child(native_virtual_collection_element(
2605            spec,
2606            environment,
2607            path,
2608            retained_id,
2609        ))
2610        .into_any_element()
2611}
2612
2613fn render_flattened_children<C: ColorResolver>(
2614    children: &[UiNode],
2615    environment: &RenderEnvironment<'_, C>,
2616    boundary_fallback: Option<&UiNode>,
2617    path: &str,
2618    retained_id: Option<NodeId>,
2619) -> Vec<AnyElement> {
2620    let mut rendered = Vec::new();
2621    for (index, child) in children.iter().enumerate() {
2622        let child_path = child.key().map_or_else(
2623            || format!("{path}/{index}"),
2624            |key| format!("{path}/{}", key.as_str()),
2625        );
2626        let child_id = retained_child_id(
2627            environment.retained,
2628            environment.retained_links,
2629            retained_id,
2630            "children",
2631            index,
2632        );
2633        if let UiNodeKind::Fragment { children } = child.kind() {
2634            rendered.extend(render_flattened_children(
2635                children,
2636                environment,
2637                boundary_fallback,
2638                &child_path,
2639                child_id,
2640            ));
2641        } else {
2642            rendered.push(GpuiNodeRenderer::render_internal(
2643                child,
2644                environment,
2645                boundary_fallback,
2646                &child_path,
2647                child_id,
2648            ));
2649        }
2650    }
2651    rendered
2652}
2653
2654fn node_needs_interaction_wrapper(node: &UiNode, needs: u8) -> bool {
2655    needs & 0b10_1000 != 0
2656        || !is_disabled(node)
2657            && (needs & 0b0111 != 0
2658                || node_has_focus_declaration(node)
2659                || node.style().hover.is_some()
2660                || node.style().active.is_some()
2661                || node.style().focus.is_some()
2662                || node_has_raw_pointer_handlers(node)
2663                || node_scrollable(node))
2664}
2665
2666fn apply_native_semantics(
2667    mut element: Stateful<Div>,
2668    semantic: Option<&crate::AccessibilityNode>,
2669) -> Stateful<Div> {
2670    let Some(semantic) = semantic else {
2671        return element;
2672    };
2673    let Some(role) = crate::accessibility::native_role(&semantic.role)
2674        .expect("committed semantic roles are validated")
2675    else {
2676        return element;
2677    };
2678    element = element.role(role);
2679    if let Some(id) = &semantic.semantic_id {
2680        element = element.accessibility_id(id.clone());
2681    }
2682    if !semantic.name.is_empty() {
2683        if semantic.role == "text" {
2684            // A retained plain-text wrapper owns the native text value. It
2685            // must not masquerade as a value-less AccessKit TextRun; macOS
2686            // consumers traverse TextRun values without an Option fallback.
2687            element = element.aria_value(semantic.name.clone());
2688        } else {
2689            element = element.aria_label(semantic.name.clone());
2690        }
2691    }
2692    if !semantic.description.is_empty() {
2693        element = element.aria_description(semantic.description.clone());
2694    }
2695    if let Some(selected) = semantic
2696        .selected
2697        .or_else(|| selected_from_checked(role, semantic.checked.as_ref()))
2698    {
2699        element = element.aria_selected(selected);
2700    }
2701    if let Some(expanded) = semantic
2702        .expanded
2703        .or_else(|| expanded_from_checked(role, semantic.checked.as_ref()))
2704    {
2705        element = element.aria_expanded(expanded);
2706    }
2707    if let Some(toggled) = toggled_from_semantics(role, semantic) {
2708        element = element.aria_toggled(toggled);
2709    }
2710    if let Some(value) = semantic.value.as_ref().and_then(accessibility_value_text) {
2711        element = element.aria_value(value);
2712    }
2713    if let Some(placeholder) = &semantic.placeholder {
2714        element = element.aria_placeholder(placeholder.clone());
2715    }
2716    if let Some(key_shortcuts) = &semantic.key_shortcuts {
2717        element = element.aria_keyshortcuts(key_shortcuts.clone());
2718    }
2719    if let Some(value) = semantic.value.as_ref().and_then(accessibility_number_value) {
2720        element = element.aria_numeric_value(value);
2721    }
2722    if let Some(value) = semantic.value_min {
2723        element = element.aria_min_numeric_value(value);
2724    }
2725    if let Some(value) = semantic.value_max {
2726        element = element.aria_max_numeric_value(value);
2727    }
2728    if let Some(orientation) = semantic.orientation.as_deref() {
2729        element = element.aria_orientation(match orientation {
2730            "horizontal" => gpui::Orientation::Horizontal,
2731            "vertical" => gpui::Orientation::Vertical,
2732            _ => unreachable!("committed accessibility orientation is validated"),
2733        });
2734    }
2735    if let Some(level) = semantic.level {
2736        element = element.aria_level(level);
2737    }
2738    if let Some(position) = semantic.position_in_set {
2739        element = element.aria_position_in_set(position);
2740    }
2741    if let Some(size) = semantic.size_of_set {
2742        element = element.aria_size_of_set(size);
2743    }
2744    if let Some(index) = semantic.row_index {
2745        element = element.aria_row_index(index);
2746    }
2747    if let Some(index) = semantic.column_index {
2748        element = element.aria_column_index(index);
2749    }
2750    if let Some(count) = semantic.row_count {
2751        element = element.aria_row_count(count);
2752    }
2753    if let Some(count) = semantic.column_count {
2754        element = element.aria_column_count(count);
2755    }
2756    apply_native_semantic_flags(element, semantic)
2757}
2758
2759fn apply_native_semantic_flags(
2760    mut element: Stateful<Div>,
2761    semantic: &crate::AccessibilityNode,
2762) -> Stateful<Div> {
2763    let required = semantic.required;
2764    let disabled = semantic.disabled;
2765    let read_only = semantic.read_only;
2766    let invalid = semantic.invalid;
2767    let current = semantic.current.clone();
2768    if !(required || disabled || read_only || invalid || current.is_some()) {
2769        return element;
2770    }
2771    element = element.a11y_synthetic_children(move |builder| {
2772        let node = builder.parent_node();
2773        if required {
2774            node.set_required();
2775        }
2776        if disabled {
2777            node.set_disabled();
2778        }
2779        if read_only {
2780            node.set_read_only();
2781        }
2782        if invalid {
2783            node.set_invalid(gpui::accesskit::Invalid::True);
2784        }
2785        if let Some(current) = current.as_deref() {
2786            node.set_aria_current(match current {
2787                "page" => gpui::accesskit::AriaCurrent::Page,
2788                "step" => gpui::accesskit::AriaCurrent::Step,
2789                "location" => gpui::accesskit::AriaCurrent::Location,
2790                "date" => gpui::accesskit::AriaCurrent::Date,
2791                "time" => gpui::accesskit::AriaCurrent::Time,
2792                "true" => gpui::accesskit::AriaCurrent::True,
2793                _ => unreachable!("committed aria-current values are validated"),
2794            });
2795        }
2796    });
2797    element
2798}
2799
2800fn apply_primitive_accessibility_actions<C: ColorResolver>(
2801    mut element: Stateful<Div>,
2802    node: &UiNode,
2803    retained_id: Option<NodeId>,
2804    semantic: Option<&crate::AccessibilityNode>,
2805    environment: &RenderEnvironment<'_, C>,
2806) -> Stateful<Div> {
2807    let Some(semantic) = semantic else {
2808        return element;
2809    };
2810    if semantic.disabled {
2811        return element;
2812    }
2813    let (UiNodeKind::Custom { primitive }, Some(node), Some(key)) =
2814        (node.kind(), retained_id, node.key())
2815    else {
2816        return element;
2817    };
2818    let instance =
2819        crate::PrimitiveInstanceId::new(primitive.primitive.clone(), key.as_str().to_owned(), node);
2820    for action in environment
2821        .primitives
2822        .accessibility_actions(&instance)
2823        .into_iter()
2824        .filter(|action| !(semantic.read_only && *action == gpui::AccessibleAction::SetValue))
2825    {
2826        let registry = environment.primitives.clone();
2827        let instance = instance.clone();
2828        element = element.on_a11y_action(action, move |data, window, cx| {
2829            // Stale and disabled native instances intentionally reject the
2830            // request without falling through to a second input path.
2831            let _ = registry.perform_accessibility_action(&instance, action, data, window, cx);
2832        });
2833    }
2834    element
2835}
2836
2837fn selected_from_checked(role: gpui::Role, checked: Option<&UiValue>) -> Option<bool> {
2838    matches!(
2839        role,
2840        gpui::Role::Tab
2841            | gpui::Role::ListBoxOption
2842            | gpui::Role::Row
2843            | gpui::Role::GridCell
2844            | gpui::Role::MenuItem
2845    )
2846    .then(|| checked.and_then(accessibility_bool_value))
2847    .flatten()
2848}
2849
2850fn expanded_from_checked(role: gpui::Role, checked: Option<&UiValue>) -> Option<bool> {
2851    matches!(role, gpui::Role::ComboBox)
2852        .then(|| checked.and_then(accessibility_bool_value))
2853        .flatten()
2854}
2855
2856fn toggled_from_semantics(
2857    role: gpui::Role,
2858    semantic: &crate::AccessibilityNode,
2859) -> Option<gpui::Toggled> {
2860    if let Some(pressed) = semantic.pressed {
2861        return Some(pressed.into());
2862    }
2863    if matches!(
2864        role,
2865        gpui::Role::CheckBox
2866            | gpui::Role::RadioButton
2867            | gpui::Role::Switch
2868            | gpui::Role::MenuItemCheckBox
2869            | gpui::Role::MenuItemRadio
2870    ) {
2871        return match semantic.checked.as_ref() {
2872            Some(UiValue::Bool(value)) => Some((*value).into()),
2873            Some(UiValue::String(value)) if value == "mixed" => Some(gpui::Toggled::Mixed),
2874            _ => None,
2875        };
2876    }
2877    None
2878}
2879
2880fn accessibility_bool_value(value: &UiValue) -> Option<bool> {
2881    match value {
2882        UiValue::Bool(value) => Some(*value),
2883        _ => None,
2884    }
2885}
2886
2887fn accessibility_number_value(value: &UiValue) -> Option<f64> {
2888    match value {
2889        UiValue::Float(value) => Some(*value),
2890        UiValue::Integer(value) => value.to_string().parse().ok(),
2891        _ => None,
2892    }
2893}
2894
2895fn accessibility_value_text(value: &UiValue) -> Option<String> {
2896    match value {
2897        UiValue::Null | UiValue::Handle(_) => None,
2898        UiValue::Bool(value) => Some(value.to_string()),
2899        UiValue::Integer(value) => Some(value.to_string()),
2900        UiValue::Float(value) => Some(value.to_string()),
2901        UiValue::String(value) => Some(value.clone()),
2902        UiValue::Array(_) | UiValue::Map(_) => serde_json::to_string(value).ok(),
2903    }
2904}
2905
2906fn resolved_hit_test<C: ColorResolver>(
2907    node: &UiNode,
2908    environment: &RenderEnvironment<'_, C>,
2909) -> Option<HitTestBehavior> {
2910    let interaction = if is_disabled(node) {
2911        environment.interaction.clone().with(PseudoState::Disabled)
2912    } else {
2913        environment.interaction.clone()
2914    };
2915    node.style().resolve(&interaction).hit_test
2916}
2917
2918fn apply_hit_test(element: Stateful<Div>, hit_test: Option<HitTestBehavior>) -> Stateful<Div> {
2919    match hit_test {
2920        Some(HitTestBehavior::Block) => element.occlude(),
2921        Some(HitTestBehavior::BlockExceptScroll) => element.block_mouse_except_scroll(),
2922        None => element,
2923    }
2924}
2925
2926fn node_has_focus_declaration(node: &UiNode) -> bool {
2927    node.attributes().contains_key("tab_index")
2928        || node.attributes().get("tab_group") == Some(&UiValue::Bool(true))
2929        || node.attributes().contains_key("tab_stop")
2930}
2931
2932fn apply_environment_scroll<C: ColorResolver>(
2933    element: Stateful<Div>,
2934    node: &UiNode,
2935    retained_id: Option<NodeId>,
2936    environment: &RenderEnvironment<'_, C>,
2937) -> Stateful<Div> {
2938    apply_scroll_behavior(
2939        element,
2940        node,
2941        retained_id,
2942        environment.scroll_handles,
2943        environment.scroll_anchors,
2944    )
2945}
2946
2947fn apply_environment_raw_pointer<C: ColorResolver>(
2948    element: Stateful<Div>,
2949    node: &UiNode,
2950    retained_id: Option<NodeId>,
2951    environment: &RenderEnvironment<'_, C>,
2952) -> Stateful<Div> {
2953    apply_raw_pointer_handlers(
2954        element,
2955        node,
2956        environment.dispatcher,
2957        retained_id,
2958        environment.pointer_capture,
2959        environment.geometry,
2960        scroll_handles_for_node(
2961            environment.retained,
2962            retained_id,
2963            environment.scroll_handles,
2964        ),
2965    )
2966}
2967
2968fn node_tab_stop(node: &UiNode) -> bool {
2969    !matches!(
2970        node.attributes().get("tab_stop"),
2971        Some(UiValue::Bool(false))
2972    )
2973}
2974
2975fn node_selectable(node: &UiNode) -> bool {
2976    matches!(
2977        node.attributes().get("selectable"),
2978        Some(UiValue::Bool(true))
2979    )
2980}
2981
2982fn node_tab_index(node: &UiNode) -> isize {
2983    match node.attributes().get("tab_index") {
2984        Some(UiValue::Integer(index)) => isize::try_from(*index).unwrap_or_default(),
2985        _ => 0,
2986    }
2987}
2988
2989fn apply_tab_behavior(
2990    mut element: Stateful<Div>,
2991    node: &UiNode,
2992    implicit_tab_stop: bool,
2993) -> Stateful<Div> {
2994    element =
2995        element
2996            .tab_index(node_tab_index(node))
2997            .tab_stop(if node_has_focus_declaration(node) {
2998                node_tab_stop(node)
2999            } else {
3000                implicit_tab_stop
3001            });
3002    if node.attributes().get("tab_group") == Some(&UiValue::Bool(true)) {
3003        element = element.tab_group();
3004    }
3005    element
3006}
3007
3008fn interaction_element_id(retained_id: Option<NodeId>, path: &str) -> String {
3009    retained_id.map_or_else(|| path.to_owned(), |id| format!("gpui-rhai-node-{id}"))
3010}
3011
3012fn retained_child_id(
3013    tree: Option<&RetainedUiTree>,
3014    links: Option<&BTreeMap<NodeId, Vec<crate::RetainedChildLink>>>,
3015    parent: Option<NodeId>,
3016    group: &str,
3017    index: usize,
3018) -> Option<NodeId> {
3019    let parent = parent?;
3020    tree.and_then(|tree| tree.node(parent))
3021        .and_then(|node| {
3022            node.children()
3023                .filter(|child| child.group() == group)
3024                .nth(index)
3025        })
3026        .or_else(|| {
3027            links
3028                .and_then(|links| links.get(&parent))
3029                .and_then(|children| {
3030                    children
3031                        .iter()
3032                        .filter(|child| child.group() == group)
3033                        .nth(index)
3034                })
3035        })
3036        .map(crate::RetainedChildLink::node)
3037}
3038
3039fn styled_text(
3040    text: &str,
3041    spans: &[crate::Span],
3042    colors: &impl ColorResolver,
3043    motions: &BTreeMap<MotionKey, f64>,
3044    path: &str,
3045) -> StyledText {
3046    let mut offset = 0usize;
3047    let highlights = spans.iter().filter_map(|span| {
3048        let start = offset;
3049        offset = offset.saturating_add(span.text().len());
3050        let style = HighlightStyle {
3051            color: span
3052                .color_value()
3053                .and_then(|color| colors.resolve(color))
3054                .map(|color| rgba(color.as_rgba_hex()).into()),
3055            font_weight: span.is_bold().then_some(FontWeight::BOLD),
3056            font_style: span.is_italic().then_some(FontStyle::Italic),
3057            fade_out: span.key().and_then(|key| {
3058                motions
3059                    .get(&MotionKey::for_node(
3060                        &crate::motion::span_motion_path(path, key),
3061                        MotionProperty::Opacity,
3062                    ))
3063                    .map(|opacity| f64_to_f32(1.0 - opacity.clamp(0.0, 1.0)))
3064            }),
3065            ..HighlightStyle::default()
3066        };
3067        (style != HighlightStyle::default()).then_some((start..offset, style))
3068    });
3069    StyledText::new(text.to_owned()).with_highlights(highlights)
3070}
3071
3072fn render_canvas(
3073    element: impl ParentElement + IntoElement,
3074    scene: &crate::CanvasScene,
3075    colors: &impl ColorResolver,
3076    motion: NodeMotionValues,
3077    retained_id: Option<NodeId>,
3078    geometry: &crate::GeometryRegistry,
3079) -> AnyElement {
3080    let scene = scene.clone();
3081    let colors = OwnedColorResolver::capture(colors);
3082    let geometry = geometry.clone();
3083    let canvas = gpui::canvas(
3084        move |bounds, window, _| {
3085            if let Some(node) = retained_id
3086                && let Ok(drawable) = crate::GeometryBounds::new(
3087                    f64::from(bounds.origin.x),
3088                    f64::from(bounds.origin.y),
3089                    f64::from(bounds.size.width),
3090                    f64::from(bounds.size.height),
3091                )
3092            {
3093                let offset = window.pixel_snap_point(window.element_offset());
3094                geometry.update_canvas_drawable(
3095                    node,
3096                    drawable,
3097                    (f64::from(offset.x), f64::from(offset.y)),
3098                );
3099            }
3100        },
3101        move |bounds, (), window, _| {
3102            paint_canvas_scene(bounds, &scene, &colors, motion, window);
3103        },
3104    )
3105    .size_full();
3106    element.child(canvas).into_any_element()
3107}
3108
3109fn paint_canvas_scene(
3110    bounds: Bounds<Pixels>,
3111    scene: &crate::CanvasScene,
3112    colors: &impl ColorResolver,
3113    motion: NodeMotionValues,
3114    window: &mut Window,
3115) {
3116    for command in scene.commands() {
3117        match command {
3118            crate::CanvasCommand::Rect {
3119                x,
3120                y,
3121                width,
3122                height,
3123                fill: color,
3124                ..
3125            } => {
3126                if let Some(color) = colors.resolve(color) {
3127                    paint_canvas_polygon(
3128                        [
3129                            (*x, *y),
3130                            (x + width, *y),
3131                            (x + width, y + height),
3132                            (*x, y + height),
3133                        ],
3134                        bounds,
3135                        motion,
3136                        rgba(color.as_rgba_hex()),
3137                        window,
3138                    );
3139                }
3140            }
3141            crate::CanvasCommand::Circle {
3142                center_x,
3143                center_y,
3144                radius,
3145                fill: color,
3146                ..
3147            } => {
3148                if let Some(color) = colors.resolve(color) {
3149                    let points = (0..48).map(|index| {
3150                        let angle = std::f64::consts::TAU * f64::from(index) / 48.0;
3151                        (
3152                            center_x + radius * angle.cos(),
3153                            center_y + radius * angle.sin(),
3154                        )
3155                    });
3156                    paint_canvas_polygon(points, bounds, motion, rgba(color.as_rgba_hex()), window);
3157                }
3158            }
3159            crate::CanvasCommand::Line {
3160                from_x,
3161                from_y,
3162                to_x,
3163                to_y,
3164                width,
3165                color,
3166                ..
3167            } => {
3168                if let Some(color) = colors.resolve(color) {
3169                    let mut path = gpui::PathBuilder::stroke(px(f64_to_f32(*width)));
3170                    path.move_to(node_canvas_point(bounds, *from_x, *from_y, motion));
3171                    path.line_to(node_canvas_point(bounds, *to_x, *to_y, motion));
3172                    if let Ok(path) = path.build() {
3173                        window.paint_path(path, rgba(color.as_rgba_hex()));
3174                    }
3175                }
3176            }
3177            crate::CanvasCommand::Path { .. } | crate::CanvasCommand::MorphPath { .. } => {
3178                paint_canvas_path(bounds, command, colors, motion, window);
3179            }
3180        }
3181    }
3182}
3183
3184#[allow(clippy::too_many_lines)]
3185fn paint_canvas_path(
3186    bounds: Bounds<Pixels>,
3187    command: &crate::CanvasCommand,
3188    colors: &impl ColorResolver,
3189    motion: NodeMotionValues,
3190    window: &mut Window,
3191) {
3192    let interpolated;
3193    let (segments, fill, stroke, transform, clip, is_morph) = match command {
3194        crate::CanvasCommand::Path {
3195            segments,
3196            fill,
3197            stroke,
3198            transform,
3199            clip,
3200            ..
3201        } => (
3202            segments.as_slice(),
3203            fill.as_ref(),
3204            stroke.as_ref(),
3205            transform,
3206            clip,
3207            false,
3208        ),
3209        crate::CanvasCommand::MorphPath {
3210            from,
3211            to,
3212            stroke,
3213            transform,
3214            clip,
3215            ..
3216        } => {
3217            interpolated =
3218                crate::canvas::interpolate_path(from, to, motion.path_progress.unwrap_or(0.0))
3219                    .unwrap_or_else(|| from.clone());
3220            (
3221                interpolated.as_slice(),
3222                None,
3223                Some(stroke),
3224                transform,
3225                clip,
3226                true,
3227            )
3228        }
3229        _ => return,
3230    };
3231    let mut builder = stroke.map_or_else(gpui::PathBuilder::fill, |(_, width)| {
3232        gpui::PathBuilder::stroke(px(f64_to_f32(*width)))
3233    });
3234    if !is_morph && stroke.is_some() && motion.path_progress.is_some_and(|progress| progress < 1.0)
3235    {
3236        let paths = crate::canvas::trimmed_canvas_paths(
3237            crate::canvas::flatten_path(segments, *transform),
3238            motion.path_progress.unwrap_or(1.0).clamp(0.0, 1.0),
3239        );
3240        for path in paths {
3241            if let Some((first, rest)) = path.split_first() {
3242                builder.move_to(node_canvas_point(bounds, first.0, first.1, motion));
3243                for point in rest {
3244                    builder.line_to(node_canvas_point(bounds, point.0, point.1, motion));
3245                }
3246            }
3247        }
3248    } else {
3249        for segment in segments {
3250            match segment {
3251                crate::CanvasPathSegment::Move { x, y } => {
3252                    builder.move_to(canvas_path_point(bounds, *transform, *x, *y, motion));
3253                }
3254                crate::CanvasPathSegment::Line { x, y } => {
3255                    builder.line_to(canvas_path_point(bounds, *transform, *x, *y, motion));
3256                }
3257                crate::CanvasPathSegment::Quadratic {
3258                    x,
3259                    y,
3260                    control_x,
3261                    control_y,
3262                } => builder.curve_to(
3263                    canvas_path_point(bounds, *transform, *x, *y, motion),
3264                    canvas_path_point(bounds, *transform, *control_x, *control_y, motion),
3265                ),
3266                crate::CanvasPathSegment::Cubic {
3267                    x,
3268                    y,
3269                    control_a_x,
3270                    control_a_y,
3271                    control_b_x,
3272                    control_b_y,
3273                } => builder.cubic_bezier_to(
3274                    canvas_path_point(bounds, *transform, *x, *y, motion),
3275                    canvas_path_point(bounds, *transform, *control_a_x, *control_a_y, motion),
3276                    canvas_path_point(bounds, *transform, *control_b_x, *control_b_y, motion),
3277                ),
3278                crate::CanvasPathSegment::Close => builder.close(),
3279            }
3280        }
3281    }
3282    let Ok(path) = builder.build() else {
3283        return;
3284    };
3285    let paint = stroke
3286        .and_then(|(color, _)| {
3287            colors
3288                .resolve(color)
3289                .map(|color| Background::from(rgba(color.as_rgba_hex())))
3290        })
3291        .or_else(|| fill.and_then(|fill| canvas_fill(fill, colors)));
3292    let Some(paint) = paint else {
3293        return;
3294    };
3295    if let Some(clip) = clip {
3296        let mask = ContentMask {
3297            bounds: Bounds::new(
3298                point(
3299                    bounds.origin.x + px(f64_to_f32(clip.x)),
3300                    bounds.origin.y + px(f64_to_f32(clip.y)),
3301                ),
3302                gpui::size(px(f64_to_f32(clip.width)), px(f64_to_f32(clip.height))),
3303            ),
3304        };
3305        window.with_content_mask(Some(mask), |window| window.paint_path(path, paint));
3306    } else {
3307        window.paint_path(path, paint);
3308    }
3309}
3310
3311fn paint_canvas_polygon(
3312    points: impl IntoIterator<Item = (f64, f64)>,
3313    bounds: Bounds<Pixels>,
3314    motion: NodeMotionValues,
3315    paint: impl Into<Background>,
3316    window: &mut Window,
3317) {
3318    let mut points = points.into_iter();
3319    let Some((x, y)) = points.next() else {
3320        return;
3321    };
3322    let mut path = gpui::PathBuilder::fill();
3323    path.move_to(node_canvas_point(bounds, x, y, motion));
3324    for (x, y) in points {
3325        path.line_to(node_canvas_point(bounds, x, y, motion));
3326    }
3327    path.close();
3328    if let Ok(path) = path.build() {
3329        window.paint_path(path, paint.into());
3330    }
3331}
3332
3333fn canvas_fill(fill: &crate::CanvasFill, colors: &impl ColorResolver) -> Option<Background> {
3334    match fill {
3335        crate::CanvasFill::Solid(color) => colors
3336            .resolve(color)
3337            .map(|color| Background::from(rgba(color.as_rgba_hex()))),
3338        crate::CanvasFill::LinearGradient(gradient) => {
3339            let from = colors.resolve(&gradient.from)?;
3340            let to = colors.resolve(&gradient.to)?;
3341            Some(linear_gradient(
3342                f64_to_f32(gradient.angle_degrees),
3343                linear_color_stop(rgba(from.as_rgba_hex()), 0.0),
3344                linear_color_stop(rgba(to.as_rgba_hex()), 1.0),
3345            ))
3346        }
3347    }
3348}
3349
3350fn canvas_path_point(
3351    bounds: Bounds<Pixels>,
3352    transform: crate::CanvasTransform,
3353    x: f64,
3354    y: f64,
3355    motion: NodeMotionValues,
3356) -> Point<Pixels> {
3357    let (transformed_x, transformed_y) = crate::canvas::canvas_transform_point(transform, x, y);
3358    node_canvas_point(bounds, transformed_x, transformed_y, motion)
3359}
3360
3361fn node_canvas_point(
3362    bounds: Bounds<Pixels>,
3363    x: f64,
3364    y: f64,
3365    motion: NodeMotionValues,
3366) -> Point<Pixels> {
3367    let (transformed_x, transformed_y) = crate::canvas::canvas_motion_point(
3368        f64::from(bounds.size.width),
3369        f64::from(bounds.size.height),
3370        x,
3371        y,
3372        motion.canvas_transform(),
3373    );
3374    point(
3375        bounds.origin.x + px(f64_to_f32(transformed_x)),
3376        bounds.origin.y + px(f64_to_f32(transformed_y)),
3377    )
3378}
3379
3380fn render_image<C: ColorResolver>(
3381    element: impl ParentElement + IntoElement,
3382    source: &ImageSourceSpec,
3383    environment: &RenderEnvironment<'_, C>,
3384) -> AnyElement {
3385    let tint = environment.ambient_text_color;
3386    environment.assets.map_or_else(
3387        || div().child("Image registry unavailable").into_any_element(),
3388        |assets| {
3389            let handle = match source {
3390                ImageSourceSpec::Handle(handle) => Ok(handle.clone()),
3391                ImageSourceSpec::Asset(asset) => assets
3392                    .cached_image(asset)
3393                    .map(|image| image.opaque().clone()),
3394            };
3395            match handle.and_then(|handle| assets.image_source_tinted(&handle, tint)) {
3396                Ok(source) => element.child(img(source).size_full()).into_any_element(),
3397                Err(error) => div()
3398                    .child(format!("Image error: {error}"))
3399                    .into_any_element(),
3400            }
3401        },
3402    )
3403}
3404
3405fn render_inline_svg<C: ColorResolver>(
3406    element: impl ParentElement + IntoElement,
3407    node: &UiNode,
3408    source: &crate::InlineSvg,
3409    environment: &RenderEnvironment<'_, C>,
3410) -> AnyElement {
3411    let interaction = if is_disabled(node) {
3412        environment.interaction.clone().with(PseudoState::Disabled)
3413    } else {
3414        environment.interaction.clone()
3415    };
3416    let resolved_style = node.style().resolve(&interaction);
3417    let color = environment.ambient_text_color;
3418    let fills_styled_box = resolved_style.width.is_some() || resolved_style.height.is_some();
3419    let image = environment.assets.map_or_else(
3420        || AssetRegistry::new().inline_svg_source(source.shared_source(), color),
3421        |assets| assets.inline_svg_source(source.shared_source(), color),
3422    );
3423    element
3424        .child(inline_svg_image(image, fills_styled_box))
3425        .into_any_element()
3426}
3427
3428fn inline_svg_image(source: impl Into<gpui::ImageSource>, fills_styled_box: bool) -> Img {
3429    let image = img(source);
3430    if fills_styled_box {
3431        image.size_full()
3432    } else {
3433        image
3434    }
3435}
3436
3437fn scoped_overlay_spec(
3438    spec: &OverlayNodeSpec,
3439    view_id: &str,
3440    direction: TextDirection,
3441) -> OverlayNodeSpec {
3442    let mut rendered = spec.clone();
3443    rendered.id = WindowOverlayCoordinator::scoped_id(view_id, &rendered.id);
3444    rendered.parent = rendered
3445        .parent
3446        .as_ref()
3447        .map(|parent| WindowOverlayCoordinator::scoped_id(view_id, parent));
3448    rendered.placement = match (rendered.placement, direction) {
3449        (crate::OverlayPlacement::Start, TextDirection::LeftToRight)
3450        | (crate::OverlayPlacement::End, TextDirection::RightToLeft) => {
3451            crate::OverlayPlacement::Left
3452        }
3453        (crate::OverlayPlacement::End, TextDirection::LeftToRight)
3454        | (crate::OverlayPlacement::Start, TextDirection::RightToLeft) => {
3455            crate::OverlayPlacement::Right
3456        }
3457        (placement, _) => placement,
3458    };
3459    rendered
3460}
3461
3462fn native_overlay_element<C: ColorResolver>(
3463    node: &UiNode,
3464    trigger: &UiNode,
3465    content: &UiNode,
3466    spec: &OverlayNodeSpec,
3467    environment: &RenderEnvironment<'_, C>,
3468    boundary_fallback: Option<&UiNode>,
3469    (path, retained_id): (&str, Option<NodeId>),
3470) -> ScriptOverlayElement {
3471    let mut rendered_spec = scoped_overlay_spec(spec, environment.view_id, environment.direction);
3472    if rendered_spec.kind == crate::OverlayKind::Tooltip {
3473        rendered_spec.open = environment
3474            .overlays
3475            .tooltip_visible(&rendered_spec.id, std::time::Instant::now());
3476    }
3477    let trigger = GpuiNodeRenderer::render_internal(
3478        trigger,
3479        environment,
3480        boundary_fallback,
3481        &format!("{path}/trigger"),
3482        retained_child_id(
3483            environment.retained,
3484            environment.retained_links,
3485            retained_id,
3486            "trigger",
3487            0,
3488        ),
3489    );
3490    let content = GpuiNodeRenderer::render_internal(
3491        content,
3492        environment,
3493        boundary_fallback,
3494        &format!("{path}/content"),
3495        retained_child_id(
3496            environment.retained,
3497            environment.retained_links,
3498            retained_id,
3499            "content",
3500            0,
3501        ),
3502    );
3503    let event_target = EventTargetContext::new(retained_id, environment.geometry.clone());
3504    let open_target = event_target.clone();
3505    let open_change = node.handler("open_change").map(|handler| {
3506        let handler = handler.clone();
3507        let dispatcher = environment.dispatcher.cloned();
3508        Rc::new(move |open, window: &mut Window, cx: &mut App| {
3509            dispatch_ui_event(
3510                &handler,
3511                "open_change",
3512                UiValue::Bool(open),
3513                open_target.snapshot(),
3514                window,
3515                cx,
3516                dispatcher.as_ref(),
3517            );
3518        }) as crate::overlay_element::OpenChangeHandler
3519    });
3520    let panel_key = overlay_panel_key(
3521        node,
3522        environment.dispatcher,
3523        environment.direction,
3524        event_target,
3525    );
3526    let restore_focus_on_close =
3527        rendered_spec.modal || rendered_spec.kind == crate::OverlayKind::Menu;
3528    let overlay = ScriptOverlayElement::new(
3529        path,
3530        trigger,
3531        content,
3532        rendered_spec,
3533        open_change,
3534        panel_key,
3535        environment.overlays.clone(),
3536    );
3537    let backdrop_style = node.part_style("backdrop").map(|style| {
3538        let style = style.clone();
3539        let colors = OwnedColorResolver::capture(environment.colors);
3540        let direction = environment.direction;
3541        Rc::new(move |backdrop: Div| apply_style_override(backdrop, &style, &colors, direction))
3542            as crate::overlay_element::BackdropStyleHandler
3543    });
3544    overlay
3545        .with_backdrop_style(backdrop_style)
3546        .with_focus_ring(semantic_color(
3547            environment.colors,
3548            "focus_ring",
3549            0x003b_82f6,
3550        ))
3551        .with_focus_surface(semantic_color(environment.colors, "surface", 0x0018_181b))
3552        .restore_focus_on_close(restore_focus_on_close)
3553}
3554
3555fn overlay_panel_key(
3556    node: &UiNode,
3557    dispatcher: Option<&NodeEventDispatcher>,
3558    direction: TextDirection,
3559    target: EventTargetContext,
3560) -> Option<crate::overlay_element::PanelKeyHandler> {
3561    let handlers = node
3562        .handlers()
3563        .keys()
3564        .filter_map(|event| {
3565            event.strip_prefix("key:").and_then(|key| {
3566                node.handler(event)
3567                    .cloned()
3568                    .map(|handler| (key.to_owned(), handler))
3569            })
3570        })
3571        .collect::<BTreeMap<_, _>>();
3572    (!handlers.is_empty()).then(|| {
3573        let dispatcher = dispatcher.cloned();
3574        let payloads = node
3575            .handlers()
3576            .keys()
3577            .filter_map(|event| {
3578                event
3579                    .strip_prefix("key:")
3580                    .map(|key| (key.to_owned(), node.handler_payload(event).cloned()))
3581            })
3582            .collect::<BTreeMap<_, _>>();
3583        Rc::new(
3584            move |event: &gpui::KeyDownEvent, window: &mut Window, cx: &mut App| {
3585                let key = logical_keyboard_key(event.keystroke.key.as_str(), direction);
3586                let Some(callback) = handlers.get(key) else {
3587                    return false;
3588                };
3589                dispatch_ui_event(
3590                    callback,
3591                    "key",
3592                    payloads
3593                        .get(key)
3594                        .and_then(Clone::clone)
3595                        .unwrap_or(UiValue::Null),
3596                    target.snapshot(),
3597                    window,
3598                    cx,
3599                    dispatcher.as_ref(),
3600                );
3601                true
3602            },
3603        ) as crate::overlay_element::PanelKeyHandler
3604    })
3605}
3606
3607fn native_virtual_collection_element<C: ColorResolver>(
3608    spec: &crate::VirtualCollectionNodeSpec,
3609    environment: &RenderEnvironment<'_, C>,
3610    path: &str,
3611    retained_id: Option<NodeId>,
3612) -> VirtualListEntityElement {
3613    let retained_roots: BTreeMap<String, NodeId> = retained_id
3614        .map(|node| {
3615            retained_children(environment.retained, environment.retained_links, node)
3616                .into_iter()
3617                .filter(|child| child.group() == "items")
3618                .zip(spec.realized.keys())
3619                .filter_map(|(child, index)| {
3620                    crate::virtual_list_element::collection_item_key(spec, *index)
3621                        .map(|key| (format!("item:{key}"), child.node()))
3622                })
3623                .collect()
3624        })
3625        .unwrap_or_default();
3626    let runtime = owned_slot_runtime(environment, path, retained_roots);
3627    VirtualListEntityElement::new_collection(path, spec.clone(), runtime, retained_id)
3628}
3629
3630fn owned_slot_runtime<C: ColorResolver>(
3631    environment: &RenderEnvironment<'_, C>,
3632    path: &str,
3633    retained_roots: BTreeMap<String, NodeId>,
3634) -> NodeSlotRuntime {
3635    let retained_links = retained_link_subtrees(
3636        environment.retained,
3637        environment.retained_links,
3638        retained_roots.values().copied(),
3639    );
3640    let mut focus_handles = environment.focus_handles.clone();
3641    if let Some(tree) = environment.retained {
3642        let nodes = retained_links
3643            .iter()
3644            .flat_map(|(parent, children)| {
3645                std::iter::once(*parent).chain(children.iter().map(crate::RetainedChildLink::node))
3646            })
3647            .collect::<BTreeSet<_>>();
3648        for node in nodes {
3649            if let Some(focus) =
3650                primitive_focus_owner(Some(tree), Some(node), environment.focus_handles)
3651            {
3652                focus_handles.insert(node, focus);
3653            }
3654        }
3655    }
3656    NodeSlotRuntime {
3657        now: environment.now,
3658        clock: environment.clock.clone(),
3659        colors: OwnedColorResolver::capture(environment.colors),
3660        primitives: environment.primitives.clone(),
3661        assets: environment.assets.cloned().unwrap_or_default(),
3662        dispatcher: environment
3663            .dispatcher
3664            .cloned()
3665            .unwrap_or_else(|| NodeEventDispatcher::new(|_, _, _, _, _| EventPropagation::Handled)),
3666        overlays: environment.overlays.clone(),
3667        interactions: environment.interactions.clone(),
3668        motions: environment.motions.clone(),
3669        motion_preference: environment.motion_preference,
3670        motion_quality: environment.motion_quality,
3671        signals: environment.signals.clone(),
3672        geometry: environment.geometry.clone(),
3673        pointer_capture: environment.pointer_capture.clone(),
3674        focus_handles,
3675        scroll_handles: environment.scroll_handles.clone(),
3676        scroll_anchors: environment.scroll_anchors.clone(),
3677        virtual_requests: environment.virtual_requests.clone(),
3678        text_selection: environment.text_selection.clone(),
3679        host_focus: environment.host_focus.cloned(),
3680        direction: environment.direction,
3681        locale: environment.locale.to_owned(),
3682        number: environment.number.cloned(),
3683        ambient_text_color: environment.ambient_text_color,
3684        base_path: path.to_owned(),
3685        view_id: environment.view_id.to_owned(),
3686        semantics: environment.semantics.cloned().unwrap_or_default(),
3687        a11y_active: environment.a11y_active,
3688        retained_roots,
3689        retained_links,
3690    }
3691}
3692
3693fn render_table_layout<C: ColorResolver>(
3694    node: &UiNode,
3695    environment: &RenderEnvironment<'_, C>,
3696    path: &str,
3697    retained_id: Option<NodeId>,
3698) -> Option<AnyElement> {
3699    let crate::table_layout::TableLayout::Columns(columns) = node.table_layout()? else {
3700        return None;
3701    };
3702    let mut columns = columns.clone();
3703    collect_table_column_minima(node, &mut columns, environment, true);
3704    let mut style = node.style().resolve(environment.interaction);
3705    resolve_style_lengths(&mut style, environment.colors);
3706    let roots = retained_id
3707        .into_iter()
3708        .map(|root| ("table".to_owned(), root))
3709        .collect();
3710    let runtime = owned_slot_runtime(environment, path, roots);
3711    let table = node.clone();
3712    let root_path = path.to_owned();
3713    Some(
3714        crate::table_layout::TableViewportElement::new(
3715            &interaction_element_id(retained_id, path),
3716            columns,
3717            environment.signals.clone(),
3718            environment.direction,
3719            style,
3720            retained_id.and_then(|root| environment.scroll_handles.get(&root).cloned()),
3721            move |plan, border| {
3722                let mut table = table;
3723                table.resolve_table_layout(&plan, border);
3724                runtime.render_at(&table, &root_path, retained_id)
3725            },
3726        )
3727        .into_any_element(),
3728    )
3729}
3730
3731fn collect_table_column_minima<C: ColorResolver>(
3732    node: &UiNode,
3733    columns: &mut [crate::table_layout::TableColumn],
3734    environment: &RenderEnvironment<'_, C>,
3735    root: bool,
3736) {
3737    if !root && node.table_layout().is_some() {
3738        return;
3739    }
3740    if let Some(column) = node.table_column().and_then(|index| columns.get_mut(index)) {
3741        let mut style = node.style().resolve(environment.interaction);
3742        resolve_style_lengths(&mut style, environment.colors);
3743        column.include_minimum_style(&style, environment.direction);
3744    }
3745    match node.kind() {
3746        UiNodeKind::Box { children } | UiNodeKind::Fragment { children } => {
3747            for child in children {
3748                collect_table_column_minima(child, columns, environment, false);
3749            }
3750        }
3751        UiNodeKind::VirtualCollection { spec } => {
3752            for child in spec.realized.values() {
3753                collect_table_column_minima(child, columns, environment, false);
3754            }
3755        }
3756        _ => {}
3757    }
3758}
3759
3760fn retained_link_subtrees(
3761    tree: Option<&RetainedUiTree>,
3762    links: Option<&BTreeMap<NodeId, Vec<crate::RetainedChildLink>>>,
3763    roots: impl IntoIterator<Item = NodeId>,
3764) -> BTreeMap<NodeId, Vec<crate::RetainedChildLink>> {
3765    let mut result = BTreeMap::new();
3766    let mut pending = roots.into_iter().collect::<Vec<_>>();
3767    while let Some(node) = pending.pop() {
3768        let children = retained_children(tree, links, node);
3769        pending.extend(children.iter().map(crate::RetainedChildLink::node));
3770        result.insert(node, children);
3771    }
3772    result
3773}
3774
3775fn retained_children(
3776    tree: Option<&RetainedUiTree>,
3777    links: Option<&BTreeMap<NodeId, Vec<crate::RetainedChildLink>>>,
3778    node: NodeId,
3779) -> Vec<crate::RetainedChildLink> {
3780    tree.and_then(|tree| tree.node(node))
3781        .map(|node| node.children().cloned().collect())
3782        .or_else(|| links.and_then(|links| links.get(&node)).cloned())
3783        .unwrap_or_default()
3784}
3785
3786#[derive(Clone, Copy, Default)]
3787struct NodeMotionValues {
3788    opacity: Option<f64>,
3789    translate_x: Option<f64>,
3790    translate_y: Option<f64>,
3791    rotate: Option<f64>,
3792    scale_x: Option<f64>,
3793    scale_y: Option<f64>,
3794    skew_x: Option<f64>,
3795    skew_y: Option<f64>,
3796    width: Option<f64>,
3797    height: Option<f64>,
3798    clip_height: Option<f64>,
3799    path_progress: Option<f64>,
3800}
3801
3802impl NodeMotionValues {
3803    fn set(&mut self, property: MotionProperty, value: f64) {
3804        let target = match property {
3805            MotionProperty::Opacity => &mut self.opacity,
3806            MotionProperty::TranslateX => &mut self.translate_x,
3807            MotionProperty::TranslateY => &mut self.translate_y,
3808            MotionProperty::Rotate => &mut self.rotate,
3809            MotionProperty::ScaleX => &mut self.scale_x,
3810            MotionProperty::ScaleY => &mut self.scale_y,
3811            MotionProperty::SkewX => &mut self.skew_x,
3812            MotionProperty::SkewY => &mut self.skew_y,
3813            MotionProperty::Width => &mut self.width,
3814            MotionProperty::Height => &mut self.height,
3815            MotionProperty::ClipHeight => &mut self.clip_height,
3816            MotionProperty::PathProgress => &mut self.path_progress,
3817        };
3818        *target = Some(value);
3819    }
3820
3821    fn canvas_transform(self) -> crate::geometry::CanvasMotionTransform {
3822        crate::geometry::CanvasMotionTransform {
3823            rotate: self.rotate.unwrap_or(0.0),
3824            scale_x: self.scale_x.unwrap_or(1.0),
3825            scale_y: self.scale_y.unwrap_or(1.0),
3826            skew_x: self.skew_x.unwrap_or(0.0),
3827            skew_y: self.skew_y.unwrap_or(0.0),
3828            path_progress: self.path_progress,
3829        }
3830    }
3831}
3832
3833#[derive(Clone, Debug, Default)]
3834struct NodeSignalValues {
3835    opacity: Option<f64>,
3836    translate_x: Option<f64>,
3837    translate_y: Option<f64>,
3838    rotate: Option<f64>,
3839    scale_x: Option<f64>,
3840    scale_y: Option<f64>,
3841    width: Option<f64>,
3842    width_override: Option<f64>,
3843    height: Option<f64>,
3844    height_override: Option<f64>,
3845    background: Option<ColorValue>,
3846    text_color: Option<ColorValue>,
3847    border_color: Option<ColorValue>,
3848}
3849
3850fn node_signals(registry: &crate::SignalRegistry, node: &UiNode) -> NodeSignalValues {
3851    let mut values = NodeSignalValues::default();
3852    for (property, signal) in node.signal_bindings() {
3853        let Ok(value) = registry.read(signal) else {
3854            continue;
3855        };
3856        match (property, value) {
3857            (crate::SignalProperty::Opacity, crate::SignalValue::Float(value)) => {
3858                values.opacity = Some(value);
3859            }
3860            (crate::SignalProperty::TranslateX, crate::SignalValue::Float(value)) => {
3861                values.translate_x = Some(value);
3862            }
3863            (
3864                crate::SignalProperty::TranslateXOverride,
3865                crate::SignalValue::OptionalFloat(value),
3866            ) => {
3867                values.translate_x = value;
3868            }
3869            (crate::SignalProperty::TranslateY, crate::SignalValue::Float(value)) => {
3870                values.translate_y = Some(value);
3871            }
3872            (
3873                crate::SignalProperty::TranslateYOverride,
3874                crate::SignalValue::OptionalFloat(value),
3875            ) => {
3876                values.translate_y = value;
3877            }
3878            (crate::SignalProperty::Rotate, crate::SignalValue::Float(value)) => {
3879                values.rotate = Some(value);
3880            }
3881            (crate::SignalProperty::RotateOverride, crate::SignalValue::OptionalFloat(value)) => {
3882                values.rotate = value;
3883            }
3884            (crate::SignalProperty::ScaleX, crate::SignalValue::Float(value)) => {
3885                values.scale_x = Some(value);
3886            }
3887            (crate::SignalProperty::ScaleXOverride, crate::SignalValue::OptionalFloat(value)) => {
3888                values.scale_x = value;
3889            }
3890            (crate::SignalProperty::ScaleY, crate::SignalValue::Float(value)) => {
3891                values.scale_y = Some(value);
3892            }
3893            (crate::SignalProperty::ScaleYOverride, crate::SignalValue::OptionalFloat(value)) => {
3894                values.scale_y = value;
3895            }
3896            (crate::SignalProperty::Width, crate::SignalValue::Float(value)) => {
3897                values.width = Some(value);
3898            }
3899            (crate::SignalProperty::WidthOverride, crate::SignalValue::OptionalFloat(value)) => {
3900                values.width_override = value;
3901            }
3902            (crate::SignalProperty::Height, crate::SignalValue::Float(value)) => {
3903                values.height = Some(value);
3904            }
3905            (crate::SignalProperty::HeightOverride, crate::SignalValue::OptionalFloat(value)) => {
3906                values.height_override = value;
3907            }
3908            (crate::SignalProperty::Background, crate::SignalValue::Color(value)) => {
3909                values.background = Some(value);
3910            }
3911            (crate::SignalProperty::TextColor, crate::SignalValue::Color(value)) => {
3912                values.text_color = Some(value);
3913            }
3914            (crate::SignalProperty::BorderColor, crate::SignalValue::Color(value)) => {
3915                values.border_color = Some(value);
3916            }
3917            _ => debug_assert!(false, "validated signal binding changed type"),
3918        }
3919    }
3920    values
3921}
3922
3923fn apply_canvas_signal_transform(
3924    mut motion: NodeMotionValues,
3925    signals: &NodeSignalValues,
3926) -> NodeMotionValues {
3927    motion.rotate = signals.rotate.or(motion.rotate);
3928    motion.scale_x = signals.scale_x.or(motion.scale_x);
3929    motion.scale_y = signals.scale_y.or(motion.scale_y);
3930    motion
3931}
3932
3933fn apply_signal_style(style: &mut StyleProperties, values: &NodeSignalValues) {
3934    if let Some(width) = values.width {
3935        style.width = Some(Length::Pixels(width.max(0.0)).into());
3936    }
3937    if let Some(width) = values.width_override {
3938        style.width = Some(Length::Pixels(width.max(0.0)).into());
3939        style.flex_basis = None;
3940        style.flex_grow = Some(false);
3941        style.flex_grow_weight = None;
3942        style.flex_shrink = Some(false);
3943    }
3944    if let Some(height) = values.height {
3945        style.height = Some(Length::Pixels(height.max(0.0)).into());
3946    }
3947    if let Some(height) = values.height_override {
3948        style.height = Some(Length::Pixels(height.max(0.0)).into());
3949        style.flex_basis = None;
3950        style.flex_grow = Some(false);
3951        style.flex_grow_weight = None;
3952        style.flex_shrink = Some(false);
3953    }
3954    if let Some(background) = &values.background {
3955        style.background = Some(background.clone());
3956    }
3957    if let Some(text_color) = &values.text_color {
3958        style.text_color = Some(text_color.clone());
3959    }
3960    if let Some(border_color) = &values.border_color {
3961        style.border_color = Some(border_color.clone());
3962    }
3963}
3964
3965fn node_motion(values: &BTreeMap<MotionKey, f64>, path: &str) -> NodeMotionValues {
3966    let value = |property| values.get(&MotionKey::for_node(path, property)).copied();
3967    NodeMotionValues {
3968        opacity: value(MotionProperty::Opacity),
3969        translate_x: value(MotionProperty::TranslateX),
3970        translate_y: value(MotionProperty::TranslateY),
3971        rotate: value(MotionProperty::Rotate),
3972        scale_x: value(MotionProperty::ScaleX),
3973        scale_y: value(MotionProperty::ScaleY),
3974        skew_x: value(MotionProperty::SkewX),
3975        skew_y: value(MotionProperty::SkewY),
3976        width: value(MotionProperty::Width),
3977        height: value(MotionProperty::Height),
3978        clip_height: value(MotionProperty::ClipHeight),
3979        path_progress: value(MotionProperty::PathProgress),
3980    }
3981}
3982
3983fn apply_motion_dimensions(style: &mut StyleProperties, values: NodeMotionValues) {
3984    if let Some(width) = values.width {
3985        style.width = Some(Length::Pixels(width.max(0.0)).into());
3986    }
3987    if let Some(height) = values.height {
3988        style.height = Some(Length::Pixels(height.max(0.0)).into());
3989    }
3990    if let Some(height) = values.clip_height {
3991        style.height = Some(Length::Pixels(height.max(0.0)).into());
3992    }
3993}
3994
3995fn translated(element: AnyElement, x: Option<f64>, y: Option<f64>) -> AnyElement {
3996    let offset = point(
3997        px(f64_to_f32(x.unwrap_or(0.0))),
3998        px(f64_to_f32(y.unwrap_or(0.0))),
3999    );
4000    if offset == Point::default() {
4001        element
4002    } else {
4003        TranslatedElement {
4004            child: Some(element),
4005            offset,
4006        }
4007        .into_any_element()
4008    }
4009}
4010
4011pub(crate) fn f64_to_f32(value: f64) -> f32 {
4012    value.to_string().parse().unwrap_or_else(|_| {
4013        if value.is_sign_negative() {
4014            f32::MIN
4015        } else {
4016            f32::MAX
4017        }
4018    })
4019}
4020
4021struct TranslatedElement {
4022    child: Option<AnyElement>,
4023    offset: Point<Pixels>,
4024}
4025
4026#[derive(Clone, Debug)]
4027struct ActiveTextSelection {
4028    owner: String,
4029    node: Option<NodeId>,
4030    text: String,
4031    range: Option<(usize, usize)>,
4032    bounds: Bounds<Pixels>,
4033}
4034
4035/// One active text selection per mounted script view.
4036#[derive(Clone, Debug, Default)]
4037pub(crate) struct TextSelectionRegistry {
4038    active: Rc<RefCell<Option<ActiveTextSelection>>>,
4039}
4040
4041impl TextSelectionRegistry {
4042    fn begin(&self, owner: String, node: Option<NodeId>, text: String, bounds: Bounds<Pixels>) {
4043        self.active.borrow_mut().replace(ActiveTextSelection {
4044            owner,
4045            node,
4046            text,
4047            range: None,
4048            bounds,
4049        });
4050    }
4051
4052    fn update(&self, owner: &str, text: &str, bounds: Bounds<Pixels>, range: (usize, usize)) {
4053        let mut active = self.active.borrow_mut();
4054        let Some(selection) = active.as_mut().filter(|selection| selection.owner == owner) else {
4055            return;
4056        };
4057        text.clone_into(&mut selection.text);
4058        selection.range = (range.1 > range.0).then_some(range);
4059        selection.bounds = bounds;
4060    }
4061
4062    fn range(&self, owner: &str, text: &str) -> Option<(usize, usize)> {
4063        let mut active = self.active.borrow_mut();
4064        let selection = active.as_ref()?;
4065        if selection.owner != owner {
4066            return None;
4067        }
4068        if selection.text != text {
4069            active.take();
4070            return None;
4071        }
4072        selection.range
4073    }
4074
4075    pub(crate) fn selected_text(&self) -> Option<String> {
4076        let active = self.active.borrow();
4077        let selection = active.as_ref()?;
4078        let (start, end) = selection.range?;
4079        selection.text.get(start..end).map(ToOwned::to_owned)
4080    }
4081
4082    pub(crate) fn clear_outside(&self, position: Point<Pixels>) -> bool {
4083        let mut active = self.active.borrow_mut();
4084        if active
4085            .as_ref()
4086            .is_some_and(|selection| !selection.bounds.contains(&position))
4087        {
4088            active.take();
4089            true
4090        } else {
4091            false
4092        }
4093    }
4094
4095    pub(crate) fn retain(&self, tree: &RetainedUiTree) {
4096        let mut active = self.active.borrow_mut();
4097        if active
4098            .as_ref()
4099            .and_then(|selection| selection.node)
4100            .is_some_and(|node| tree.node(node).is_none())
4101        {
4102            active.take();
4103        }
4104    }
4105}
4106
4107/// Drag-to-select text for a `text().selectable(true)` node. Selection is
4108/// single-node and retained by stable node identity. Clipboard mutation is
4109/// deliberately handled by the host's normal copy action, not mouse-up.
4110struct SelectableText {
4111    id: ElementId,
4112    owner: String,
4113    node: Option<NodeId>,
4114    text: StyledText,
4115    highlight: Rgba8,
4116    selection: TextSelectionRegistry,
4117    host_focus: Option<FocusHandle>,
4118}
4119
4120#[derive(Default)]
4121struct SelectableTextState {
4122    anchor: Rc<std::cell::Cell<Option<usize>>>,
4123}
4124
4125impl SelectableText {
4126    fn new(
4127        owner: String,
4128        node: Option<NodeId>,
4129        text: &str,
4130        highlight: Rgba8,
4131        selection: TextSelectionRegistry,
4132        host_focus: Option<FocusHandle>,
4133    ) -> Self {
4134        Self {
4135            id: ElementId::Name(SharedString::from(format!("{owner}/selectable"))),
4136            owner,
4137            node,
4138            text: StyledText::new(text.to_owned()),
4139            highlight,
4140            selection,
4141            host_focus,
4142        }
4143    }
4144}
4145
4146pub(crate) fn selectable_text_element(
4147    owner: String,
4148    text: &str,
4149    highlight: Rgba8,
4150    selection: TextSelectionRegistry,
4151    host_focus: Option<FocusHandle>,
4152) -> AnyElement {
4153    SelectableText::new(owner, None, text, highlight, selection, host_focus).into_any_element()
4154}
4155
4156/// Build one tight highlight rectangle per visual line from UTF-8 character
4157/// boundaries. This avoids painting trailing whitespace across wrapped lines.
4158fn selection_rects(layout: &gpui::TextLayout, start: usize, end: usize) -> Vec<Bounds<Pixels>> {
4159    let text = layout.text();
4160    let Some(selected) = text.get(start..end) else {
4161        return Vec::new();
4162    };
4163    let mut rows: Vec<(Pixels, Pixels, Pixels)> = Vec::new();
4164    for index in selected
4165        .char_indices()
4166        .map(|(offset, _)| start + offset)
4167        .chain(std::iter::once(end))
4168    {
4169        let Some(position) = layout.position_for_index(index) else {
4170            continue;
4171        };
4172        if let Some((_, left, right)) = rows.iter_mut().find(|(y, _, _)| *y == position.y) {
4173            *left = (*left).min(position.x);
4174            *right = (*right).max(position.x);
4175        } else {
4176            rows.push((position.y, position.x, position.x));
4177        }
4178    }
4179    let line_height = layout.line_height();
4180    rows.into_iter()
4181        .filter(|(_, left, right)| right > left)
4182        .map(|(y, left, right)| Bounds::from_corners(point(left, y), point(right, y + line_height)))
4183        .collect()
4184}
4185
4186impl Element for SelectableText {
4187    type RequestLayoutState = ();
4188    type PrepaintState = gpui::Hitbox;
4189
4190    fn id(&self) -> Option<ElementId> {
4191        Some(self.id.clone())
4192    }
4193
4194    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4195        None
4196    }
4197
4198    fn request_layout(
4199        &mut self,
4200        _id: Option<&GlobalElementId>,
4201        inspector_id: Option<&InspectorElementId>,
4202        window: &mut Window,
4203        cx: &mut App,
4204    ) -> (LayoutId, Self::RequestLayoutState) {
4205        self.text.request_layout(None, inspector_id, window, cx)
4206    }
4207
4208    #[allow(clippy::too_many_lines)]
4209    fn prepaint(
4210        &mut self,
4211        _id: Option<&GlobalElementId>,
4212        inspector_id: Option<&InspectorElementId>,
4213        bounds: Bounds<Pixels>,
4214        state: &mut Self::RequestLayoutState,
4215        window: &mut Window,
4216        cx: &mut App,
4217    ) -> gpui::Hitbox {
4218        self.text
4219            .prepaint(None, inspector_id, bounds, state, window, cx);
4220        window.insert_hitbox(bounds, gpui::HitboxBehavior::Normal)
4221    }
4222
4223    fn paint(
4224        &mut self,
4225        global_id: Option<&GlobalElementId>,
4226        inspector_id: Option<&InspectorElementId>,
4227        bounds: Bounds<Pixels>,
4228        state: &mut Self::RequestLayoutState,
4229        hitbox: &mut Self::PrepaintState,
4230        window: &mut Window,
4231        cx: &mut App,
4232    ) {
4233        let Some(global_id) = global_id else {
4234            self.text
4235                .paint(None, inspector_id, bounds, state, &mut (), window, cx);
4236            return;
4237        };
4238        let layout = self.text.layout().clone();
4239        let rendered_text = layout.text();
4240        let highlight = rgba(self.highlight.as_rgba_hex());
4241        let anchor = window.with_element_state::<SelectableTextState, _>(global_id, |prev, _| {
4242            let prev = prev.unwrap_or_default();
4243            (prev.anchor.clone(), prev)
4244        });
4245
4246        // Highlight under the glyphs: paint quads first, text second.
4247        if let Some((start, end)) = self.selection.range(&self.owner, &rendered_text) {
4248            for rect in selection_rects(&layout, start, end) {
4249                window.paint_quad(gpui::fill(rect, highlight));
4250            }
4251        }
4252        self.text
4253            .paint(None, inspector_id, bounds, state, &mut (), window, cx);
4254        window.set_cursor_style(CursorStyle::IBeam, hitbox);
4255
4256        let clamp = |index: Result<usize, usize>| match index {
4257            Ok(ix) | Err(ix) => ix,
4258        };
4259
4260        {
4261            let anchor = anchor.clone();
4262            let layout = layout.clone();
4263            let hitbox = hitbox.clone();
4264            let owner = self.owner.clone();
4265            let node = self.node;
4266            let selection = self.selection.clone();
4267            let host_focus = self.host_focus.clone();
4268            window.on_mouse_event(move |event: &MouseDownEvent, phase, window, cx| {
4269                if phase.bubble() && event.button == MouseButton::Left && hitbox.is_hovered(window)
4270                {
4271                    let index = clamp(layout.index_for_position(event.position));
4272                    anchor.set(Some(index));
4273                    selection.begin(owner.clone(), node, layout.text(), hitbox.bounds);
4274                    if let Some(focus) = &host_focus {
4275                        focus.focus(window, cx);
4276                    }
4277                    window.refresh();
4278                }
4279            });
4280        }
4281        {
4282            let anchor = anchor.clone();
4283            let layout = layout.clone();
4284            let owner = self.owner.clone();
4285            let selection = self.selection.clone();
4286            window.on_mouse_event(move |event: &MouseMoveEvent, phase, window, cx| {
4287                if phase.bubble()
4288                    && event.pressed_button == Some(MouseButton::Left)
4289                    && let Some(from) = anchor.get()
4290                {
4291                    let to = clamp(layout.index_for_position(event.position));
4292                    let range = (from.min(to), from.max(to));
4293                    selection.update(&owner, &layout.text(), layout.bounds(), range);
4294                    if range.1 > range.0 {
4295                        cx.stop_propagation();
4296                    }
4297                    window.refresh();
4298                }
4299            });
4300        }
4301        {
4302            let owner = self.owner.clone();
4303            let selection = self.selection.clone();
4304            window.on_mouse_event(move |event: &MouseUpEvent, phase, window, cx| {
4305                if phase.bubble() && event.button == MouseButton::Left && anchor.take().is_some() {
4306                    if selection.range(&owner, &layout.text()).is_some() {
4307                        cx.stop_propagation();
4308                    }
4309                    window.refresh();
4310                }
4311            });
4312        }
4313    }
4314}
4315
4316impl IntoElement for SelectableText {
4317    type Element = Self;
4318
4319    fn into_element(self) -> Self::Element {
4320        self
4321    }
4322}
4323
4324fn layout_motion_spec(node: &UiNode, now: Instant) -> Option<LayoutMotionRenderSpec> {
4325    let duration_ms = match node.attributes().get("layout_motion_duration_ms") {
4326        Some(UiValue::Integer(value)) => u64::try_from(*value).ok()?,
4327        _ => return None,
4328    };
4329    let easing = match node.attributes().get("layout_motion_easing") {
4330        Some(UiValue::String(value)) => crate::MotionEasing::parse(value).ok()?,
4331        _ => crate::MotionEasing::EaseOut,
4332    };
4333    let shared = match (
4334        node.attributes().get("shared_layout_group"),
4335        node.attributes().get("shared_layout_id"),
4336    ) {
4337        (Some(UiValue::String(group)), Some(UiValue::String(id))) => {
4338            Some((group.clone(), id.clone()))
4339        }
4340        _ => None,
4341    };
4342    Some(LayoutMotionRenderSpec {
4343        now,
4344        duration: std::time::Duration::from_millis(duration_ms),
4345        easing,
4346        shared,
4347    })
4348}
4349
4350struct GeometryTrackedElement {
4351    child: Option<AnyElement>,
4352    node: NodeId,
4353    registry: crate::GeometryRegistry,
4354    translate_x: f64,
4355    translate_y: f64,
4356    layout_motion: Option<LayoutMotionRenderSpec>,
4357    progress_motions: Vec<crate::MotionProgressBinding>,
4358    scroll_handles: Vec<ScrollHandle>,
4359    focus_handle: Option<FocusHandle>,
4360    now: Instant,
4361    motion_preference: crate::MotionPreference,
4362}
4363
4364#[derive(Clone, Debug)]
4365struct LayoutMotionRenderSpec {
4366    now: Instant,
4367    duration: std::time::Duration,
4368    easing: crate::MotionEasing,
4369    shared: Option<(String, String)>,
4370}
4371
4372impl Element for GeometryTrackedElement {
4373    type RequestLayoutState = AnyElement;
4374    type PrepaintState = ();
4375
4376    fn id(&self) -> Option<ElementId> {
4377        None
4378    }
4379
4380    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4381        None
4382    }
4383
4384    fn request_layout(
4385        &mut self,
4386        _id: Option<&GlobalElementId>,
4387        _inspector_id: Option<&InspectorElementId>,
4388        window: &mut Window,
4389        cx: &mut App,
4390    ) -> (LayoutId, Self::RequestLayoutState) {
4391        let mut child = self.child.take().expect("tracked element renders once");
4392        let layout = child.request_layout(window, cx);
4393        (layout, child)
4394    }
4395
4396    #[allow(clippy::too_many_lines)]
4397    fn prepaint(
4398        &mut self,
4399        _id: Option<&GlobalElementId>,
4400        _inspector_id: Option<&InspectorElementId>,
4401        bounds: Bounds<Pixels>,
4402        child: &mut Self::RequestLayoutState,
4403        window: &mut Window,
4404        cx: &mut App,
4405    ) {
4406        let base_offset = window.pixel_snap_point(window.element_offset());
4407        self.registry.record_element_offset(
4408            self.node,
4409            (f64::from(base_offset.x), f64::from(base_offset.y)),
4410        );
4411        if let Ok(layout) = crate::GeometryBounds::new(
4412            f64::from(bounds.origin.x),
4413            f64::from(bounds.origin.y),
4414            f64::from(bounds.size.width),
4415            f64::from(bounds.size.height),
4416        ) {
4417            let viewport = window.viewport_size();
4418            for binding in &self.progress_motions {
4419                if binding.driver == crate::MotionProgressDriver::Focus {
4420                    self.registry.set_motion_trigger(
4421                        self.node,
4422                        crate::MotionProgressDriver::Focus,
4423                        self.focus_handle
4424                            .as_ref()
4425                            .is_some_and(|handle| handle.contains_focused(window, cx)),
4426                    );
4427                }
4428                if matches!(
4429                    binding.driver,
4430                    crate::MotionProgressDriver::Hover
4431                        | crate::MotionProgressDriver::Press
4432                        | crate::MotionProgressDriver::Focus
4433                ) {
4434                    let sample = self.registry.sample_motion_trigger(
4435                        self.node,
4436                        binding,
4437                        self.motion_preference,
4438                        self.now,
4439                    );
4440                    let changed = self.registry.update_motion_progress(
4441                        self.node,
4442                        binding.property(),
4443                        sample.value,
4444                    );
4445                    if changed || sample.active {
4446                        window.request_animation_frame();
4447                    }
4448                    continue;
4449                }
4450                let progress = match binding.driver {
4451                    crate::MotionProgressDriver::InView => {
4452                        let visible_width = (layout.x + layout.width)
4453                            .min(f64::from(viewport.width))
4454                            .max(0.0)
4455                            - layout.x.max(0.0);
4456                        let visible_height = (layout.y + layout.height)
4457                            .min(f64::from(viewport.height))
4458                            .max(0.0)
4459                            - layout.y.max(0.0);
4460                        if layout.width <= f64::EPSILON || layout.height <= f64::EPSILON {
4461                            0.0
4462                        } else {
4463                            (visible_width.max(0.0) * visible_height.max(0.0)
4464                                / (layout.width * layout.height))
4465                                .clamp(0.0, 1.0)
4466                        }
4467                    }
4468                    crate::MotionProgressDriver::Viewport => ((f64::from(viewport.height)
4469                        - layout.y)
4470                        / (f64::from(viewport.height) + layout.height).max(1.0))
4471                    .clamp(0.0, 1.0),
4472                    crate::MotionProgressDriver::ScrollX => {
4473                        self.scroll_handles.first().map_or(0.0, |handle| {
4474                            let maximum = f64::from(handle.max_offset().x).abs();
4475                            if maximum <= f64::EPSILON {
4476                                0.0
4477                            } else {
4478                                (-f64::from(handle.offset().x) / maximum).clamp(0.0, 1.0)
4479                            }
4480                        })
4481                    }
4482                    crate::MotionProgressDriver::ScrollY => {
4483                        self.scroll_handles.first().map_or(0.0, |handle| {
4484                            let maximum = f64::from(handle.max_offset().y).abs();
4485                            if maximum <= f64::EPSILON {
4486                                0.0
4487                            } else {
4488                                (-f64::from(handle.offset().y) / maximum).clamp(0.0, 1.0)
4489                            }
4490                        })
4491                    }
4492                    crate::MotionProgressDriver::Hover
4493                    | crate::MotionProgressDriver::Press
4494                    | crate::MotionProgressDriver::Focus => unreachable!(),
4495                };
4496                let value = crate::motion::sample_progress_source(&binding.source, progress);
4497                if self
4498                    .registry
4499                    .update_motion_progress(self.node, binding.property(), value)
4500                {
4501                    window.request_animation_frame();
4502                }
4503            }
4504            let sample = self.layout_motion.as_ref().map_or_else(
4505                crate::geometry::LayoutMotionSample::default,
4506                |spec| {
4507                    self.registry.sample_layout_motion(
4508                        self.node,
4509                        layout,
4510                        crate::geometry::LayoutMotionRequest {
4511                            shared: spec
4512                                .shared
4513                                .as_ref()
4514                                .map(|(group, id)| (group.as_str(), id.as_str())),
4515                            duration: spec.duration,
4516                            easing: spec.easing,
4517                            preference: self.motion_preference,
4518                            now: spec.now,
4519                        },
4520                    )
4521                },
4522            );
4523            if sample.active {
4524                window.request_animation_frame();
4525            }
4526            self.registry.update(
4527                self.node,
4528                crate::ElementGeometry {
4529                    layout,
4530                    visual: crate::GeometryBounds {
4531                        x: layout.x + self.translate_x + sample.offset_x,
4532                        y: layout.y + self.translate_y + sample.offset_y,
4533                        width: layout.width * sample.scale_x,
4534                        height: layout.height * sample.scale_y,
4535                    },
4536                    clip: None,
4537                },
4538            );
4539            let offset = point(
4540                px(f64_to_f32(sample.offset_x)),
4541                px(f64_to_f32(sample.offset_y)),
4542            );
4543            if offset != Point::default() {
4544                window.with_element_offset(offset, |window| child.prepaint(window, cx));
4545                return;
4546            }
4547        }
4548        child.prepaint(window, cx);
4549    }
4550
4551    fn paint(
4552        &mut self,
4553        _id: Option<&GlobalElementId>,
4554        _inspector_id: Option<&InspectorElementId>,
4555        _bounds: Bounds<Pixels>,
4556        child: &mut Self::RequestLayoutState,
4557        _prepaint: &mut Self::PrepaintState,
4558        window: &mut Window,
4559        cx: &mut App,
4560    ) {
4561        child.paint(window, cx);
4562    }
4563}
4564
4565impl IntoElement for GeometryTrackedElement {
4566    type Element = Self;
4567
4568    fn into_element(self) -> Self::Element {
4569        self
4570    }
4571}
4572
4573impl Element for TranslatedElement {
4574    type RequestLayoutState = AnyElement;
4575    type PrepaintState = ();
4576
4577    fn id(&self) -> Option<ElementId> {
4578        None
4579    }
4580
4581    fn source_location(&self) -> Option<&'static core::panic::Location<'static>> {
4582        None
4583    }
4584
4585    fn request_layout(
4586        &mut self,
4587        _id: Option<&GlobalElementId>,
4588        _inspector_id: Option<&InspectorElementId>,
4589        window: &mut Window,
4590        cx: &mut App,
4591    ) -> (LayoutId, Self::RequestLayoutState) {
4592        let mut child = self.child.take().expect("translated element renders once");
4593        let layout = child.request_layout(window, cx);
4594        (layout, child)
4595    }
4596
4597    fn prepaint(
4598        &mut self,
4599        _id: Option<&GlobalElementId>,
4600        _inspector_id: Option<&InspectorElementId>,
4601        _bounds: Bounds<Pixels>,
4602        child: &mut Self::RequestLayoutState,
4603        window: &mut Window,
4604        cx: &mut App,
4605    ) {
4606        window.with_element_offset(self.offset, |window| child.prepaint(window, cx));
4607    }
4608
4609    fn paint(
4610        &mut self,
4611        _id: Option<&GlobalElementId>,
4612        _inspector_id: Option<&InspectorElementId>,
4613        _bounds: Bounds<Pixels>,
4614        child: &mut Self::RequestLayoutState,
4615        _prepaint: &mut Self::PrepaintState,
4616        window: &mut Window,
4617        cx: &mut App,
4618    ) {
4619        child.paint(window, cx);
4620    }
4621}
4622
4623impl IntoElement for TranslatedElement {
4624    type Element = Self;
4625
4626    fn into_element(self) -> Self::Element {
4627        self
4628    }
4629}
4630
4631fn semantic_color(colors: &impl ColorResolver, token: &str, fallback: u32) -> Rgba8 {
4632    colors
4633        .resolve(&ColorValue::Token(token.to_owned()))
4634        .unwrap_or_else(|| Rgba8::from_rgb_hex(fallback))
4635}
4636
4637#[derive(Clone, Copy, Default)]
4638struct PseudoPaint {
4639    background: Option<Rgba8>,
4640    border: Option<Rgba8>,
4641    text: Option<Rgba8>,
4642    opacity: Option<f32>,
4643}
4644
4645fn pseudo_paint(properties: Option<&StyleProperties>, colors: &impl ColorResolver) -> PseudoPaint {
4646    let Some(properties) = properties else {
4647        return PseudoPaint::default();
4648    };
4649    PseudoPaint {
4650        background: properties
4651            .background
4652            .as_ref()
4653            .and_then(|color| colors.resolve(color)),
4654        border: properties
4655            .border_color
4656            .as_ref()
4657            .and_then(|color| colors.resolve(color)),
4658        text: properties
4659            .text_color
4660            .as_ref()
4661            .and_then(|color| colors.resolve(color)),
4662        opacity: properties
4663            .opacity
4664            .map(|opacity| f64_to_f32(opacity.clamp(0.0, 1.0))),
4665    }
4666}
4667
4668fn apply_pseudo_paint(
4669    mut style: gpui::StyleRefinement,
4670    paint: PseudoPaint,
4671) -> gpui::StyleRefinement {
4672    if let Some(color) = paint.background {
4673        style = style.bg(rgba(color.as_rgba_hex()));
4674    }
4675    if let Some(color) = paint.border {
4676        style.style().border_color = Some(rgba(color.as_rgba_hex()).into());
4677    }
4678    if let Some(color) = paint.text {
4679        style = style.text_color(rgba(color.as_rgba_hex()));
4680    }
4681    if let Some(opacity) = paint.opacity {
4682        style = style.opacity(opacity);
4683    }
4684    style
4685}
4686
4687fn apply_pseudo_backgrounds(
4688    mut element: Stateful<Div>,
4689    style: &Style,
4690    colors: &impl ColorResolver,
4691) -> Stateful<Div> {
4692    let hover = pseudo_paint(style.hover.as_ref(), colors);
4693    let active = pseudo_paint(style.active.as_ref(), colors);
4694    let mut focus = pseudo_paint(style.focus.as_ref(), colors);
4695    if focus.border.is_none() {
4696        focus.border = Some(semantic_color(colors, "focus_ring", 0x003b_82f6));
4697    }
4698    element = element.hover(move |style| apply_pseudo_paint(style, hover));
4699    element = element.active(move |style| apply_pseudo_paint(style, active));
4700    element = element.focus(move |style| apply_pseudo_paint(style, focus));
4701    element
4702}
4703
4704fn is_disabled(node: &UiNode) -> bool {
4705    node.attributes().get("disabled") == Some(&UiValue::Bool(true))
4706}
4707
4708fn apply_style(
4709    element: Div,
4710    style: &StyleProperties,
4711    colors: &impl ColorResolver,
4712    direction: TextDirection,
4713) -> Div {
4714    let mut style = style.clone();
4715    resolve_typography_role(&mut style, colors);
4716    resolve_style_lengths(&mut style, colors);
4717    let element = apply_layout(element, &style, direction);
4718    let element = apply_spacing(element, &style, direction);
4719    apply_paint_and_text(element, &style, colors, direction)
4720}
4721
4722fn resolve_typography_role(style: &mut StyleProperties, resolver: &impl ColorResolver) {
4723    let Some(role) = style.typography.as_deref() else {
4724        return;
4725    };
4726    let Some(typography) = resolver.resolve_typography(role) else {
4727        return;
4728    };
4729    if style.font_family.is_none() {
4730        style.font_family = typography.family;
4731    }
4732    if style.font_fallbacks.is_none() && !typography.fallbacks.is_empty() {
4733        style.font_fallbacks = Some(typography.fallbacks);
4734    }
4735    if style.font_size.is_none() {
4736        style.font_size = Some(typography.size);
4737    }
4738    if style.line_height.is_none() {
4739        style.line_height = Some(typography.line_height);
4740    }
4741    if style.font_weight.is_none() {
4742        style.font_weight = Some(typography.weight);
4743    }
4744}
4745
4746fn resolve_style_lengths(style: &mut StyleProperties, resolver: &impl ColorResolver) {
4747    let resolve_definite = |value: &mut Option<Length>| {
4748        *value = (*value).and_then(|value| resolver.resolve_length(value));
4749    };
4750    let resolve_layout = |value: &mut Option<LayoutLength>| {
4751        *value = (*value).and_then(|value| match value {
4752            LayoutLength::Definite(value) => {
4753                resolver.resolve_length(value).map(LayoutLength::Definite)
4754            }
4755            LayoutLength::Signed(_) | LayoutLength::Auto => Some(value),
4756        });
4757    };
4758    for value in [
4759        &mut style.width,
4760        &mut style.height,
4761        &mut style.min_width,
4762        &mut style.max_width,
4763        &mut style.min_height,
4764        &mut style.max_height,
4765        &mut style.flex_basis,
4766    ] {
4767        resolve_layout(value);
4768    }
4769    for value in [
4770        &mut style.margin.top,
4771        &mut style.margin.right,
4772        &mut style.margin.bottom,
4773        &mut style.margin.left,
4774        &mut style.margin.start,
4775        &mut style.margin.end,
4776        &mut style.top,
4777        &mut style.right,
4778        &mut style.bottom,
4779        &mut style.left,
4780        &mut style.inset_start,
4781        &mut style.inset_end,
4782    ] {
4783        resolve_layout(value);
4784    }
4785    for value in [
4786        &mut style.gap,
4787        &mut style.padding.top,
4788        &mut style.padding.right,
4789        &mut style.padding.bottom,
4790        &mut style.padding.left,
4791        &mut style.padding.start,
4792        &mut style.padding.end,
4793        &mut style.border_widths.top,
4794        &mut style.border_widths.right,
4795        &mut style.border_widths.bottom,
4796        &mut style.border_widths.left,
4797        &mut style.border_widths.start,
4798        &mut style.border_widths.end,
4799        &mut style.radii.top_left,
4800        &mut style.radii.top_right,
4801        &mut style.radii.bottom_right,
4802        &mut style.radii.bottom_left,
4803        &mut style.font_size,
4804        &mut style.line_height,
4805    ] {
4806        resolve_definite(value);
4807    }
4808}
4809
4810pub(crate) fn apply_style_override(
4811    element: Div,
4812    style: &Style,
4813    colors: &impl ColorResolver,
4814    direction: TextDirection,
4815) -> Div {
4816    apply_style(
4817        element,
4818        &style.resolve(&InteractionState::default()),
4819        colors,
4820        direction,
4821    )
4822}
4823
4824fn apply_layout(element: Div, style: &StyleProperties, text_direction: TextDirection) -> Div {
4825    let element = apply_display_and_position(element, style, text_direction);
4826    let element = apply_flex_alignment(element, style, text_direction);
4827    apply_layout_dimensions(element, style)
4828}
4829
4830fn apply_display_and_position(
4831    mut element: Div,
4832    style: &StyleProperties,
4833    direction: TextDirection,
4834) -> Div {
4835    if let Some(display) = style.display {
4836        element = match display {
4837            DisplayMode::Block => element.block(),
4838            DisplayMode::Flex => element.flex(),
4839            DisplayMode::Grid => element.grid(),
4840            DisplayMode::None => element.hidden(),
4841        };
4842    }
4843    if let Some(position) = style.position {
4844        element = match position {
4845            PositionMode::Relative => element.relative(),
4846            PositionMode::Absolute => element.absolute(),
4847        };
4848    }
4849    if let Some(value) = style.top {
4850        element = inset_top(element, value);
4851    }
4852    let (left, right) = logical_horizontal_edges(
4853        style.left,
4854        style.right,
4855        style.inset_start,
4856        style.inset_end,
4857        direction,
4858    );
4859    if let Some(value) = right {
4860        element = inset_right(element, value);
4861    }
4862    if let Some(value) = style.bottom {
4863        element = inset_bottom(element, value);
4864    }
4865    if let Some(value) = left {
4866        element = inset_left(element, value);
4867    }
4868    if matches!(style.overflow_x, Some(OverflowMode::Hidden))
4869        || matches!(style.overflow_y, Some(OverflowMode::Hidden))
4870    {
4871        element = element.overflow_hidden();
4872    }
4873    element
4874}
4875
4876fn apply_flex_alignment(
4877    mut element: Div,
4878    style: &StyleProperties,
4879    text_direction: TextDirection,
4880) -> Div {
4881    if let Some(direction) = style.direction {
4882        element = element.flex();
4883        element = match (direction, text_direction) {
4884            (FlexDirection::Row, TextDirection::LeftToRight) => element.flex_row(),
4885            (FlexDirection::Row, TextDirection::RightToLeft) => element.flex_row_reverse(),
4886            (FlexDirection::Column, _) => element.flex_col(),
4887        };
4888    }
4889    if let Some(wrap) = style.flex_wrap {
4890        element = match wrap {
4891            FlexWrapMode::NoWrap => element.flex_nowrap(),
4892            FlexWrapMode::Wrap => element.flex_wrap(),
4893            FlexWrapMode::WrapReverse => element.flex_wrap_reverse(),
4894        };
4895    }
4896    if let Some(align) = style.align_self {
4897        let align = if text_direction == TextDirection::RightToLeft {
4898            match align {
4899                Align::Start => Align::End,
4900                Align::End => Align::Start,
4901                other => other,
4902            }
4903        } else {
4904            align
4905        };
4906        element.style().align_self = Some(match align {
4907            Align::Start => GpuiAlignSelf::Start,
4908            Align::Center => GpuiAlignSelf::Center,
4909            Align::End => GpuiAlignSelf::End,
4910            Align::Stretch => GpuiAlignSelf::Stretch,
4911        });
4912    }
4913    if let Some(align) = style.align {
4914        let align = if style.direction == Some(FlexDirection::Column)
4915            && text_direction == TextDirection::RightToLeft
4916        {
4917            match align {
4918                Align::Start => Align::End,
4919                Align::End => Align::Start,
4920                other => other,
4921            }
4922        } else {
4923            align
4924        };
4925        element = match align {
4926            Align::Start => element.items_start(),
4927            Align::Center => element.items_center(),
4928            Align::End => element.items_end(),
4929            Align::Stretch => element,
4930        };
4931    }
4932    if let Some(justify) = style.justify {
4933        element = match justify {
4934            Justify::Start => element.justify_start(),
4935            Justify::Center => element.justify_center(),
4936            Justify::End => element.justify_end(),
4937            Justify::Between => element.justify_between(),
4938            Justify::Around => element.justify_around(),
4939        };
4940    }
4941    element
4942}
4943
4944fn apply_layout_dimensions(mut element: Div, style: &StyleProperties) -> Div {
4945    if let Some(value) = style.width {
4946        element = width(element, value);
4947    }
4948    if let Some(value) = style.height {
4949        element = height(element, value);
4950    }
4951    if let Some(value) = style.min_width {
4952        element = min_width(element, value);
4953    }
4954    if let Some(value) = style.max_width {
4955        element = max_width(element, value);
4956    }
4957    if let Some(value) = style.min_height {
4958        element = min_height(element, value);
4959    }
4960    if let Some(value) = style.max_height {
4961        element = max_height(element, value);
4962    }
4963    if let Some(value) = style.gap {
4964        element = gap(element, value);
4965    }
4966    if let Some(weight) = style.flex_grow_weight {
4967        element.style().flex_grow = Some(to_f32(weight));
4968    } else if style.flex_grow == Some(true) {
4969        element = element.flex_grow_1();
4970    }
4971    if let Some(shrink) = style.flex_shrink {
4972        element = if shrink {
4973            element.flex_shrink_1()
4974        } else {
4975            element.flex_shrink_0()
4976        };
4977    }
4978    if let Some(value) = style.flex_basis {
4979        element = flex_basis(element, value);
4980    }
4981    if let Some(columns) = style.grid_columns {
4982        element = element.grid_cols(columns);
4983    }
4984    if let Some(rows) = style.grid_rows {
4985        element = element.grid_rows(rows);
4986    }
4987    if let Some(span) = style.column_span {
4988        element = element.col_span(span);
4989    }
4990    if let Some(span) = style.row_span {
4991        element = element.row_span(span);
4992    }
4993    element
4994}
4995
4996fn apply_spacing(mut element: Div, style: &StyleProperties, direction: TextDirection) -> Div {
4997    if let Some(value) = style.padding.top {
4998        element = padding_top(element, value);
4999    }
5000    let (padding_left_value, padding_right_value) = logical_horizontal_edges(
5001        style.padding.left,
5002        style.padding.right,
5003        style.padding.start,
5004        style.padding.end,
5005        direction,
5006    );
5007    if let Some(value) = padding_right_value {
5008        element = padding_right(element, value);
5009    }
5010    if let Some(value) = style.padding.bottom {
5011        element = padding_bottom(element, value);
5012    }
5013    if let Some(value) = padding_left_value {
5014        element = padding_left(element, value);
5015    }
5016    if let Some(value) = style.margin.top {
5017        element = margin_top(element, value);
5018    }
5019    let (margin_left_value, margin_right_value) = logical_horizontal_edges(
5020        style.margin.left,
5021        style.margin.right,
5022        style.margin.start,
5023        style.margin.end,
5024        direction,
5025    );
5026    if let Some(value) = margin_right_value {
5027        element = margin_right(element, value);
5028    }
5029    if let Some(value) = style.margin.bottom {
5030        element = margin_bottom(element, value);
5031    }
5032    if let Some(value) = margin_left_value {
5033        element = margin_left(element, value);
5034    }
5035    element
5036}
5037
5038fn logical_horizontal_edges<T: Copy>(
5039    mut left: Option<T>,
5040    mut right: Option<T>,
5041    start: Option<T>,
5042    end: Option<T>,
5043    direction: TextDirection,
5044) -> (Option<T>, Option<T>) {
5045    match direction {
5046        TextDirection::LeftToRight => {
5047            if start.is_some() {
5048                left = start;
5049            }
5050            if end.is_some() {
5051                right = end;
5052            }
5053        }
5054        TextDirection::RightToLeft => {
5055            if start.is_some() {
5056                right = start;
5057            }
5058            if end.is_some() {
5059                left = end;
5060            }
5061        }
5062    }
5063    (left, right)
5064}
5065
5066fn logical_keyboard_key(key: &str, direction: TextDirection) -> &str {
5067    match (key, direction) {
5068        ("left", TextDirection::RightToLeft) => "right",
5069        ("right", TextDirection::RightToLeft) => "left",
5070        _ => key,
5071    }
5072}
5073
5074fn apply_paint_and_text(
5075    element: Div,
5076    style: &StyleProperties,
5077    colors: &impl ColorResolver,
5078    direction: TextDirection,
5079) -> Div {
5080    let element = apply_paint(element, style, colors, direction);
5081    let element = apply_typography(element, style, direction);
5082    apply_shadows(element, style, colors)
5083}
5084
5085fn apply_paint(
5086    mut element: Div,
5087    style: &StyleProperties,
5088    colors: &impl ColorResolver,
5089    direction: TextDirection,
5090) -> Div {
5091    if let Some(gradient) = &style.gradient
5092        && let (Some(from), Some(to)) =
5093            (colors.resolve(&gradient.from), colors.resolve(&gradient.to))
5094    {
5095        element = element.bg(linear_gradient(
5096            f64_to_f32(gradient.angle_degrees),
5097            linear_color_stop(rgba(from.as_rgba_hex()), 0.0),
5098            linear_color_stop(rgba(to.as_rgba_hex()), 1.0),
5099        ));
5100    } else if let Some(color) = style
5101        .background
5102        .as_ref()
5103        .and_then(|color| colors.resolve(color))
5104    {
5105        element = element.bg(rgba(color.as_rgba_hex()));
5106    }
5107    if let Some(color) = style
5108        .text_color
5109        .as_ref()
5110        .and_then(|color| colors.resolve(color))
5111    {
5112        element = element.text_color(rgba(color.as_rgba_hex()));
5113    }
5114    if let Some(color) = style
5115        .border_color
5116        .as_ref()
5117        .and_then(|color| colors.resolve(color))
5118    {
5119        element = element.border_color(rgba(color.as_rgba_hex()));
5120    }
5121    if let Some(border_style) = style.border_style {
5122        match border_style {
5123            crate::BorderLineStyle::Solid => {
5124                element.style().border_style = Some(gpui::BorderStyle::Solid);
5125            }
5126            crate::BorderLineStyle::Dashed => {
5127                element = element.border_dashed();
5128            }
5129        }
5130    }
5131    if let Some(value) = style.border_widths.top {
5132        element = border_top(element, value);
5133    }
5134    let (border_left_value, border_right_value) = logical_horizontal_edges(
5135        style.border_widths.left,
5136        style.border_widths.right,
5137        style.border_widths.start,
5138        style.border_widths.end,
5139        direction,
5140    );
5141    if let Some(value) = border_right_value {
5142        element = border_right(element, value);
5143    }
5144    if let Some(value) = style.border_widths.bottom {
5145        element = border_bottom(element, value);
5146    }
5147    if let Some(value) = border_left_value {
5148        element = border_left(element, value);
5149    }
5150    if let Some(value) = style.radii.top_left {
5151        element = radius_top_left(element, value);
5152    }
5153    if let Some(value) = style.radii.top_right {
5154        element = radius_top_right(element, value);
5155    }
5156    if let Some(value) = style.radii.bottom_right {
5157        element = radius_bottom_right(element, value);
5158    }
5159    if let Some(value) = style.radii.bottom_left {
5160        element = radius_bottom_left(element, value);
5161    }
5162    if let Some(value) = style.font_size {
5163        element = font_size(element, value);
5164    }
5165    if let Some(opacity) = style.opacity {
5166        element = element.opacity(f64_to_f32(opacity));
5167    }
5168    if let Some(visible) = style.visible {
5169        element = if visible {
5170            element.visible()
5171        } else {
5172            element.invisible()
5173        };
5174    }
5175    if let Some(cursor) = style.cursor {
5176        element = element.cursor(gpui_cursor(cursor));
5177    }
5178    element
5179}
5180
5181fn apply_typography(mut element: Div, style: &StyleProperties, direction: TextDirection) -> Div {
5182    if let Some(family) = &style.font_family {
5183        element = element.font_family(family.clone());
5184    }
5185    if let Some(fallbacks) = &style.font_fallbacks {
5186        element.text_style().font_fallbacks = Some(FontFallbacks::from_fonts(fallbacks.clone()));
5187    }
5188    if let Some(features) = &style.font_features {
5189        element.text_style().font_features = Some(FontFeatures(Arc::new(
5190            features
5191                .iter()
5192                .map(|(tag, value)| (tag.clone(), *value))
5193                .collect(),
5194        )));
5195    }
5196    if let Some(weight) = style.font_weight {
5197        element = element.font_weight(FontWeight(f32::from(weight)));
5198    }
5199    if let Some(slant) = style.font_slant {
5200        element = match slant {
5201            FontSlant::Normal => element.not_italic(),
5202            FontSlant::Italic => element.italic(),
5203        };
5204    }
5205    if let Some(value) = style.line_height {
5206        element = line_height(element, value);
5207    }
5208    if let Some(align) = style.text_align {
5209        let align = match (align, direction) {
5210            (TextAlignMode::Start, TextDirection::LeftToRight)
5211            | (TextAlignMode::End, TextDirection::RightToLeft) => TextAlign::Left,
5212            (TextAlignMode::Start, TextDirection::RightToLeft)
5213            | (TextAlignMode::End, TextDirection::LeftToRight) => TextAlign::Right,
5214            (TextAlignMode::Center, _) => TextAlign::Center,
5215        };
5216        element = element.text_align(align);
5217    }
5218    if let Some(white_space) = style.white_space {
5219        element = match white_space {
5220            WhiteSpaceMode::Normal => element.whitespace_normal(),
5221            WhiteSpaceMode::NoWrap => element.whitespace_nowrap(),
5222        };
5223    }
5224    if style.text_ellipsis == Some(true) {
5225        element = element.text_ellipsis();
5226    }
5227    if let Some(lines) = style.line_clamp {
5228        element = element.line_clamp(lines);
5229    }
5230    element
5231}
5232
5233fn apply_shadows(mut element: Div, style: &StyleProperties, colors: &impl ColorResolver) -> Div {
5234    if let Some(shadows) = &style.shadows {
5235        element = element.shadow(
5236            shadows
5237                .iter()
5238                .filter_map(|shadow| {
5239                    colors.resolve(&shadow.color).map(|color| BoxShadow {
5240                        color: rgba(color.as_rgba_hex()).into(),
5241                        offset: point(px(f64_to_f32(shadow.x)), px(f64_to_f32(shadow.y))),
5242                        blur_radius: px(f64_to_f32(shadow.blur)),
5243                        spread_radius: px(f64_to_f32(shadow.spread)),
5244                        inset: false,
5245                    })
5246                })
5247                .collect(),
5248        );
5249    }
5250    element
5251}
5252
5253macro_rules! definite_length_fn {
5254    ($name:ident, $method:ident) => {
5255        fn $name(element: Div, value: Length) -> Div {
5256            match value {
5257                Length::Pixels(value) => element.$method(px(to_f32(value))),
5258                Length::Rems(value) => element.$method(rems(to_f32(value))),
5259                Length::Relative(value) => element.$method(relative(to_f32(value))),
5260                Length::ThemeSpacing(_) | Length::ThemeRadius(_) => element,
5261            }
5262        }
5263    };
5264}
5265
5266definite_length_fn!(gap, gap);
5267definite_length_fn!(padding_top, pt);
5268definite_length_fn!(padding_right, pr);
5269definite_length_fn!(padding_bottom, pb);
5270definite_length_fn!(padding_left, pl);
5271
5272macro_rules! layout_length_fn {
5273    ($name:ident, $method:ident) => {
5274        fn $name(element: Div, value: LayoutLength) -> Div {
5275            match value {
5276                LayoutLength::Definite(Length::Pixels(value)) => element.$method(px(to_f32(value))),
5277                LayoutLength::Signed(SignedLength::Pixels(value)) => {
5278                    element.$method(px(f64_to_f32(value)))
5279                }
5280                LayoutLength::Definite(Length::Rems(value)) => element.$method(rems(to_f32(value))),
5281                LayoutLength::Signed(SignedLength::Rems(value)) => {
5282                    element.$method(rems(f64_to_f32(value)))
5283                }
5284                LayoutLength::Definite(Length::Relative(value)) => {
5285                    element.$method(relative(to_f32(value)))
5286                }
5287                LayoutLength::Signed(SignedLength::Relative(value)) => {
5288                    element.$method(relative(f64_to_f32(value)))
5289                }
5290                LayoutLength::Auto => element.$method(auto()),
5291                LayoutLength::Definite(Length::ThemeSpacing(_) | Length::ThemeRadius(_)) => element,
5292            }
5293        }
5294    };
5295}
5296
5297layout_length_fn!(width, w);
5298layout_length_fn!(height, h);
5299layout_length_fn!(min_width, min_w);
5300layout_length_fn!(max_width, max_w);
5301layout_length_fn!(min_height, min_h);
5302layout_length_fn!(max_height, max_h);
5303layout_length_fn!(margin_top, mt);
5304layout_length_fn!(margin_right, mr);
5305layout_length_fn!(margin_bottom, mb);
5306layout_length_fn!(margin_left, ml);
5307layout_length_fn!(inset_top, top);
5308layout_length_fn!(inset_right, right);
5309layout_length_fn!(inset_bottom, bottom);
5310layout_length_fn!(inset_left, left);
5311layout_length_fn!(flex_basis, flex_basis);
5312
5313macro_rules! absolute_length_fn {
5314    ($name:ident, $method:ident) => {
5315        fn $name(element: Div, value: Length) -> Div {
5316            match value {
5317                Length::Pixels(value) => element.$method(px(to_f32(value))),
5318                Length::Rems(value) => element.$method(rems(to_f32(value))),
5319                Length::Relative(_) | Length::ThemeSpacing(_) | Length::ThemeRadius(_) => element,
5320            }
5321        }
5322    };
5323}
5324
5325absolute_length_fn!(border_top, border_t);
5326absolute_length_fn!(border_right, border_r);
5327absolute_length_fn!(border_bottom, border_b);
5328absolute_length_fn!(border_left, border_l);
5329absolute_length_fn!(radius_top_left, rounded_tl);
5330absolute_length_fn!(radius_top_right, rounded_tr);
5331absolute_length_fn!(radius_bottom_right, rounded_br);
5332absolute_length_fn!(radius_bottom_left, rounded_bl);
5333
5334fn font_size(element: Div, value: Length) -> Div {
5335    match value {
5336        Length::Pixels(value) => element.text_size(px(to_f32(value))),
5337        Length::Rems(value) => element.text_size(rems(to_f32(value))),
5338        Length::Relative(_) | Length::ThemeSpacing(_) | Length::ThemeRadius(_) => element,
5339    }
5340}
5341
5342fn line_height(element: Div, value: Length) -> Div {
5343    match value {
5344        Length::Pixels(value) => element.line_height(px(to_f32(value))),
5345        Length::Rems(value) => element.line_height(rems(to_f32(value))),
5346        Length::Relative(_) | Length::ThemeSpacing(_) | Length::ThemeRadius(_) => element,
5347    }
5348}
5349
5350const fn gpui_cursor(cursor: CursorKind) -> CursorStyle {
5351    match cursor {
5352        CursorKind::Default => CursorStyle::Arrow,
5353        CursorKind::Pointer => CursorStyle::PointingHand,
5354        CursorKind::Text => CursorStyle::IBeam,
5355        CursorKind::Move => CursorStyle::ClosedHand,
5356        CursorKind::Crosshair => CursorStyle::Crosshair,
5357        CursorKind::NotAllowed => CursorStyle::OperationNotAllowed,
5358        CursorKind::ResizeHorizontal => CursorStyle::ResizeLeftRight,
5359        CursorKind::ResizeVertical => CursorStyle::ResizeUpDown,
5360    }
5361}
5362
5363#[allow(clippy::cast_possible_truncation)]
5364fn to_f32(value: f64) -> f32 {
5365    debug_assert!(value.is_finite() && value >= 0.0 && value <= f64::from(f32::MAX));
5366    value as f32
5367}
5368
5369/// Minimal GPUI view for a previously evaluated Rhai tree or a Host-built tree.
5370pub struct StaticUiView {
5371    tree: crate::RetainedUiTree,
5372    semantics: crate::CommittedSemanticFrame,
5373    primitives: PrimitiveRegistry,
5374}
5375
5376#[derive(Debug, thiserror::Error)]
5377pub enum StaticUiViewError {
5378    #[error(transparent)]
5379    Reconcile(#[from] crate::ReconcileError),
5380    #[error(transparent)]
5381    Accessibility(#[from] crate::AccessibilityError),
5382}
5383
5384impl StaticUiView {
5385    /// Create a retained view from one accepted root snapshot.
5386    ///
5387    /// # Errors
5388    ///
5389    /// Returns structural reconciliation errors such as duplicate sibling keys.
5390    pub fn new(root: UiNode) -> Result<Self, StaticUiViewError> {
5391        Self::with_primitives(root, PrimitiveRegistry::new())
5392    }
5393
5394    /// Create a retained view with a custom primitive registry.
5395    ///
5396    /// # Errors
5397    ///
5398    /// Returns structural reconciliation errors such as duplicate sibling keys.
5399    pub fn with_primitives(
5400        root: UiNode,
5401        primitives: PrimitiveRegistry,
5402    ) -> Result<Self, StaticUiViewError> {
5403        let mut tree = crate::RetainedUiTree::new();
5404        tree.reconcile(root)?;
5405        let semantics = crate::CommittedSemanticFrame::from_retained(&tree)?;
5406        Ok(Self {
5407            tree,
5408            semantics,
5409            primitives,
5410        })
5411    }
5412
5413    /// Reconcile and atomically accept a new Host-owned snapshot.
5414    ///
5415    /// # Errors
5416    ///
5417    /// Returns structural reconciliation errors without changing the live root.
5418    pub fn set_root(
5419        &mut self,
5420        root: UiNode,
5421        cx: &mut Context<Self>,
5422    ) -> Result<crate::ReconcileReport, StaticUiViewError> {
5423        let mut tree = self.tree.clone();
5424        let report = tree.reconcile(root)?;
5425        let semantics = crate::CommittedSemanticFrame::from_retained(&tree)?;
5426        self.tree = tree;
5427        self.semantics = semantics;
5428        cx.notify();
5429        Ok(report)
5430    }
5431
5432    #[must_use]
5433    pub fn root(&self) -> Option<&UiNode> {
5434        self.tree.root()
5435    }
5436
5437    #[must_use]
5438    pub const fn retained(&self) -> &crate::RetainedUiTree {
5439        &self.tree
5440    }
5441}
5442
5443impl Render for StaticUiView {
5444    fn render(&mut self, window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
5445        if let Err(error) = self.primitives.retain_tree(&self.tree) {
5446            return div()
5447                .child(format!("Custom primitive lifecycle error: {error}"))
5448                .into_any_element();
5449        }
5450        self.root().map_or_else(
5451            || {
5452                div()
5453                    .child("Static UI view has no accepted root")
5454                    .into_any_element()
5455            },
5456            |_| {
5457                GpuiNodeRenderer::render_retained_with_committed_semantics(
5458                    &self.tree,
5459                    &self.semantics,
5460                    window.is_a11y_active(),
5461                    &LiteralColorResolver,
5462                    &InteractionState::default(),
5463                    &self.primitives,
5464                )
5465            },
5466        )
5467    }
5468}
5469
5470#[cfg(test)]
5471mod tests {
5472    use super::*;
5473    use crate::{ColorValue, Length, Rgba8, Style};
5474
5475    struct TypographyResolver;
5476
5477    impl ColorResolver for TypographyResolver {
5478        fn resolve(&self, color: &ColorValue) -> Option<Rgba8> {
5479            LiteralColorResolver.resolve(color)
5480        }
5481
5482        fn resolve_typography(&self, role: &str) -> Option<crate::ResolvedTypography> {
5483            (role == "body").then(|| crate::ResolvedTypography {
5484                family: Some("JetBrains Mono".to_owned()),
5485                fallbacks: vec!["PingFang SC".to_owned()],
5486                size: Length::Pixels(12.0),
5487                line_height: Length::Pixels(16.0),
5488                weight: 400,
5489            })
5490        }
5491    }
5492
5493    #[test]
5494    fn owned_color_snapshot_matches_the_native_primitive_theme_surface() {
5495        let engine = crate::RuntimeEngine::new();
5496        let mut theme = crate::load_theme_source(
5497            engine.engine(),
5498            "default_light.rhai",
5499            include_str!("../../../registry/themes/default_light.rhai"),
5500        )
5501        .unwrap();
5502        let custom = Rgba8::from_rgba_hex(0x55aa_ccff);
5503        theme.tokens.namespaces.insert(
5504            "brand".to_owned(),
5505            BTreeMap::from([("tint".to_owned(), crate::ThemeTokenValue::Color(custom))]),
5506        );
5507        let snapshot = OwnedColorResolver::capture(&theme);
5508        assert_eq!(
5509            snapshot.resolve(&ColorValue::Token("selection".to_owned())),
5510            theme.tokens.color("selection")
5511        );
5512        assert_eq!(
5513            snapshot.resolve(&ColorValue::Token("table.selection".to_owned())),
5514            theme.tokens.color("table.selection")
5515        );
5516        assert_eq!(
5517            snapshot.resolve_length(Length::ThemeSpacing(SpacingToken::Xxs)),
5518            theme.tokens.spacing.get("xxs").copied()
5519        );
5520        assert_eq!(
5521            snapshot.resolve(&ColorValue::Token("brand.tint".to_owned())),
5522            Some(custom)
5523        );
5524    }
5525
5526    #[test]
5527    fn declarative_nodes_and_typed_styles_convert_without_a_gpui_context() {
5528        let root = UiNode::column(vec![
5529            UiNode::text("one"),
5530            UiNode::text("two"),
5531            UiNode::svg("<svg viewBox='0 0 1 1'><path fill='currentColor' d='M0 0L1 1'/></svg>")
5532                .unwrap()
5533                .with_style(
5534                    &Style::new().text_color(ColorValue::Literal(Rgba8::from_rgb_hex(0x00ff_ffff))),
5535                ),
5536        ])
5537        .with_style(
5538            &Style::new()
5539                .gap(Length::pixels(8.0).unwrap())
5540                .background(ColorValue::Literal(Rgba8::from_rgb_hex(0x0022_2222))),
5541        );
5542        let _element = GpuiNodeRenderer::render(&root);
5543    }
5544
5545    #[test]
5546    fn styled_inline_svg_fills_its_node_box_without_losing_intrinsic_default() {
5547        let bytes = b"<svg width='24' height='24' viewBox='0 0 24 24'></svg>".to_vec();
5548        let image = crate::asset::svg_image(&bytes, None).unwrap();
5549        let mut sized = inline_svg_image(Arc::clone(&image), true);
5550        assert_eq!(sized.style().size.width, Some(relative(1.0).into()));
5551        assert_eq!(sized.style().size.height, Some(relative(1.0).into()));
5552
5553        let mut intrinsic = inline_svg_image(image, false);
5554        assert_eq!(intrinsic.style().size.width, None);
5555        assert_eq!(intrinsic.style().size.height, None);
5556    }
5557
5558    #[test]
5559    fn ambient_text_color_inherits_and_a_local_value_wins() {
5560        let inherited = Rgba8::from_rgb_hex(0x0012_34ab);
5561        assert_eq!(
5562            resolve_ambient_text_color(&Style::new().base, &LiteralColorResolver, Some(inherited)),
5563            Some(inherited)
5564        );
5565
5566        let local = Rgba8::from_rgb_hex(0x00ab_cd12);
5567        assert_eq!(
5568            resolve_ambient_text_color(
5569                &Style::new().text_color(ColorValue::Literal(local)).base,
5570                &LiteralColorResolver,
5571                Some(inherited)
5572            ),
5573            Some(local)
5574        );
5575    }
5576
5577    #[test]
5578    fn asset_svg_uses_an_ancestor_text_color() {
5579        let assets = AssetRegistry::new();
5580        assets
5581            .register(
5582                "app",
5583                crate::InMemoryAssetProvider::new(BTreeMap::from([(
5584                    "icon".to_owned(),
5585                    crate::AssetData {
5586                        mime_type: "image/svg+xml".to_owned(),
5587                        bytes: br#"<svg xmlns="http://www.w3.org/2000/svg" width="1" height="1"><rect width="1" height="1" fill="currentColor"/></svg>"#.to_vec(),
5588                    },
5589                )])),
5590            )
5591            .unwrap();
5592        let handle = assets
5593            .load_image(&crate::AssetId::parse("app/icon").unwrap())
5594            .unwrap();
5595        let color = Rgba8::from_rgb_hex(0x0012_34ab);
5596        let root = UiNode::row(vec![UiNode::image(handle.opaque().clone())])
5597            .with_style(&Style::new().text_color(ColorValue::Literal(color)));
5598        let dispatcher = NodeEventDispatcher::new(|_, _, _, _, _| EventPropagation::Handled);
5599
5600        let _element = GpuiNodeRenderer::render_with_runtime(
5601            &root,
5602            &LiteralColorResolver,
5603            &InteractionState::default(),
5604            &PrimitiveRegistry::new(),
5605            &assets,
5606            &dispatcher,
5607        );
5608
5609        assert!(matches!(
5610            assets
5611                .image_source_tinted(handle.opaque(), Some(color))
5612                .unwrap(),
5613            gpui::ImageSource::Custom(_)
5614        ));
5615    }
5616
5617    #[test]
5618    fn symbolic_typography_resolves_and_explicit_fields_win() {
5619        let mut style = Style::new()
5620            .typography("body")
5621            .unwrap()
5622            .font_weight(650)
5623            .unwrap()
5624            .base;
5625        resolve_typography_role(&mut style, &TypographyResolver);
5626        assert_eq!(style.font_family.as_deref(), Some("JetBrains Mono"));
5627        assert_eq!(
5628            style.font_fallbacks.as_deref(),
5629            Some(["PingFang SC".to_owned()].as_slice())
5630        );
5631        assert_eq!(style.font_size, Some(Length::Pixels(12.0)));
5632        assert_eq!(style.line_height, Some(Length::Pixels(16.0)));
5633        assert_eq!(style.font_weight, Some(650));
5634    }
5635
5636    #[test]
5637    fn pseudo_paint_preserves_native_border_text_and_opacity_states() {
5638        let hover = Style::new()
5639            .background(ColorValue::Literal(Rgba8::from_rgb_hex(0x0011_2233)))
5640            .border_color(ColorValue::Literal(Rgba8::from_rgb_hex(0x0044_5566)))
5641            .text_color(ColorValue::Literal(Rgba8::from_rgb_hex(0x0077_8899)))
5642            .opacity(0.625)
5643            .unwrap();
5644        let paint = pseudo_paint(Some(&hover.base), &LiteralColorResolver);
5645        assert_eq!(paint.background, Some(Rgba8::from_rgb_hex(0x0011_2233)));
5646        assert_eq!(paint.border, Some(Rgba8::from_rgb_hex(0x0044_5566)));
5647        assert_eq!(paint.text, Some(Rgba8::from_rgb_hex(0x0077_8899)));
5648        assert_eq!(paint.opacity, Some(0.625));
5649    }
5650
5651    #[test]
5652    fn aligned_text_nodes_become_flex_content_boxes() {
5653        let node = UiNode::text("Centered").with_style(
5654            &Style::new()
5655                .items_center()
5656                .justify_center()
5657                .height(Length::pixels(32.0).unwrap()),
5658        );
5659        let mut resolved = node.style().resolve(&InteractionState::default());
5660
5661        normalize_text_content_layout(&node, &mut resolved);
5662
5663        assert_eq!(resolved.display, Some(DisplayMode::Flex));
5664        assert_eq!(resolved.align, Some(Align::Center));
5665        assert_eq!(resolved.justify, Some(Justify::Center));
5666    }
5667
5668    #[test]
5669    fn sampled_motion_values_override_dimensions_and_transform_without_rhai() {
5670        let values = BTreeMap::from([
5671            (
5672                MotionKey::for_node("root/card", MotionProperty::Width),
5673                180.0,
5674            ),
5675            (
5676                MotionKey::for_node("root/card", MotionProperty::ClipHeight),
5677                64.0,
5678            ),
5679            (
5680                MotionKey::for_node("root/card", MotionProperty::TranslateX),
5681                12.0,
5682            ),
5683        ]);
5684        let sampled = node_motion(&values, "root/card");
5685        let mut style = StyleProperties::default();
5686        apply_motion_dimensions(&mut style, sampled);
5687        assert_eq!(style.width, Some(Length::Pixels(180.0).into()));
5688        assert_eq!(style.height, Some(Length::Pixels(64.0).into()));
5689        assert_eq!(sampled.translate_x, Some(12.0));
5690    }
5691
5692    #[test]
5693    fn native_signal_values_override_approved_properties_without_rhai() {
5694        let component = crate::ComponentInstancePath::root("Meter", "primary");
5695        let id =
5696            crate::SignalId::new(component.clone(), "width", crate::SignalKind::Float).unwrap();
5697        let signal = crate::NativeSignal::new(id.clone());
5698        let node = UiNode::text("meter")
5699            .with_signal_binding(crate::SignalProperty::Width, signal.clone())
5700            .unwrap();
5701        let mut registry = crate::SignalRegistry::new();
5702        registry.reconcile(
5703            &component,
5704            BTreeMap::from([(
5705                id,
5706                crate::signal::SignalDescriptor::new(crate::SignalValue::Float(80.0)),
5707            )]),
5708        );
5709        registry
5710            .write(&signal, crate::SignalValue::Float(144.0))
5711            .unwrap();
5712        let values = node_signals(&registry, &node);
5713        let mut style = StyleProperties::default();
5714        apply_signal_style(&mut style, &values);
5715        assert_eq!(style.width, Some(Length::Pixels(144.0).into()));
5716    }
5717
5718    #[test]
5719    fn canvas_signal_transform_is_shared_by_paint_and_hit_testing() {
5720        let component = crate::ComponentInstancePath::root("PanZoom", "viewport");
5721        let scale_ids = (
5722            crate::SignalId::new(component.clone(), "scale-x", crate::SignalKind::Float).unwrap(),
5723            crate::SignalId::new(component.clone(), "scale-y", crate::SignalKind::Float).unwrap(),
5724        );
5725        let rotate_id =
5726            crate::SignalId::new(component.clone(), "rotate", crate::SignalKind::Float).unwrap();
5727        let scale_x = crate::NativeSignal::new(scale_ids.0.clone());
5728        let scale_y = crate::NativeSignal::new(scale_ids.1.clone());
5729        let rotate = crate::NativeSignal::new(rotate_id.clone());
5730        let scene = crate::CanvasScene::new(vec![crate::CanvasCommand::Rect {
5731            key: "target".to_owned(),
5732            x: 20.0,
5733            y: 25.0,
5734            width: 20.0,
5735            height: 20.0,
5736            fill: ColorValue::Token("accent".to_owned()),
5737        }])
5738        .unwrap();
5739        let node = UiNode::canvas(scene.clone())
5740            .with_signal_binding(crate::SignalProperty::ScaleX, scale_x)
5741            .unwrap()
5742            .with_signal_binding(crate::SignalProperty::ScaleY, scale_y)
5743            .unwrap()
5744            .with_signal_binding(crate::SignalProperty::Rotate, rotate)
5745            .unwrap();
5746        let mut registry = crate::SignalRegistry::new();
5747        registry.reconcile(
5748            &component,
5749            BTreeMap::from([
5750                (
5751                    scale_ids.0,
5752                    crate::signal::SignalDescriptor::new(crate::SignalValue::Float(1.6)),
5753                ),
5754                (
5755                    scale_ids.1,
5756                    crate::signal::SignalDescriptor::new(crate::SignalValue::Float(0.8)),
5757                ),
5758                (
5759                    rotate_id,
5760                    crate::signal::SignalDescriptor::new(crate::SignalValue::Float(19.0)),
5761                ),
5762            ]),
5763        );
5764        let motion = apply_canvas_signal_transform(
5765            NodeMotionValues::default(),
5766            &node_signals(&registry, &node),
5767        );
5768        let mut tree = RetainedUiTree::new();
5769        tree.reconcile(node).unwrap();
5770        let retained = tree.root_id().unwrap();
5771        let geometry = crate::GeometryRegistry::new();
5772        let bounds = crate::GeometryBounds::new(100.0, 50.0, 120.0, 90.0).unwrap();
5773        geometry.update(
5774            retained,
5775            crate::ElementGeometry {
5776                layout: bounds,
5777                visual: bounds,
5778                clip: None,
5779            },
5780        );
5781        geometry.update_canvas_transform(retained, motion.canvas_transform());
5782        let paint_bounds = Bounds::new(point(px(100.0), px(50.0)), gpui::size(px(120.0), px(90.0)));
5783        let painted = node_canvas_point(paint_bounds, 30.0, 35.0, motion);
5784        let context = PointerPayloadContext::retained(retained, geometry, Some(scene), Vec::new());
5785        let payload = context.enrich(pointer_payload(
5786            painted,
5787            Some(MouseButton::Left),
5788            vec![MouseButton::Left],
5789            Modifiers::default(),
5790            1,
5791            false,
5792        ));
5793        let UiValue::Map(payload) = payload else {
5794            unreachable!()
5795        };
5796        assert_eq!(payload["canvas_key"], UiValue::String("target".to_owned()));
5797    }
5798
5799    #[test]
5800    fn optional_width_override_activates_fixed_layout_only_when_present() {
5801        let mut base = StyleProperties {
5802            flex_basis: Some(Length::Relative(0.0).into()),
5803            flex_grow_weight: Some(2.0),
5804            ..StyleProperties::default()
5805        };
5806        apply_signal_style(&mut base, &NodeSignalValues::default());
5807        assert_eq!(base.flex_grow_weight, Some(2.0));
5808        let mut overridden = base;
5809        apply_signal_style(
5810            &mut overridden,
5811            &NodeSignalValues {
5812                width_override: Some(180.0),
5813                ..NodeSignalValues::default()
5814            },
5815        );
5816        assert_eq!(overridden.width, Some(Length::Pixels(180.0).into()));
5817        assert_eq!(overridden.flex_basis, None);
5818        assert_eq!(overridden.flex_grow, Some(false));
5819        assert_eq!(overridden.flex_grow_weight, None);
5820        assert_eq!(overridden.flex_shrink, Some(false));
5821    }
5822
5823    #[test]
5824    fn property_specific_layout_values_reach_gpui_without_loss() {
5825        let mut automatic = width(div(), LayoutLength::Auto);
5826        assert_eq!(automatic.style().size.width, Some(auto()));
5827
5828        let mut signed = margin_left(div(), LayoutLength::Signed(SignedLength::Pixels(-12.0)));
5829        assert_eq!(
5830            signed.style().margin.left,
5831            Some(gpui::Length::from(px(-12.0)))
5832        );
5833
5834        let mut weighted = apply_layout_dimensions(
5835            div(),
5836            &StyleProperties {
5837                flex_grow_weight: Some(2.5),
5838                flex_basis: Some(Length::Relative(0.0).into()),
5839                ..StyleProperties::default()
5840            },
5841        );
5842        assert_eq!(weighted.style().flex_grow, Some(2.5));
5843        assert_eq!(weighted.style().flex_basis, Some(relative(0.0).into()));
5844    }
5845
5846    #[test]
5847    fn committed_semantics_write_native_role_label_state_and_range() {
5848        let source = UiNode::text("ignored")
5849            .with_attribute("role", UiValue::String("slider".to_owned()))
5850            .with_attribute("label", UiValue::String("Volume".to_owned()))
5851            .with_attribute("value", UiValue::Float(25.0))
5852            .with_attribute("value_min", UiValue::Float(0.0))
5853            .with_attribute("value_max", UiValue::Float(100.0))
5854            .with_attribute("orientation", UiValue::String("horizontal".to_owned()));
5855        let mut retained = RetainedUiTree::new();
5856        retained.reconcile(source).unwrap();
5857        let frame = crate::CommittedSemanticFrame::from_retained(&retained).unwrap();
5858        let semantic = frame.node(retained.root_id().unwrap()).unwrap();
5859        let element = apply_native_semantics(div().id("volume"), Some(semantic));
5860        assert_eq!(element.a11y_role(), Some(gpui::Role::Slider));
5861        let mut node = gpui::accesskit::Node::new(gpui::Role::Slider);
5862        element.write_a11y_info(&mut node);
5863        assert_eq!(node.label(), Some("Volume"));
5864        assert_eq!(node.value(), Some("25"));
5865        assert_eq!(node.numeric_value(), Some(25.0));
5866        assert_eq!(node.min_numeric_value(), Some(0.0));
5867        assert_eq!(node.max_numeric_value(), Some(100.0));
5868        assert_eq!(node.orientation(), Some(gpui::Orientation::Horizontal));
5869    }
5870
5871    #[test]
5872    fn plain_text_semantics_use_a_valued_native_label() {
5873        let mut retained = RetainedUiTree::new();
5874        retained.reconcile(UiNode::text("Ready")).unwrap();
5875        let frame = crate::CommittedSemanticFrame::from_retained(&retained).unwrap();
5876        let semantic = frame.node(retained.root_id().unwrap()).unwrap();
5877        let element = apply_native_semantics(div().id("status-text"), Some(semantic));
5878
5879        assert_eq!(semantic.role, "text");
5880        assert_eq!(element.a11y_role(), Some(gpui::Role::Label));
5881        let mut node = gpui::accesskit::Node::new(gpui::Role::Label);
5882        element.write_a11y_info(&mut node);
5883        assert_eq!(node.label(), None);
5884        assert_eq!(node.value(), Some("Ready"));
5885        assert!(node.character_lengths().is_empty());
5886    }
5887
5888    #[test]
5889    fn logical_spacing_resolves_to_physical_edges_in_both_directions() {
5890        let start = Length::Pixels(12.0);
5891        let end = Length::Pixels(4.0);
5892        assert_eq!(
5893            logical_horizontal_edges(
5894                None,
5895                None,
5896                Some(start),
5897                Some(end),
5898                TextDirection::LeftToRight,
5899            ),
5900            (Some(start), Some(end))
5901        );
5902        assert_eq!(
5903            logical_horizontal_edges(
5904                None,
5905                None,
5906                Some(start),
5907                Some(end),
5908                TextDirection::RightToLeft,
5909            ),
5910            (Some(end), Some(start))
5911        );
5912    }
5913
5914    #[test]
5915    fn rtl_keyboard_navigation_maps_physical_arrows_to_logical_handlers() {
5916        assert_eq!(
5917            logical_keyboard_key("left", TextDirection::RightToLeft),
5918            "right"
5919        );
5920        assert_eq!(
5921            logical_keyboard_key("right", TextDirection::RightToLeft),
5922            "left"
5923        );
5924        assert_eq!(
5925            logical_keyboard_key("left", TextDirection::LeftToRight),
5926            "left"
5927        );
5928        assert_eq!(
5929            logical_keyboard_key("down", TextDirection::RightToLeft),
5930            "down"
5931        );
5932    }
5933
5934    #[test]
5935    fn logical_overlay_edges_resolve_against_text_direction() {
5936        let spec = |placement| crate::OverlayNodeSpec {
5937            id: crate::OverlayId::new("logical-sheet"),
5938            parent: None,
5939            kind: crate::OverlayKind::Sheet,
5940            placement,
5941            anchor: None,
5942            open: true,
5943            gap: 0.0,
5944            modal: true,
5945            dismiss: crate::OverlayDismissPolicy {
5946                escape: true,
5947                outside: true,
5948            },
5949            tooltip_delays: None,
5950            initial_focus: crate::OverlayInitialFocus::Panel,
5951            activate_on_trigger: false,
5952            width_policy: crate::OverlayWidthPolicy::Content,
5953        };
5954        assert_eq!(
5955            scoped_overlay_spec(
5956                &spec(crate::OverlayPlacement::Start),
5957                "view",
5958                TextDirection::RightToLeft,
5959            )
5960            .placement,
5961            crate::OverlayPlacement::Right
5962        );
5963        assert_eq!(
5964            scoped_overlay_spec(
5965                &spec(crate::OverlayPlacement::End),
5966                "view",
5967                TextDirection::RightToLeft,
5968            )
5969            .placement,
5970            crate::OverlayPlacement::Left
5971        );
5972    }
5973
5974    #[test]
5975    fn retained_tab_order_reads_explicit_group_indices() {
5976        let node = UiNode::text("tab")
5977            .with_attribute("tab_index", UiValue::Integer(7))
5978            .with_attribute("tab_stop", UiValue::Bool(false))
5979            .with_attribute("tab_group", UiValue::Bool(true));
5980        assert_eq!(node_tab_index(&node), 7);
5981        assert!(!node_tab_stop(&node));
5982        assert!(node_has_focus_declaration(&node));
5983        let _element = GpuiNodeRenderer::render(&node);
5984    }
5985
5986    #[test]
5987    fn pointer_payload_uses_committed_local_geometry_and_canvas_hit_key() {
5988        let scene = crate::CanvasScene::new(vec![crate::CanvasCommand::Rect {
5989            key: "clip".to_owned(),
5990            x: 0.0,
5991            y: 0.0,
5992            width: 40.0,
5993            height: 30.0,
5994            fill: ColorValue::Token("accent".to_owned()),
5995        }])
5996        .unwrap();
5997        let mut tree = crate::RetainedUiTree::new();
5998        tree.reconcile(UiNode::canvas(scene.clone()).with_key("canvas"))
5999            .unwrap();
6000        let node = tree.root_id().unwrap();
6001        let geometry = crate::GeometryRegistry::new();
6002        geometry.update(
6003            node,
6004            crate::ElementGeometry {
6005                layout: crate::GeometryBounds::new(100.0, 50.0, 200.0, 120.0).unwrap(),
6006                visual: crate::GeometryBounds::new(105.0, 54.0, 200.0, 120.0).unwrap(),
6007                clip: None,
6008            },
6009        );
6010        let outer_scroll = ScrollHandle::new();
6011        outer_scroll.set_offset(point(px(-10.0), px(-20.0)));
6012        let inner_scroll = ScrollHandle::new();
6013        inner_scroll.set_offset(point(px(-3.0), px(-4.0)));
6014        let context = PointerPayloadContext::retained(
6015            node,
6016            geometry,
6017            Some(scene),
6018            vec![outer_scroll, inner_scroll],
6019        );
6020        let payload = context.enrich(pointer_payload(
6021            point(px(112.0), px(68.0)),
6022            Some(MouseButton::Left),
6023            vec![MouseButton::Left],
6024            Modifiers::default(),
6025            1,
6026            false,
6027        ));
6028        let UiValue::Map(payload) = payload else {
6029            unreachable!()
6030        };
6031        assert_eq!(payload["canvas_key"], UiValue::String("clip".to_owned()));
6032        assert_eq!(
6033            payload["target"],
6034            crate::GeometryBounds::new(105.0, 54.0, 200.0, 120.0)
6035                .unwrap()
6036                .into_value()
6037        );
6038        assert!(value_point(&payload["local"]).is_some_and(|(x, y)| {
6039            (x - 7.0).abs() < f64::EPSILON && (y - 14.0).abs() < f64::EPSILON
6040        }));
6041        assert!(value_point(&payload["content"]).is_some_and(|(x, y)| {
6042            (x - 20.0).abs() < f64::EPSILON && (y - 38.0).abs() < f64::EPSILON
6043        }));
6044
6045        let scroll_style = Style::new().overflow_scroll();
6046        let nested_root = UiNode::box_node(vec![
6047            UiNode::box_node(vec![UiNode::text("target").with_key("target")])
6048                .with_key("inner")
6049                .with_style(&scroll_style),
6050        ])
6051        .with_key("outer")
6052        .with_style(&scroll_style);
6053        let mut nested = RetainedUiTree::new();
6054        nested.reconcile(nested_root).unwrap();
6055        let ids = nested
6056            .nodes()
6057            .filter_map(|node| node.key().map(|key| (key.to_owned(), node.id())))
6058            .collect::<BTreeMap<_, _>>();
6059        let handles = BTreeMap::from([
6060            (ids["outer"], ScrollHandle::new()),
6061            (ids["inner"], ScrollHandle::new()),
6062        ]);
6063        assert_eq!(
6064            scroll_handles_for_node(Some(&nested), Some(ids["target"]), &handles).len(),
6065            2
6066        );
6067    }
6068
6069    #[test]
6070    fn canvas_motion_uses_one_affine_transform_for_paint_and_hit_testing() {
6071        let scene = crate::CanvasScene::new(vec![crate::CanvasCommand::Rect {
6072            key: "target".to_owned(),
6073            x: 20.0,
6074            y: 25.0,
6075            width: 20.0,
6076            height: 20.0,
6077            fill: ColorValue::Token("accent".to_owned()),
6078        }])
6079        .unwrap();
6080        let mut tree = crate::RetainedUiTree::new();
6081        tree.reconcile(UiNode::canvas(scene.clone()).with_key("canvas"))
6082            .unwrap();
6083        let node = tree.root_id().unwrap();
6084        let geometry = crate::GeometryRegistry::new();
6085        let bounds = crate::GeometryBounds::new(100.0, 50.0, 120.0, 90.0).unwrap();
6086        geometry.update(
6087            node,
6088            crate::ElementGeometry {
6089                layout: bounds,
6090                visual: bounds,
6091                clip: None,
6092            },
6093        );
6094        let transform = crate::geometry::CanvasMotionTransform {
6095            rotate: 31.0,
6096            scale_x: 1.4,
6097            scale_y: 0.7,
6098            skew_x: 12.0,
6099            skew_y: -8.0,
6100            path_progress: None,
6101        };
6102        geometry.update_canvas_transform(node, transform);
6103        let motion = NodeMotionValues {
6104            rotate: Some(transform.rotate),
6105            scale_x: Some(transform.scale_x),
6106            scale_y: Some(transform.scale_y),
6107            skew_x: Some(transform.skew_x),
6108            skew_y: Some(transform.skew_y),
6109            ..NodeMotionValues::default()
6110        };
6111        let paint_bounds = Bounds::new(point(px(100.0), px(50.0)), gpui::size(px(120.0), px(90.0)));
6112        let painted = node_canvas_point(paint_bounds, 30.0, 35.0, motion);
6113        let context = PointerPayloadContext::retained(node, geometry, Some(scene), Vec::new());
6114        let payload = context.enrich(pointer_payload(
6115            painted,
6116            Some(MouseButton::Left),
6117            vec![MouseButton::Left],
6118            Modifiers::default(),
6119            1,
6120            false,
6121        ));
6122        let UiValue::Map(payload) = payload else {
6123            unreachable!()
6124        };
6125        assert_eq!(payload["canvas_key"], UiValue::String("target".to_owned()));
6126    }
6127}