Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/mod.rs
1mod align;
2mod child_view;
3mod clipped;
4mod clipped_scrollable;
5mod constrained_box;
6mod container;
7#[cfg(debug_assertions)]
8mod debug;
9mod dismiss;
10mod drag;
11pub mod drag_resize;
12mod empty;
13mod event_handler;
14mod flex;
15mod formatted_text_element;
16mod hoverable;
17mod icon;
18mod image;
19mod list;
20mod min_size;
21pub mod new_scrollable;
22mod percentage;
23mod rect;
24pub mod resizable;
25mod scrollable;
26mod selectable_area;
27pub mod shared_scrollbar;
28pub mod shimmering_text;
29mod size_constraint_switch;
30mod stack;
31pub mod table;
32mod text;
33mod uniform_list;
34mod viewported_list;
35 
36pub use align::*;
37pub use child_view::*;
38pub use clipped::*;
39pub use clipped_scrollable::*;
40pub use constrained_box::*;
41pub use container::*;
42#[cfg(debug_assertions)]
43pub use debug::*;
44pub use dismiss::*;
45pub use drag::*;
46pub use drag_resize::*;
47pub use empty::*;
48pub use event_handler::*;
49pub use flex::*;
50pub use formatted_text_element::*;
51pub use hoverable::*;
52pub use icon::*;
53pub use image::*;
54pub use list::*;
55pub use min_size::*;
56pub use new_scrollable::NewScrollable;
57pub use percentage::*;
58pub use rect::*;
59pub use resizable::*;
60pub use scrollable::*;
61pub use selectable_area::*;
62pub use shared_scrollbar::*;
63pub use size_constraint_switch::*;
64pub use stack::*;
65pub use table::{
66 RowBackground, Table, TableColumnWidth, TableConfig, TableHeader, TableState, TableStateHandle,
67 TableVerticalSizing,
68};
69pub use text::*;
70pub use uniform_list::*;
71pub use viewported_list::*;
72 
73use crate::event::ModifiersState;
74use crate::platform::Cursor;
75use crate::{
76 event::DispatchedEvent,
77 text::{word_boundaries::WordBoundariesPolicy, IsRect, SelectionDirection, SelectionType},
78 Gradient,
79};
80pub use crate::{
81 scene::Dash, scene::ZIndex, AfterLayoutContext, AppContext, Event, EventContext, LayoutContext,
82 PaintContext, SizeConstraint,
83};
84use core::fmt;
85use pathfinder_color::ColorU;
86use pathfinder_geometry::{
87 rect::RectF,
88 vector::{vec2f, Vector2F},
89};
90use std::any::Any;
91use std::borrow::Cow;
92use std::ops::Range;
93use std::sync::MutexGuard;
94 
95/// The result of dispatching an event.
96/// This is (future) return type of `dispatch_event`.
97/// This will eventually replace the current boolean return type, to be more explicit about
98/// which events should continue to propagate to parent elements and which should stop.
99pub enum DispatchEventResult {
100 /// The event should continue to propagate to parent elements.
101 PropagateToParent,
102 /// The event should not propagate to parent elements.
103 StopPropagation,
104}
105 
106pub trait Element {
107 fn layout(
108 &mut self,
109 constraint: SizeConstraint,
110 ctx: &mut LayoutContext,
111 app: &AppContext,
112 ) -> Vector2F;
113 
114 fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &AppContext);
115 
116 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext);
117 
118 fn size(&self) -> Option<Vector2F>;
119 
120 fn origin(&self) -> Option<Point>;
121 
122 fn z_index(&self) -> Option<ZIndex> {
123 self.origin().map(|p| p.z_index())
124 }
125 
126 fn bounds(&self) -> Option<RectF> {
127 try_rect_with_z(self.origin(), self.size())
128 }
129 
130 fn parent_data(&self) -> Option<&dyn Any> {
131 None
132 }
133 
134 /// Should be implemented alongside the SelectableElement trait. If implemented, it
135 /// should return the element as a SelectableElement.
136 fn as_selectable_element(&self) -> Option<&dyn SelectableElement> {
137 None
138 }
139 
140 /// Handle an event from the OS (e.g. Mouse or Keyboard events)
141 ///
142 /// Note: For each OS event, this is called on the root Element of the Element tree. Each
143 /// Element is then itself responsible for calling `dispatch_event` on its children. The
144 /// expectations for how an event propagates through the Element tree are:
145 ///
146 /// 1. Each Element that handles an event in some meaningful way will first verify that the
147 /// event applies to them by doing any necessary hit testing.
148 /// 2. Each parent Element will unconditionally pass the event to its children by calling
149 /// `dispatch_event` on them, which allows the children to make their own determination
150 /// of whether or not the event applies.
151 /// 3. Elements should return true if they handled the event and don't want it to propagate
152 /// to parent elements, and false if they want it to propagate to parent elements.
153 fn dispatch_event(
154 &mut self,
155 event: &DispatchedEvent,
156 ctx: &mut EventContext,
157 app: &AppContext,
158 ) -> bool;
159 
160 fn finish(self) -> Box<dyn Element>
161 where
162 Self: 'static + Sized,
163 {
164 Box::new(self)
165 }
166 
167 #[cfg(debug_assertions)]
168 fn type_name(&self) -> &'static str {
169 std::any::type_name::<Self>()
170 }
171 
172 /// Returns the text content of this element, if it contains text.
173 /// This is primarily used for testing to verify rendered text content.
174 /// Container elements should aggregate text from their children.
175 #[cfg(any(test, feature = "test-util"))]
176 fn debug_text_content(&self) -> Option<String> {
177 None
178 }
179}
180 
181pub trait ParentElement: Extend<Box<dyn Element>> + Sized {
182 #[cfg_attr(debug_assertions, track_caller)]
183 fn add_children(&mut self, children: impl IntoIterator<Item = Box<dyn Element>>) {
184 self.extend(children);
185 }
186 
187 #[cfg_attr(debug_assertions, track_caller)]
188 fn add_child(&mut self, child: Box<dyn Element>) {
189 self.extend(Some(child))
190 }
191 
192 #[cfg_attr(debug_assertions, track_caller)]
193 fn with_children(mut self, children: impl IntoIterator<Item = Box<dyn Element>>) -> Self {
194 self.add_children(children);
195 self
196 }
197 
198 #[cfg_attr(debug_assertions, track_caller)]
199 fn with_child(self, child: Box<dyn Element>) -> Self {
200 self.with_children(Some(child))
201 }
202}
203 
204impl<T> ParentElement for T where T: Extend<Box<dyn Element>> {}
205 
206#[derive(Clone, Debug)]
207pub struct SelectionFragment {
208 pub text: String,
209 pub origin: Point,
210}
211 
212#[derive(Clone, Copy, Debug, PartialEq)]
213pub struct Point {
214 xy: Vector2F,
215 z_index: ZIndex,
216}
217 
218impl Point {
219 pub fn new(x: f32, y: f32, z_index: ZIndex) -> Self {
220 Self {
221 xy: vec2f(x, y),
222 z_index,
223 }
224 }
225 
226 pub fn from_vec2f(xy: Vector2F, z_index: ZIndex) -> Self {
227 Self { xy, z_index }
228 }
229 
230 pub fn x(&self) -> f32 {
231 self.xy.x()
232 }
233 
234 pub fn y(&self) -> f32 {
235 self.xy.y()
236 }
237 
238 pub fn xy(&self) -> Vector2F {
239 self.xy
240 }
241 
242 pub fn z_index(&self) -> ZIndex {
243 self.z_index
244 }
245}
246 
247#[derive(Clone, Copy, Debug, Eq, PartialEq)]
248pub enum Axis {
249 Horizontal,
250 Vertical,
251}
252 
253impl Axis {
254 pub fn invert(self) -> Self {
255 match self {
256 Self::Horizontal => Self::Vertical,
257 Self::Vertical => Self::Horizontal,
258 }
259 }
260 
261 pub fn to_point(self, pos_along_main_axis: f32, pos_along_inverse_axis: f32) -> Vector2F {
262 match self {
263 Self::Horizontal => vec2f(pos_along_main_axis, pos_along_inverse_axis),
264 Self::Vertical => vec2f(pos_along_inverse_axis, pos_along_main_axis),
265 }
266 }
267}
268 
269pub enum AxisOrientation {
270 Normal,
271 Reverse,
272}
273 
274#[derive(Clone, Copy, Debug, Default, PartialEq)]
275pub enum Fill {
276 #[default]
277 None,
278 Solid(ColorU),
279 Gradient {
280 start: Vector2F,
281 end: Vector2F,
282 start_color: ColorU,
283 end_color: ColorU,
284 },
285}
286 
287impl From<ColorU> for Fill {
288 fn from(color: ColorU) -> Self {
289 Fill::Solid(color)
290 }
291}
292 
293#[derive(Default, Debug, Clone, Copy, PartialEq)]
294pub struct Margin {
295 top: f32,
296 left: f32,
297 bottom: f32,
298 right: f32,
299}
300 
301impl Margin {
302 pub const fn uniform(margin: f32) -> Self {
303 Margin {
304 top: margin,
305 left: margin,
306 bottom: margin,
307 right: margin,
308 }
309 }
310 
311 pub const fn with_left(mut self, margin: f32) -> Self {
312 self.left = margin;
313 self
314 }
315 
316 pub const fn with_right(mut self, margin: f32) -> Self {
317 self.right = margin;
318 self
319 }
320 
321 pub const fn with_top(mut self, margin: f32) -> Self {
322 self.top = margin;
323 self
324 }
325 
326 pub const fn with_bottom(mut self, margin: f32) -> Self {
327 self.bottom = margin;
328 self
329 }
330 
331 pub fn top(&self) -> f32 {
332 self.top
333 }
334 
335 pub fn left(&self) -> f32 {
336 self.left
337 }
338 
339 pub fn bottom(&self) -> f32 {
340 self.bottom
341 }
342 
343 pub fn right(&self) -> f32 {
344 self.right
345 }
346}
347 
348#[derive(Default, Debug, Clone, Copy, PartialEq)]
349pub struct Padding {
350 top: f32,
351 left: f32,
352 bottom: f32,
353 right: f32,
354}
355 
356impl Padding {
357 pub const fn uniform(padding: f32) -> Self {
358 Self {
359 top: padding,
360 left: padding,
361 bottom: padding,
362 right: padding,
363 }
364 }
365 
366 pub const fn with_top(mut self, padding: f32) -> Self {
367 self.top = padding;
368 self
369 }
370 
371 pub const fn with_left(mut self, padding: f32) -> Self {
372 self.left = padding;
373 self
374 }
375 
376 pub const fn with_bottom(mut self, padding: f32) -> Self {
377 self.bottom = padding;
378 self
379 }
380 
381 pub const fn with_right(mut self, padding: f32) -> Self {
382 self.right = padding;
383 self
384 }
385 
386 pub fn with_vertical(mut self, vertical: f32) -> Self {
387 self.top = vertical;
388 self.bottom = vertical;
389 self
390 }
391 
392 pub fn with_horizontal(mut self, horizontal: f32) -> Self {
393 self.left = horizontal;
394 self.right = horizontal;
395 self
396 }
397 
398 pub fn top(&self) -> f32 {
399 self.top
400 }
401 
402 pub fn left(&self) -> f32 {
403 self.left
404 }
405 
406 pub fn bottom(&self) -> f32 {
407 self.bottom
408 }
409 
410 pub fn right(&self) -> f32 {
411 self.right
412 }
413}
414 
415#[derive(Default)]
416pub struct Overdraw {
417 top: f32,
418 left: f32,
419 bottom: f32,
420 right: f32,
421}
422 
423impl Border {
424 pub const fn new(width: f32) -> Self {
425 Self {
426 width,
427 color: Fill::None,
428 top: false,
429 left: false,
430 bottom: false,
431 right: false,
432 dash: None,
433 }
434 }
435 
436 pub fn all(width: f32) -> Self {
437 Self {
438 width,
439 color: Fill::None,
440 top: true,
441 left: true,
442 bottom: true,
443 right: true,
444 dash: None,
445 }
446 }
447 
448 pub fn top(width: f32) -> Self {
449 let mut border = Self::new(width);
450 border.top = true;
451 border
452 }
453 
454 pub fn left(width: f32) -> Self {
455 let mut border = Self::new(width);
456 border.left = true;
457 border
458 }
459 
460 pub fn bottom(width: f32) -> Self {
461 let mut border = Self::new(width);
462 border.bottom = true;
463 border
464 }
465 
466 pub fn right(width: f32) -> Self {
467 let mut border = Self::new(width);
468 border.right = true;
469 border
470 }
471 
472 pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
473 self.top = top;
474 self.left = left;
475 self.bottom = bottom;
476 self.right = right;
477 self
478 }
479 
480 pub fn with_border_fill<F>(mut self, fill: F) -> Self
481 where
482 F: Into<Fill>,
483 {
484 self.color = fill.into();
485 self
486 }
487 
488 pub fn with_border_color(mut self, color: ColorU) -> Self {
489 self.color = Fill::Solid(color);
490 self
491 }
492 
493 pub fn with_horizontal_border_gradient(mut self, gradient: Gradient) -> Self {
494 self.color = Fill::Gradient {
495 start: vec2f(0.0, 0.0),
496 end: vec2f(1.0, 0.0),
497 start_color: gradient.start,
498 end_color: gradient.end,
499 };
500 self
501 }
502 
503 pub fn with_border_gradient(
504 mut self,
505 start: Vector2F,
506 end: Vector2F,
507 gradient: Gradient,
508 ) -> Self {
509 self.color = Fill::Gradient {
510 start,
511 end,
512 start_color: gradient.start,
513 end_color: gradient.end,
514 };
515 self
516 }
517 
518 /// Note: only implemented for sharp corners. ***DO NOT*** use for elements with corner radius != 0, as this causes visual bugs.
519 pub fn with_dashed_border(mut self, dash: Dash) -> Self {
520 self.dash = Some(dash);
521 self
522 }
523}
524 
525impl From<ColorU> for Border {
526 fn from(value: ColorU) -> Self {
527 Border::all(1.).with_border_color(value)
528 }
529}
530 
531impl Fill {
532 pub fn start(&self) -> Vector2F {
533 match self {
534 Self::Gradient { start, .. } => *start,
535 _ => vec2f(0.0, 0.0),
536 }
537 }
538 
539 pub fn end(&self) -> Vector2F {
540 match self {
541 Self::Gradient { end, .. } => *end,
542 _ => vec2f(1.0, 0.0),
543 }
544 }
545 
546 pub fn start_color(&self) -> ColorU {
547 match self {
548 Self::Gradient { start_color, .. } => *start_color,
549 Self::Solid(color) => *color,
550 Self::None => ColorU::transparent_black(),
551 }
552 }
553 
554 pub fn end_color(&self) -> ColorU {
555 match self {
556 Self::Gradient { end_color, .. } => *end_color,
557 Self::Solid(color) => *color,
558 Self::None => ColorU::transparent_black(),
559 }
560 }
561}
562 
563/// Extends the `Vector2F` API to provider richer APIs for
564/// element-related computations.
565pub trait Vector2FExt {
566 /// Converts the 2D vector to a scalar according to the given `axis`.
567 fn along(self, axis: Axis) -> f32;
568 
569 /// Projects the 2D vector onto the given `axis`.
570 /// e.g. (5, 2) -> (5, 0), along the x-axis.
571 fn project_onto(self, axis: Axis) -> Vector2F;
572 
573 /// [`fmt::Display`] impl to format this `Vector2F` as a point.
574 fn display_point(self) -> Vector2FDisplayPoint;
575 
576 /// [`fmt::Display`] impl to format this `Vector2F` as a size.
577 fn display_size(self) -> Vector2FDisplaySize;
578}
579 
580impl Vector2FExt for Vector2F {
581 fn along(self, axis: Axis) -> f32 {
582 match axis {
583 Axis::Horizontal => self.x(),
584 Axis::Vertical => self.y(),
585 }
586 }
587 
588 fn project_onto(self, axis: Axis) -> Vector2F {
589 match axis {
590 Axis::Horizontal => vec2f(self.x(), 0.),
591 Axis::Vertical => vec2f(0., self.y()),
592 }
593 }
594 
595 fn display_point(self) -> Vector2FDisplayPoint {
596 Vector2FDisplayPoint(self)
597 }
598 
599 fn display_size(self) -> Vector2FDisplaySize {
600 Vector2FDisplaySize(self)
601 }
602}
603 
604pub struct Vector2FDisplaySize(Vector2F);
605 
606impl fmt::Display for Vector2FDisplaySize {
607 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
608 // We have to call .fmt directly so that formatting options are propagated.
609 self.0.x().fmt(f)?;
610 f.write_str("x")?;
611 self.0.y().fmt(f)
612 }
613}
614 
615pub struct Vector2FDisplayPoint(Vector2F);
616 
617impl fmt::Display for Vector2FDisplayPoint {
618 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
619 // We have to call .fmt directly so that formatting options are propagated.
620 f.write_str("(")?;
621 self.0.x().fmt(f)?;
622 f.write_str(", ")?;
623 self.0.y().fmt(f)?;
624 f.write_str(")")
625 }
626}
627 
628/// Extends the `f32` API to provider richer APIs for
629/// element-related computations.
630pub trait F32Ext {
631 /// Converts the `f32` to a 2D vector along the provided `axis`.
632 fn along(self, axis: Axis) -> Vector2F;
633}
634 
635impl F32Ext for f32 {
636 fn along(self, axis: Axis) -> Vector2F {
637 match axis {
638 Axis::Horizontal => vec2f(self, 0.),
639 Axis::Vertical => vec2f(0., self),
640 }
641 }
642}
643 
644/// Extends the `RectF` API to provider richer APIs for
645/// element-related computations.
646pub trait RectFExt {
647 /// Returns the minimum value along the given `axis`.
648 fn min_along(self, axis: Axis) -> f32;
649 
650 /// Returns the maximum value along the given `axis`.
651 fn max_along(self, axis: Axis) -> f32;
652}
653 
654impl RectFExt for RectF {
655 fn min_along(self, axis: Axis) -> f32 {
656 match axis {
657 Axis::Horizontal => self.min_x(),
658 Axis::Vertical => self.min_y(),
659 }
660 }
661 
662 fn max_along(self, axis: Axis) -> f32 {
663 match axis {
664 Axis::Horizontal => self.max_x(),
665 Axis::Vertical => self.max_y(),
666 }
667 }
668}
669 
670pub fn try_rect(origin: Option<Vector2F>, size: Option<Vector2F>) -> Option<RectF> {
671 origin.and_then(|origin| size.map(|size| RectF::new(origin, size)))
672}
673 
674pub fn try_rect_with_z(origin: Option<Point>, size: Option<Vector2F>) -> Option<RectF> {
675 origin.and_then(|origin| size.map(|size| RectF::new(origin.xy(), size)))
676}
677 
678/// The click handler provides the caller with the clicked text chunk index in
679/// the provided clickable char ranges and the string corresponds to that chunk,
680/// if one of the clickable chunks were clicked
681pub type ClickHandler = Box<dyn FnMut(&ModifiersState, &mut EventContext, &AppContext)>;
682/// The hover handler is called when the mouse either hovers or unhovers over a
683/// hoverable char range, with the first argument being is_hovering.
684pub type HoverHandler = Box<dyn FnMut(bool, &mut EventContext, &AppContext)>;
685 
686pub(crate) struct ClickableCharRange {
687 pub(crate) char_range: Range<usize>,
688 pub(crate) click_handler: ClickHandler,
689}
690 
691pub(crate) struct HoverableCharRange {
692 pub(crate) char_range: Range<usize>,
693 pub(crate) hover_handler: HoverHandler,
694 pub(crate) cursor_on_hover: Option<Cursor>,
695 pub(crate) mouse_state: MouseStateHandle,
696}
697 
698impl HoverableCharRange {
699 fn mouse_state(&self) -> MutexGuard<'_, MouseState> {
700 self.mouse_state
701 .lock()
702 .expect("The hoverable range should lock mouse state")
703 }
704}
705 
706/// SecretRange is used to store both the char range and byte range of a secret.
707/// We need to do this since several APIs e.g. hover/click APIs, use char ranges,
708/// whereas text-related APIs e.g. Regex and replace_range, use byte ranges.
709#[derive(Debug, Eq, PartialEq, Hash, Clone)]
710pub struct SecretRange {
711 pub char_range: Range<usize>,
712 pub byte_range: Range<usize>,
713}
714 
715impl SecretRange {
716 /// Extends the current range to include the provided range.
717 pub fn extend_range_end(&mut self, other: &SecretRange) {
718 self.char_range.end = self.char_range.end.max(other.char_range.end);
719 self.byte_range.end = self.byte_range.end.max(other.byte_range.end);
720 }
721}
722 
723pub trait PartialClickableElement {
724 /// clickable_char_ranges is the vector of char ranges that the caller can
725 /// specify, where the callback will be called if any character in one of
726 /// those char ranges was clicked
727 fn with_clickable_char_range<F>(
728 self,
729 _clickable_char_range: Range<usize>,
730 _callback: F,
731 ) -> Self
732 where
733 F: 'static + FnMut(&ModifiersState, &mut EventContext, &AppContext);
734 
735 /// Registers a callback that is called when a character in the given hoverable_char_range
736 /// is hovered or unhovered.
737 fn with_hoverable_char_range<F>(
738 self,
739 hoverable_char_range: Range<usize>,
740 mouse_state: MouseStateHandle,
741 cursor_on_hover: Option<Cursor>,
742 callback: F,
743 ) -> Self
744 where
745 F: 'static + FnMut(bool, &mut EventContext, &AppContext);
746 
747 /// Replace in the given range of the text with the replacement text.
748 fn replace_text_range(&mut self, range: SecretRange, replacement: Cow<'static, str>);
749}
750 
751/// An element that can be selected, for use with the SelectableArea element.
752/// It is expected that an element implementing this trait (i.e. Text)
753/// also implements as_selectable_element().
754pub trait SelectableElement {
755 /// Return the element's selected fragments.
756 fn get_selection(
757 &self,
758 _selection_start: Vector2F,
759 _selection_end: Vector2F,
760 _is_rect: IsRect,
761 ) -> Option<Vec<SelectionFragment>>;
762 
763 /// Semantically expands the absolute selection point based on the unit.
764 /// Does nothing if the unit is Char because there is no need to expand.
765 /// Expands to the start of the unit if expand_to_start is true, otherwise
766 /// expands to the end of the unit.
767 /// If the absolute point before the element's bounds and expand_to_start is true,
768 /// should expand to the start of the element. Similarly, if the absolute point is after
769 /// the element's bounds and expand_to_start is false, should expand to the end of the element.
770 /// Otherwise, should return None.
771 fn expand_selection(
772 &self,
773 _absolute_point: Vector2F,
774 _direction: SelectionDirection,
775 _unit: SelectionType,
776 _word_boundaries_policy: &WordBoundariesPolicy,
777 ) -> Option<Vector2F>;
778 
779 /// Returns None if neither point is in the element.
780 fn is_point_semantically_before(
781 &self,
782 _absolute_point: Vector2F,
783 _absolute_point_other: Vector2F,
784 ) -> Option<bool>;
785 
786 /// Runs smart selection on a point.
787 /// Should return None if the point is outside the element vertically,
788 /// but should snap to the nearest line if out of bounds horizontally.
789 fn smart_select(
790 &self,
791 _absolute_point: Vector2F,
792 _smart_select_fn: SmartSelectFn,
793 ) -> Option<(Vector2F, Vector2F)>;
794 
795 /// The union of the returned regions defines the area within which a mouse click is considered
796 /// to be a click performed on the element's selection. Should return an empty vector for
797 /// elements that don't define any selection-specific click behaviors.
798 fn calculate_clickable_bounds(&self, _current_selection: Option<Selection>) -> Vec<RectF>;
799}
800