Skip to main content

orinium_browser/engine/ui/
custom_node.rs

1//! [`CustomNode`] trait for replaced elements that delegate rendering.
2
3use ui_layout::Style;
4
5use crate::engine::layouter::types::{TextFlowStyle, TextStyle};
6use crate::engine::renderer_model::{DrawCommand, Rect};
7
8use super::input_text_types::InputTextEvent;
9
10/// Platform-neutral pointer event delivered to a custom node.
11///
12/// Coordinates are relative to the node's content box (see the
13/// [`CustomNode`] coordinate system). The engine translates global
14/// coordinates before dispatching.
15#[derive(Debug, Clone, Copy, PartialEq)]
16pub enum PointerEvent {
17    /// Pointer moved to a new position inside the node.
18    Move { x: f32, y: f32 },
19    /// A pointer button was pressed inside the node.
20    Down { x: f32, y: f32 },
21    /// A pointer button was released inside the node.
22    Up { x: f32, y: f32 },
23    /// The pointer left the node's bounds.
24    Leave,
25}
26
27/// An open popup (top-layer overlay) owned by a custom node.
28///
29/// Both `rect` and `commands` are expressed in the node's content-box
30/// coordinate system; the engine positions them above the page content and
31/// routes pointer input to the node's [`on_popup_pointer_event`](CustomNode::on_popup_pointer_event)
32/// while the popup is open.
33#[derive(Debug, Clone)]
34pub struct Popup {
35    /// Used to hit-test the open popup and to decide whether a click lands
36    /// outside it (dismissal).
37    pub rect: Rect,
38    /// Draw commands rendered above all page content while the popup is open.
39    pub commands: Vec<DrawCommand>,
40}
41
42/// A size expressed in the content-box coordinate system.
43///
44/// Used by the [`CustomNode`] trait so callers can distinguish content-box
45/// dimensions from border-box ones at the type level.
46#[derive(Debug, Clone, Copy, PartialEq)]
47pub struct ContentSize {
48    /// Content-box width in pixels.
49    pub width: f32,
50    /// Content-box height in pixels.
51    pub height: f32,
52}
53
54impl ContentSize {
55    /// A zero-sized content box.
56    pub fn zero() -> Self {
57        Self {
58            width: 0.0,
59            height: 0.0,
60        }
61    }
62}
63
64/// Trait for custom/replaced elements that produce their own draw commands.
65///
66/// # Coordinate System
67/// Commands must be emitted in the content-box coordinate space:
68/// `(0, 0)` = top-left of the content box. The parent's transform/clip
69/// stack handles positioning.
70///
71/// # Lifecycle
72/// - `draw_sized()` is called every frame during `generate_draw_commands`.
73/// - Event handling (focus, IME) is dispatched through `engine::input`.
74pub trait CustomNode: std::fmt::Debug + Send + Sync + 'static {
75    /// Emit draw commands fitted to the resolved content-box `size`.
76    ///
77    /// `text_style` carries the inherited CSS text properties (color,
78    /// font-size, font-weight, etc.) resolved for this element.
79    /// `style` carries the resolved `ui_layout::Style` (CSS width/height,
80    /// box-sizing, etc.) and `size` is the resolved content-box size.
81    ///
82    /// This is the primary drawing entry point.
83    fn draw_sized(
84        &self,
85        cmd_buf: &mut Vec<DrawCommand>,
86        text_style: &TextStyle,
87        text_flow_style: &TextFlowStyle,
88        style: &Style,
89        size: ContentSize,
90    );
91
92    /// Emit draw commands using the intrinsic content-box size.
93    ///
94    /// Defaults to [`draw_sized`](Self::draw_sized) with the intrinsic size
95    /// and a default style. Components that only draw at their intrinsic size
96    /// may override this instead.
97    fn draw(
98        &self,
99        cmd_buf: &mut Vec<DrawCommand>,
100        text_style: &TextStyle,
101        text_flow_style: &TextFlowStyle,
102    ) {
103        self.draw_sized(
104            cmd_buf,
105            text_style,
106            text_flow_style,
107            &Style::default(),
108            self.intrinsic_size(),
109        );
110    }
111
112    /// Returns the node's open popup (top-layer overlay), if any.
113    ///
114    /// The popup is re-generated every frame; returning `None` closes it.
115    /// Commands use the same content-box coordinate space as
116    /// [`draw_sized`](Self::draw_sized).
117    fn popup(&self, _text_style: &TextStyle, _text_flow_style: &TextFlowStyle) -> Option<Popup> {
118        None
119    }
120
121    /// Intrinsic (content-box) size in pixels.
122    ///
123    /// The layout engine uses this to size the element when no explicit
124    /// width/height is set via CSS.
125    fn intrinsic_size(&self) -> ContentSize;
126
127    /// Whether one resolved dimension should scale the other dimension using
128    /// the node's intrinsic aspect ratio.
129    fn preserves_intrinsic_aspect_ratio(&self) -> bool {
130        false
131    }
132
133    /// Whether this node can receive keyboard and IME text input.
134    fn accepts_text_input(&self) -> bool {
135        false
136    }
137
138    /// Updates keyboard focus for this node.
139    fn set_focused(&self, _focused: bool) {}
140
141    /// Returns whether this node currently owns keyboard focus.
142    fn is_focused(&self) -> bool {
143        false
144    }
145
146    /// Applies a platform-neutral text editing event.
147    fn handle_text_input(&self, _event: InputTextEvent) -> bool {
148        false
149    }
150
151    /// Returns whether an IME preedit string is active.
152    fn is_composing(&self) -> bool {
153        false
154    }
155
156    /// Dispatches a pointer event on the node's open popup.
157    ///
158    /// Coordinates are relative to the popup's top-left (the `popup.rect`
159    /// origin in content-box coordinates). The engine only dispatches while a
160    /// popup is open; nodes without a popup can ignore this.
161    fn on_popup_pointer_event(&self, _event: PointerEvent) -> bool {
162        false
163    }
164
165    /// Closes this node's popup, if open (dismiss on an outside click).
166    fn dismiss_popup(&self) {}
167
168    /// Dispatches a platform-neutral pointer event.
169    ///
170    /// Coordinates are relative to the content box. Returns `true` when the
171    /// node consumed the event.
172    fn on_pointer_event(&self, _event: PointerEvent) -> bool {
173        false
174    }
175
176    /// Updates the hover state for this node.
177    fn set_hovered(&self, _hovered: bool) {}
178
179    /// Returns whether this node is currently hovered.
180    fn is_hovered(&self) -> bool {
181        false
182    }
183
184    /// Whether this node changed its visual state since the last check.
185    ///
186    /// Consumes the flag: calling it again without an intervening state
187    /// change returns `false`. The engine uses this to skip full redraws
188    /// when no custom node is dirty.
189    fn needs_repaint(&self) -> bool {
190        false
191    }
192
193    /// Screen rectangle of the active IME composition underline, in content-box
194    /// coordinates `(x, y, width, height)`. `None` when nothing is composing.
195    fn composition_rect(&self) -> Option<(f32, f32, f32, f32)> {
196        None
197    }
198
199    /// Accessibility role for this node (e.g. `"button"`, `"textbox"`, `"img"`).
200    fn role(&self) -> Option<&'static str> {
201        None
202    }
203
204    /// Accessibility label (accessible name).
205    fn label(&self) -> Option<String> {
206        None
207    }
208
209    /// Current value for editable/stateful nodes.
210    fn value(&self) -> Option<String> {
211        None
212    }
213
214    /// Whether this node is disabled and must not receive input.
215    fn is_disabled(&self) -> bool {
216        false
217    }
218}