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}