GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/widget/mod.rs (30.4K)
1 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2 pub enum ElementState {
3 Pressed,
4 Released,
5 }
6
7 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8 pub enum MouseButton {
9 Left,
10 Right,
11 Middle,
12 Back,
13 Forward,
14 Other(u16),
15 }
16
17 #[derive(Debug, Clone, Copy, PartialEq)]
18 pub struct Position {
19 pub x: f64,
20 pub y: f64,
21 }
22
23 #[derive(Debug, Clone, Copy, PartialEq)]
24 pub enum MouseScrollDelta {
25 LineDelta(f32, f32),
26 PixelDelta(Position),
27 }
28
29 impl MouseScrollDelta {
30 /// Vertical scroll in wheel-notch equivalents for VALUE widgets (sliders,
31 /// float3 rows). The pixel divisor is calibrated against a measured
32 /// trackpad stream, not a notch convention: a real two-finger swipe
33 /// delivers 10–20 axis units per event at 6–8ms intervals (~2000
34 /// units/sec sustained). At 60 units per notch-equivalent (0.02 of the
35 /// range each), that sustains ~0.6 range/sec — a full sweep is a couple
36 /// of committed swipes, while slow fine-tuning events (2–5 units) move
37 /// well under one readout tick. 15 (the DE's hardware-notch unit) slams
38 /// bound-to-bound in ~150ms; 120 (the wheel standard) needs ~6000px of
39 /// finger travel per sweep.
40 pub fn notches_y(&self) -> f32 {
41 match self {
42 MouseScrollDelta::LineDelta(_x, y) => *y,
43 MouseScrollDelta::PixelDelta(pos) => (pos.y as f32) / 60.0,
44 }
45 }
46 }
47
48 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
49 pub enum Key {
50 Named(NamedKey),
51 Character(String),
52 }
53
54 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
55 pub enum NamedKey {
56 Backspace,
57 Tab,
58 Enter,
59 Escape,
60 Space,
61 ArrowDown,
62 ArrowLeft,
63 ArrowRight,
64 ArrowUp,
65 End,
66 Home,
67 PageDown,
68 PageUp,
69 Delete,
70 Control,
71 Shift,
72 Alt,
73 Super,
74 F5,
75 }
76
77 #[derive(Debug, Clone, PartialEq, Eq)]
78 pub struct KeyEvent {
79 pub state: ElementState,
80 pub logical_key: Key,
81 pub text: Option<String>,
82 pub repeat: bool,
83 pub ctrl: bool,
84 pub shift: bool,
85 pub alt: bool,
86 }
87
88 /// Text justification for widget labels/content (shared by Button, cce-files' row
89 /// list, and settings; formerly defined by the dissolved json_layout host).
90 #[derive(serde::Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
91 #[serde(rename_all = "lowercase")]
92 pub enum Justification {
93 Left,
94 Center,
95 Right,
96 }
97
98 /// A context-menu action dispatched on the menu's target widget (6bd phase 1: one enum
99 /// replaces the 13 per-action `WidgetHost` methods). `ClearText` is the search-box "Cear" item.
100 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
101 pub enum ContextAction {
102 Cut,
103 Copy,
104 Paste,
105 SelectAll,
106 /// Step the widget's own edit history (a text box's typing). Routed by
107 /// the runner to the focused widget on the `undo` / `redo` chords before
108 /// the app's `Application::undo` / `redo` get their turn; also reachable
109 /// as "Undo" / "Redo" context-menu rows.
110 Undo,
111 Redo,
112 ClearText,
113 CopyKey,
114 CopyValue,
115 CopyPath,
116 DeleteKey,
117 ExpandNode,
118 CollapseNode,
119 ExpandAll,
120 CollapseAll,
121 /// Ramp: hide/show the bottom control strip, the graph claiming the space.
122 ToggleRampControls,
123 }
124
125 use crate::colors;
126 use std::sync::atomic::AtomicUsize;
127 use std::collections::HashMap;
128
129 pub const DROPDOWN_ITEM_H: f32 = 22.0;
130
131 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
132 pub struct WidgetId(pub usize);
133
134 pub static NEXT_WIDGET_ID: AtomicUsize = AtomicUsize::new(1);
135
136 #[derive(Debug, Clone)]
137 pub struct LayoutTree {
138 pub parents: HashMap<WidgetId, WidgetId>,
139 pub children: HashMap<WidgetId, Vec<WidgetId>>,
140 }
141
142 pub use crate::scene::paint::{ControlPlate, PlateStance};
143 pub use crate::widget::model::FocusRole;
144 pub use crate::context::UiContext;
145
146 #[derive(Debug, Clone, PartialEq)]
147 pub enum Event {
148 PointerMove { x: f32, y: f32, local_x: f32, local_y: f32 },
149 MouseButton { button: MouseButton, state: ElementState, x: f32, y: f32, local_x: f32, local_y: f32 },
150 MouseWheel { delta: MouseScrollDelta, x: f32, y: f32, local_x: f32, local_y: f32 },
151 KeyInput(KeyEvent),
152 Tick(f32),
153
154 MouseEnter,
155 MouseLeave,
156 DragStart { start_x: f32, start_y: f32 },
157 DragUpdate { dx: f32, dy: f32, x: f32, y: f32, local_x: f32, local_y: f32 },
158 DragEnd,
159 FocusIn,
160 FocusOut,
161 }
162
163 #[derive(Debug, Clone, Copy, PartialEq)]
164 pub struct Point {
165 pub x: f32,
166 pub y: f32,
167 }
168
169 #[derive(Debug, Clone, Copy, PartialEq)]
170 pub struct LayoutConstraints {
171 pub min_width: f32,
172 pub max_width: f32,
173 pub min_height: f32,
174 pub max_height: f32,
175 }
176
177 impl LayoutConstraints {
178 pub fn new(min_w: f32, max_w: f32, min_h: f32, max_h: f32) -> Self {
179 Self { min_width: min_w, max_width: max_w, min_height: min_h, max_height: max_h }
180 }
181
182 pub fn loose(max_w: f32, max_h: f32) -> Self {
183 Self { min_width: 0.0, max_width: max_w, min_height: 0.0, max_height: max_h }
184 }
185 }
186
187 #[derive(Debug, Clone, Copy, PartialEq)]
188 pub struct Size {
189 pub width: f32,
190 pub height: f32,
191 }
192
193 /// The single host surface every widget presents to the machinery (context routing, the
194 /// paint walk, the render loop, app dyn broadcasts). **Formerly `Element`**, the ~125-method
195 /// god-trait — renamed at the 6bd flip once census-driven shrink batches brought it down to
196 /// the measured blueprint. `Adapted<W>` is the one production implementor; concrete behavior
197 /// lives on the narrow `Layout`/`Paint`/`Input` traits it wraps. The direct-dispatch and
198 /// value blocks shrink further as apps move to routed events / concrete slots.
199 pub trait WidgetHost {
200 /// The widget's shared base state — GUARANTEED (the flip): the `Option` escape hatch
201 /// and its `WidgetId(0)` sentinel class are gone. `Adapted` (the one production
202 /// implementor) always owns a base; test shims carry one via `impl_widget_base!`.
203 fn base(&self) -> &Widget;
204 fn base_mut(&mut self) -> &mut Widget;
205 /// The widget's natural CONTENT height — the control below its detached label, if
206 /// any. What a layout strategy allots; [`WidgetHost::layout`] places that content
207 /// box at the origin it is given and hangs the label ([`WidgetHost::label_strip`])
208 /// above it. `None` when the widget has no natural height.
209 fn preferred_height(&self) -> Option<f32> { None }
210
211 /// The height of the detached-label strip above this widget's content: zero for
212 /// unlabeled widgets and for those whose base label IS their content
213 /// ([`Layout::inline_label`]). A widget's rect is always its content plus this
214 /// strip — `set_rect` takes that block, `layout` lands the content at the origin
215 /// and hangs the strip above it. A strategy reserves that
216 /// row above every child's content (`container_layout::label_lead`) and puts
217 /// `layout::CONTROL_GAP` between the blocks.
218 fn label_strip(&self) -> f32 { self.base().label_offset() }
219
220 /// Where the detached label is drawn: the strip above the content, as wide as the
221 /// label's text. `None` for an unlabeled widget and for an inline label. The label
222 /// may be wider than the widget's rect (a StatusDot's, a Checkbox's) — the rect is
223 /// the content's width, and the text runs past it — so anything wrapping a widget
224 /// as a block (a `Group`'s hull) unions this with the rect.
225 fn detached_label_rect(&self) -> Option<crate::scene::layout::Rect> { None }
226
227 fn mark_dirty(&mut self, ctx: &mut UiContext) {
228 let b = self.base_mut();
229 if b.dirty {
230 return;
231 }
232 b.dirty = true;
233 if let Some(id) = b.id.get() {
234 if let Some(parent_ptr) = ctx.tree.parent_ptr(id) {
235 unsafe {
236 (*parent_ptr).mark_dirty(ctx);
237 }
238 }
239 }
240 }
241
242 // Required (the flip): the old defaults manufactured DummyAny stand-ins nothing
243 // could legitimately use. `impl_widget_base!` provides both. `as_ptr`/`as_ptr_mut`
244 // are GONE from the trait (the plumbing retype): a pointer to a widget you already
245 // hold is a plain cast (`w as *mut (dyn WidgetHost + 'static)`); concrete
246 // registration sites ride the inherent `Adapted<W>` methods (the registration
247 // bridge — derived from a live borrow, never stored beyond the registry).
248 fn as_any(&self) -> &dyn std::any::Any;
249 fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
250
251 fn handle_event(&mut self, event: &Event, ctx: &mut UiContext) -> bool {
252 // The default serves test shims only (Adapted overrides this): base hover
253 // bookkeeping on moves, tick forwarding, everything else inert — the old
254 // per-method dispatch died with the direct-dispatch entry points (6bd collapse).
255 match event {
256 Event::PointerMove { x, y, .. } => {
257 let (px, py) = (*x, *y);
258 ctx.set_cursor_pos(px, py);
259 let was = self.base().hovered;
260 let is_hit = self.hit_test(px, py, ctx);
261 self.base_mut().hovered = is_hit;
262 was != is_hit
263 }
264 Event::Tick(dt) => {
265 self.tick(*dt, ctx)
266 }
267 _ => false,
268 }
269 }
270
271 fn measure(&self, constraints: LayoutConstraints, _ctx: &UiContext) -> Size {
272 let (_, _, w, h) = self.rect();
273 let pref_h = self.preferred_height().unwrap_or(h);
274
275 let width = w.clamp(constraints.min_width, constraints.max_width);
276 let height = pref_h.clamp(constraints.min_height, constraints.max_height);
277
278 Size { width, height }
279 }
280
281 /// Land the CONTENT box at `origin`, the label strip hanging above it — the one
282 /// placement contract (`Adapted` repeats it over its measured content size).
283 fn layout(&mut self, origin: Point, constraints: LayoutConstraints, ctx: &mut UiContext) {
284 let size = self.measure(constraints, ctx);
285 let strip = self.label_strip();
286 self.set_rect(origin.x, origin.y - strip, size.width, size.height + strip);
287 }
288
289 fn rect(&self) -> (f32, f32, f32, f32) {
290 let b = self.base();
291 (b.x, b.y, b.w, b.h)
292 }
293
294 fn label(&self) -> Option<String> {
295 self.base().label.clone()
296 }
297
298 // The value/polling block (`get_value_string`/`set_value_string`/`take_change`/
299 // `take_click`/`value`/`set_text`/`set_selected`) is GONE from the trait (6bd value
300 // shrink): apps drain widget state through the concrete inherent `Adapted<W>` methods
301 // (which forward to the narrow `Input` hooks). The last dyn readers went concrete-slot
302 // (TI's roster drain, cloud's JsonControl, designer's pane-focus sync).
303
304 /// Dispatch a context-menu action on this widget. Returns whether it was applied.
305 /// Default inert; the adapter forwards to `Input::context_action` (whose default gives
306 /// every widget whole-value Cut/Copy/Paste through the value-string pair).
307 fn context_action(&mut self, _action: ContextAction) -> bool {
308 false
309 }
310
311 fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
312 let b = self.base_mut();
313 b.x = x;
314 b.y = y;
315 b.w = w;
316 b.h = h;
317 }
318
319 fn set_row_rect(&mut self, x: f32, w: f32) {
320 let b = self.base_mut();
321 b.row_x = x;
322 b.row_w = w;
323 }
324
325 fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
326 if ctx.is_coordinate_covered(self.base().id(), px, py) {
327 return false;
328 }
329 let (x, y, w, h) = self.rect();
330 if w <= 0.0 || h <= 0.0 {
331 return false;
332 }
333 let b = self.base();
334 let (hx, hw) = if b.row_w > 0.0 { (b.row_x, b.row_w) } else { (x, w) };
335 px >= hx && px <= hx + hw && py >= y && py <= y + h
336 }
337
338 // The direct-dispatch entry points (`cursor_moved`, `on_cursor_moved`, `mouse_input`,
339 // `mouse_wheel`, `keyboard_input`, `drag_begin`/`drag_update`/`drag_end`) are GONE from
340 // the trait (6bd collapse): every event delivery goes through `handle_event` — the entry
341 // points live on as inherent `Adapted<W>` methods for concrete in-crate forwards.
342
343 // `hovered`/`set_hovered` are GONE from the trait (6bd batch 2): the state is the base
344 // `Widget::hovered` flag, read/written directly by the defaults above; Button/Checkbox
345 // keep inherent accessors for immediate-mode hosts.
346
347 fn highlight_quad(&self, ctx: &UiContext) -> Option<(f32, f32, f32, f32, [f32; 4])> {
348 // Focus/hover highlight color, folded from the zero-override `highlight_color` (6bd).
349 let is_focused = ctx.is_focused_id(self.base().id());
350 let hc = if is_focused {
351 colors::highlight_primary_color()
352 } else if self.base().hovered {
353 colors::HIGHLIGHT_SECONDARY
354 } else {
355 return None;
356 };
357 let b = self.base();
358 let hx = if b.row_w > 0.0 { b.row_x } else { b.x };
359 let hw = if b.row_w > 0.0 { b.row_w } else { b.w };
360 Some((hx, b.y, hw, b.h, hc))
361 }
362
363 fn color(&self) -> [f32; 4];
364 fn solid_border(&self) -> Option<([f32; 4], f32)> { None }
365 fn plate_bevel(&self) -> Option<f32> { None }
366
367 // `draggable`/`is_dragging` are GONE from the trait (the ControlPanel endgame
368 // removed their last stored-child-pointer consumer): the drag queries are concrete
369 // inherent `Adapted<W>` reads; index-driven rosters (TI, designer) route them
370 // through per-slot matches like the other value drains.
371
372 fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> { Vec::new() }
373 fn extra_arcs(&self) -> Vec<(f32, f32, f32, f32, f32, f32, [f32; 4])> { Vec::new() }
374 fn extra_circles(&self) -> Vec<(f32, f32, f32, [f32; 4])> { Vec::new() }
375
376 fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
377 let mut quads = self.extra_quads();
378 if let Some(hq) = self.highlight_quad(ctx) {
379 if hq.4 != colors::HIGHLIGHT_SECONDARY {
380 quads.push(hq);
381 }
382 }
383 quads
384 }
385
386 /// Emit this widget's OWN primitives (non-recursive) into the single paint pass (Phase 3).
387 /// The default composes the pieces the legacy recursive `all_*` emit for one node: rounded
388 /// background, plain/decoration quads, circles, and own text. Widgets with richer painting
389 /// (borders, relief primitives, arcs, vectors, SVGs) can override. Recursion into children and clipping
390 /// are handled by the paint walk (`scene::painter`), not here.
391 fn paint_self(&self, ui: &UiContext, ctx: &mut crate::scene::paint::PaintCtx) {
392 use crate::scene::layout::Rect;
393 let (x, y, w, h) = self.rect();
394 let rect = Rect { x, y, width: w, height: h };
395 let color = self.color();
396
397 if ui.tree.children_ptrs(self.base().id()).is_empty() {
398 // Leaf: emit its own rounded quads directly. For an ordinary widget this is just the
399 // rounded background; for widgets that override `all_rounded_quads` with custom
400 // geometry (e.g. Graph's nodes and edges) it captures that too. No recursion happens
401 // because there are no children.
402 for (qx, qy, qw, qh, r, c, corners) in self.all_rounded_quads(ui) {
403 ctx.rounded_rect(Rect { x: qx, y: qy, width: qw, height: qh }, r, corners, c);
404 }
405 } else {
406 // Container: reconstruct its own plate (bevel / border / rounded background) — mirrors
407 // `push_widget_vertices`. Its children are drawn by the paint walk, so we must NOT call
408 // `all_rounded_quads` here (that would recurse and double-draw them).
409 let cr = self.corner_radii();
410 let radii = (cr.top_left, cr.top_right, cr.bottom_right, cr.bottom_left);
411 if let Some(depth) = self.plate_bevel() {
412 ctx.bevel(rect, radii, &crate::scene::material::Material::from_fill(color), depth);
413 } else if let Some((border_color, thickness)) = self.solid_border() {
414 ctx.border(rect, radii, color, border_color, thickness);
415 } else if color[3].abs() > 0.001 {
416 let (radius, (r1, r2, r3, r4)) = self.corner_style();
417 if r1 || r2 || r3 || r4 {
418 ctx.rounded_rect(rect, radius, (r1, r2, r3, r4), color);
419 }
420 }
421 }
422
423 for (qx, qy, qw, qh, c) in self.all_quads(ui) {
424 ctx.quad(Rect { x: qx, y: qy, width: qw, height: qh }, c);
425 }
426 for (cx, cy, r, t, start, end, c) in self.extra_arcs() {
427 ctx.arc(cx, cy, r, t, start, end, c);
428 }
429 for (cx, cy, r, c) in self.extra_circles() {
430 ctx.circle(cx, cy, r, c);
431 }
432 // Text: NONE by default. Every live legacy widget with own text carries a
433 // paint_self override (most via `scene::painter::paint_legacy_leaf` + its own
434 // labels); containers' text_labels aggregates are covered by the walk's descent
435 // (emitting them here would double-draw every descendant's text — the Phase 6d
436 // trap). Migrated widgets go through `Adapted::paint_self`, never this default.
437 }
438
439 /// Whether the paint walk should clip this widget's children to its rect (scroll/root plate
440 /// containers). Default: no clipping.
441 fn clips_children(&self) -> bool { false }
442
443 /// Whether this widget paints its ENTIRE subtree itself through its (recursive)
444 /// `all_rounded_quads` / `all_quads` — a legacy "subtree painter" such as `TreeList`, whose
445 /// row backgrounds and separators live in an `all_rounded_quads` override that also recurses
446 /// into its children. When true, the paint walk emits those directly and does NOT recurse
447 /// (the widget already did). Transitional: such widgets will eventually get a proper
448 /// non-recursive `paint_self`. Default: false.
449 fn renders_own_subtree(&self) -> bool { false }
450
451 fn all_rounded_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, f32, [f32; 4], (bool, bool, bool, bool))> {
452 if !self.visible() {
453 return Vec::new();
454 }
455 let mut quads = Vec::new();
456 let (radius, (r1, r2, r3, r4)) = self.corner_style();
457 if r1 || r2 || r3 || r4 {
458 let (x, y, w, h) = self.rect();
459 let c = self.color();
460 if c[3].abs() > 0.001 {
461 quads.push((x, y, w, h, radius, c, (r1, r2, r3, r4)));
462 }
463 }
464 for &child_ptr in &ctx.tree.children_ptrs(self.base().id()) {
465 let widget = unsafe { &*child_ptr };
466 quads.extend(widget.all_rounded_quads(ctx));
467 }
468 quads
469 }
470
471
472 // The per-widget text getters (text_labels / text_labels_with_bounds /
473 // text_labels_with_font_and_bounds / get_text_items) are GONE: every widget emits
474 // its own text as display-list prims via paint_self (Adapted::paint_self for
475 // migrated widgets; paint_legacy_leaf-based overrides for the legacy leaves). The
476 // deleted default's base-label synthesis lives on in Adapted's base-label fallback,
477 // and its scroll-ancestor clamp in scene::painter::scroll_ancestor_text_bounds.
478
479 fn widget_font(&self) -> Option<String> { None }
480 fn type_name(&self) -> &'static str {
481 let full_name = std::any::type_name::<Self>();
482 full_name.split("::").last().unwrap_or("Widget")
483 }
484 fn popover_rect(&self) -> Option<(f32, f32, f32, f32)> { None }
485 fn render_popover(&self, _pc: &mut dyn crate::layout::RenderTarget) {}
486
487 fn focus(&mut self) {
488 self.base_mut().focused = true;
489 }
490 fn unfocus(&mut self) {
491 self.base_mut().focused = false;
492 }
493 fn focused(&self, ctx: &UiContext) -> bool {
494 ctx.is_focused_id(self.base().id())
495 }
496 fn prepare_text(&mut self, _fs: &mut cosmic_text::FontSystem) {}
497
498 fn set_visible(&mut self, _visible: bool) {}
499 fn visible(&self) -> bool { true }
500 fn tick(&mut self, _dt: f32, _ctx: &mut UiContext) -> bool { false }
501 fn wants_tick(&self) -> bool { false }
502 fn is_child_visible(&self, _child_id: WidgetId) -> bool { true }
503 fn set_modifiers(&mut self, _ctrl: bool, _shift: bool, _alt: bool) {}
504
505 // `set_parent`/`add_child` are GONE from the trait (6bd batch 4): linking is a tree
506 // operation — concrete callers ride the inherent `Adapted` methods, dyn callers go
507 // through `focus::link_parent_child` or `ctx.tree` directly. `parent`/`children` are
508 // GONE too (the plumbing retype): tree structure is read off `ctx.tree`
509 // (`parent_id`/`parent_ptr`/`child_ids`/`children_ptrs`) — the trait no longer
510 // proxies it, and no trait method returns a raw pointer. Paginator's field-derived
511 // child (the one `Layout::container_children` implementor) reaches the walks through
512 // the tree link its per-tick `register_embedded_children` maintains.
513
514 fn z_index(&self) -> i32 { 0 }
515 fn is_scrollable(&self) -> bool { false }
516 fn blocks_root_plate_drag(&self) -> bool { true }
517
518 /// Uniform corner radius + per-corner on-flags, in one read (6bd batch 2 — replaced the
519 /// separate `corner_radius`/`rounded_corners` getters). The radius is meaningful even with
520 /// every corner off: Menu/StatusBar report their parent's radius to children this way, so
521 /// the flags-off channel can't be folded into `corner_radii`.
522 fn corner_style(&self) -> (f32, (bool, bool, bool, bool)) {
523 (12.0, (false, false, false, false))
524 }
525
526 /// This widget's part in keyboard navigation — `Input::focus_role` through
527 /// the adapter; `FocusRole::None` for anything that is not a plate or a well.
528 fn focus_role(&self) -> FocusRole {
529 FocusRole::None
530 }
531
532 fn corner_radii(&self) -> CornerRadii {
533 let (r, (tl, tr, br, bl)) = self.corner_style();
534 CornerRadii::new(
535 if tl { r } else { 0.0 },
536 if tr { r } else { 0.0 },
537 if br { r } else { 0.0 },
538 if bl { r } else { 0.0 },
539 )
540 }
541 }
542
543 // The `Control` subtrait (set_label + control_label) is DELETED (6bd value shrink):
544 // zero dyn consumers and zero `control_label()` callers remained; `set_label` lives on as
545 // the inherent `Adapted<W>` method every call site already resolved to (it shadowed the
546 // trait), and detached-label paint moved to the adapter in the Phase 5 leaf sweeps.
547
548 pub mod core;
549 pub mod input;
550 pub mod plate_dock;
551 pub mod container;
552 pub mod display;
553 pub mod editor;
554 pub mod layout_helper;
555 pub mod model;
556 pub mod scroll_region;
557 pub mod scroll_motion;
558
559 // Re-exports
560 pub use self::editor::TextEditorState;
561 pub use self::layout_helper::{ColumnLayout, RowLayout};
562 pub use self::scroll_region::{ScrollRegion, ScrollbarActivity};
563 pub use self::scroll_motion::{Bounds, ScrollAxis, ScrollMotion, ScrollPhase, ScrollSettings, LINE_PX};
564 pub use self::model::{Adapted, EventCtx, Input, Layout, Paint};
565 pub use self::core::{Widget, focus, hover_animation, clipboard, context_menu, clear_widget_references};
566 pub use self::core::focus::link_parent_child;
567 pub use self::input::{
568 Button, TextBox, Spinbox, Dropdown, Checkbox, Toggle, Slider, RangeSlider,
569 ColorSelector, Finger, Trackpad, get_font_db, ActiveThumb, FontSelector,
570 BevelPreview, bevel_ease, parse_bevel_knobs, RampPreview,
571 ButtonStrip, KeybindRecorder, Ramp, RampKey, ColorRamp, ColorRampKey,
572 format_ramp_spec, parse_ramp_spec
573 };
574 pub use self::container::{
575 Group, GroupFrame,
576 ContainerLayout, OverlayLayout, ManualLayout, VerticalLayout, GridLayout, AdaptiveGridLayout,
577 ColumnsLayout, MosaicLayout, ReverseMosaicLayout,
578 ContentBg, ParametersBg,
579 ScrollBox, MenuBar, Spreadsheet, Breadcrumb,
580 Paginator, TreeList, TreeElement
581 };
582 pub use self::display::{
583 TextLabel, Label, StyledLabel, LabelPrim, TextItem, UsageBar,
584 InfoBox, StatusDot, InteractiveListItem,
585 GraphNode, Graph, TaggedQuad, Float3, ProgressBar, StatusBar, Splitter, Node, Separator,
586 DotStatus, Panel, ImageView, serialize_widgets,
587 truncate_head, truncate_tail,
588 };
589
590 pub trait PageSelector {
591 fn selected_page(&self) -> usize;
592 fn set_selected_page(&mut self, page: usize);
593 fn sidebar_w(&self) -> f32;
594 }
595
596 pub trait MenuController {
597 fn menu_click(&mut self) -> Option<(usize, usize)>;
598 fn trigger_menu_click(&mut self, menu_idx: usize, item_idx: usize);
599 fn set_item_checked(&mut self, menu_idx: usize, item_idx: usize, checked: bool);
600 fn set_menu_items(&mut self, menu_idx: usize, items: &[String]);
601 fn is_menu_bar(&self) -> bool;
602 fn is_menu_open(&self) -> bool;
603 fn menu_items(&self) -> Vec<String>;
604 fn menu_item_checked(&self) -> Vec<Option<bool>>;
605 fn is_vertical(&self) -> bool;
606 fn menu_names(&self) -> Vec<String>;
607 fn menu_items_list(&self) -> Vec<Vec<String>>;
608 fn menu_checked_list(&self) -> Vec<Vec<Option<bool>>>;
609 fn take_context_change(&mut self) -> Option<usize>;
610 fn set_context_selected(&mut self, selected: usize);
611 fn set_center_items(&mut self, center: bool);
612 fn get_menu_items_at(&self, px: f32, py: f32) -> Option<(usize, String, Vec<String>, f32, f32, f32, f32)>;
613 }
614
615 pub trait GraphController {
616 fn set_nodes(&mut self, nodes: &[GraphNode]);
617 fn get_nodes(&self) -> Vec<GraphNode>;
618 fn selected_node(&self) -> Option<usize>;
619 fn set_selected_node(&mut self, idx: Option<usize>);
620 fn double_clicked_node(&self) -> Option<usize>;
621 fn clear_double_clicked_node(&mut self);
622 fn set_grid_snap_enabled(&mut self, enabled: bool);
623 fn take_node_geom_toggle(&mut self) -> Option<(usize, bool)>;
624 fn set_grid_snap(&mut self, gx: f32, gy: f32);
625 /// The grid's ONE size per axis: the pitch, from the centre of one grid
626 /// line to the centre of the next. Nodes are centred on the lattice
627 /// intersections. Leaves the node size alone.
628 fn set_grid_pitch(&mut self, px: f32, py: f32);
629 /// The node body's size, independent of the pitch — hosts scale it with
630 /// their zoom as they scale the pitch.
631 fn set_node_size(&mut self, w: f32, h: f32);
632 /// The older cell-and-gap description of the same lattice — a cell plus
633 /// its gap is a pitch, and the node body is the cell. Kept for hosts
634 /// that still speak it (cce-files, cce-graph); new code sets the pitch.
635 fn set_grid_sizes(&mut self, gx: f32, gy: f32);
636 /// The gap half of the cell-and-gap description; see [`set_grid_sizes`].
637 fn set_skipped_sizes(&mut self, row_h: f32, col_w: f32);
638 /// The lattice intersection node (0, 0) is centred on, window-absolute.
639 fn set_grid_origin(&mut self, ox: f32, oy: f32);
640 fn grid_origin(&self) -> (f32, f32);
641 fn set_show_network_grid(&mut self, show: bool);
642 fn take_pending_connection(&mut self) -> Option<(String, String)>;
643 /// A node dropped onto a wire, to be spliced in between its ends:
644 /// (dragged node id, the wire's upstream node NAME — what Input params
645 /// store, the wire's downstream node id). The host rewires both Input
646 /// params: dragged.Input = upstream name, downstream.Input = dragged's
647 /// name. Default None for hosts whose graphs have no wires to splice.
648 fn take_pending_splice(&mut self) -> Option<(String, String, String)> {
649 None
650 }
651 fn cancel_connecting(&mut self);
652 fn is_node_rect(&self, qx: f32, qy: f32, qw: f32, qh: f32) -> bool;
653 /// The topmost node whose body contains (px, py), window-absolute coords.
654 fn node_at(&self, px: f32, py: f32) -> Option<usize>;
655 /// The corner radius of anything node-shaped on the grid at the current
656 /// zoom — the cursor, the drop-target highlight (0 = square).
657 fn cell_corner_radius(&self) -> f32;
658 /// The flat-geometry emission with grid cells tagged by their surviving
659 /// rounded corners — for hosts that draw the graph's quads themselves
660 /// (the designer) and want cells as superellipse tiles.
661 fn geometry_quads_tagged(&self, rect: crate::scene::layout::Rect) -> Vec<TaggedQuad>;
662 /// The grid lines and origin axes, flat, over what is beneath — for
663 /// hosts that draw the graph's quads themselves, called at the point in
664 /// their walk where the grid goes (under the wires and nodes).
665 fn paint_grid(&self, rect: crate::scene::layout::Rect, pc: &mut crate::scene::paint::PaintCtx);
666 /// The pixel rect the in-flight node drag will deposit its body on
667 /// (`commit_drag`'s resolution), for hosts' drop-target highlight.
668 /// None outside a node drag.
669 fn drop_target_cell_rect(&self) -> Option<(f32, f32, f32, f32)>;
670 }
671
672 pub trait SpreadsheetController {
673 fn set_spreadsheet_data(&mut self, headers: Vec<String>, rows: Vec<Vec<String>>);
674 }
675
676 pub trait PathController {
677 fn set_path(&mut self, segments: &[String]);
678 fn path_click(&mut self) -> Option<usize>;
679 }
680
681 pub trait ParamController {
682 fn node_params(&self) -> Vec<(String, String, String)>;
683 fn set_display_params(&mut self, params: &[(String, String, String)]);
684 }
685
686 pub trait GeomController {
687 fn set_geom_visible(&mut self, visible: bool);
688 fn geom_visible(&self) -> bool;
689 fn take_geom_toggle(&mut self) -> bool;
690 }
691
692 #[derive(Debug, Clone, Copy, PartialEq)]
693 pub struct CornerRadii {
694 pub top_left: f32,
695 pub top_right: f32,
696 pub bottom_right: f32,
697 pub bottom_left: f32,
698 }
699
700 impl CornerRadii {
701 pub fn new(tl: f32, tr: f32, br: f32, bl: f32) -> Self {
702 Self { top_left: tl, top_right: tr, bottom_right: br, bottom_left: bl }
703 }
704
705 pub fn uniform(radius: f32) -> Self {
706 Self::new(radius, radius, radius, radius)
707 }
708 }
709
710 pub fn match_key_shortcut(event: &KeyEvent, shortcut_str: &str) -> bool {
711 let shortcut_lower = shortcut_str.to_lowercase();
712 let parts: Vec<&str> = shortcut_lower.split('+').collect();
713
714 let mut req_ctrl = false;
715 let mut req_shift = false;
716 let mut req_alt = false;
717 let mut req_key = "";
718
719 for part in parts {
720 match part {
721 "ctrl" | "control" => req_ctrl = true,
722 "shift" => req_shift = true,
723 "alt" | "meta" => req_alt = true,
724 // Super chords belong to the compositor; a client never sees them.
725 "super" | "win" | "logo" => {}
726 k => req_key = k,
727 }
728 }
729
730 if event.ctrl != req_ctrl { return false; }
731 if event.shift != req_shift { return false; }
732 if event.alt != req_alt { return false; }
733
734 if let Key::Character(ref ch) = event.logical_key {
735 let ch_lower = ch.to_lowercase();
736 if req_key.len() == 1 {
737 return ch_lower == req_key;
738 } else {
739 let mapped_key = match req_key {
740 "slash" => "/",
741 "enter" => "enter",
742 "escape" => "escape",
743 "space" => " ",
744 k => k,
745 };
746 return ch_lower == mapped_key;
747 }
748 } else if let Key::Named(nk) = event.logical_key {
749 let nk_str = format!("{:?}", nk).to_lowercase();
750 return nk_str == req_key;
751 }
752 false
753 }
754