Files
galaxy/crates/galaxyui_core/src/elements/mod.rs
T

800 lines
21 KiB
Rust

mod align;
mod child_view;
mod clipped;
mod clipped_scrollable;
mod constrained_box;
mod container;
#[cfg(debug_assertions)]
mod debug;
mod dismiss;
mod drag;
pub mod drag_resize;
mod empty;
mod event_handler;
mod flex;
mod formatted_text_element;
mod hoverable;
mod icon;
mod image;
mod list;
mod min_size;
pub mod new_scrollable;
mod percentage;
mod rect;
pub mod resizable;
mod scrollable;
mod selectable_area;
pub mod shared_scrollbar;
pub mod shimmering_text;
mod size_constraint_switch;
mod stack;
pub mod table;
mod text;
mod uniform_list;
mod viewported_list;
pub use align::*;
pub use child_view::*;
pub use clipped::*;
pub use clipped_scrollable::*;
pub use constrained_box::*;
pub use container::*;
#[cfg(debug_assertions)]
pub use debug::*;
pub use dismiss::*;
pub use drag::*;
pub use drag_resize::*;
pub use empty::*;
pub use event_handler::*;
pub use flex::*;
pub use formatted_text_element::*;
pub use hoverable::*;
pub use icon::*;
pub use image::*;
pub use list::*;
pub use min_size::*;
pub use new_scrollable::NewScrollable;
pub use percentage::*;
pub use rect::*;
pub use resizable::*;
pub use scrollable::*;
pub use selectable_area::*;
pub use shared_scrollbar::*;
pub use size_constraint_switch::*;
pub use stack::*;
pub use table::{
RowBackground, Table, TableColumnWidth, TableConfig, TableHeader, TableState, TableStateHandle,
TableVerticalSizing,
};
pub use text::*;
pub use uniform_list::*;
pub use viewported_list::*;
use crate::event::ModifiersState;
use crate::platform::Cursor;
use crate::{
event::DispatchedEvent,
text::{word_boundaries::WordBoundariesPolicy, IsRect, SelectionDirection, SelectionType},
Gradient,
};
pub use crate::{
scene::Dash, scene::ZIndex, AfterLayoutContext, AppContext, Event, EventContext, LayoutContext,
PaintContext, SizeConstraint,
};
use core::fmt;
use pathfinder_color::ColorU;
use pathfinder_geometry::{
rect::RectF,
vector::{vec2f, Vector2F},
};
use std::any::Any;
use std::borrow::Cow;
use std::ops::Range;
use std::sync::MutexGuard;
/// The result of dispatching an event.
/// This is (future) return type of `dispatch_event`.
/// This will eventually replace the current boolean return type, to be more explicit about
/// which events should continue to propagate to parent elements and which should stop.
pub enum DispatchEventResult {
/// The event should continue to propagate to parent elements.
PropagateToParent,
/// The event should not propagate to parent elements.
StopPropagation,
}
pub trait Element {
fn layout(
&mut self,
constraint: SizeConstraint,
ctx: &mut LayoutContext,
app: &AppContext,
) -> Vector2F;
fn after_layout(&mut self, _: &mut AfterLayoutContext, _: &AppContext);
fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext);
fn size(&self) -> Option<Vector2F>;
fn origin(&self) -> Option<Point>;
fn z_index(&self) -> Option<ZIndex> {
self.origin().map(|p| p.z_index())
}
fn bounds(&self) -> Option<RectF> {
try_rect_with_z(self.origin(), self.size())
}
fn parent_data(&self) -> Option<&dyn Any> {
None
}
/// Should be implemented alongside the SelectableElement trait. If implemented, it
/// should return the element as a SelectableElement.
fn as_selectable_element(&self) -> Option<&dyn SelectableElement> {
None
}
/// Handle an event from the OS (e.g. Mouse or Keyboard events)
///
/// Note: For each OS event, this is called on the root Element of the Element tree. Each
/// Element is then itself responsible for calling `dispatch_event` on its children. The
/// expectations for how an event propagates through the Element tree are:
///
/// 1. Each Element that handles an event in some meaningful way will first verify that the
/// event applies to them by doing any necessary hit testing.
/// 2. Each parent Element will unconditionally pass the event to its children by calling
/// `dispatch_event` on them, which allows the children to make their own determination
/// of whether or not the event applies.
/// 3. Elements should return true if they handled the event and don't want it to propagate
/// to parent elements, and false if they want it to propagate to parent elements.
fn dispatch_event(
&mut self,
event: &DispatchedEvent,
ctx: &mut EventContext,
app: &AppContext,
) -> bool;
fn finish(self) -> Box<dyn Element>
where
Self: 'static + Sized,
{
Box::new(self)
}
#[cfg(debug_assertions)]
fn type_name(&self) -> &'static str {
std::any::type_name::<Self>()
}
/// Returns the text content of this element, if it contains text.
/// This is primarily used for testing to verify rendered text content.
/// Container elements should aggregate text from their children.
#[cfg(any(test, feature = "test-util"))]
fn debug_text_content(&self) -> Option<String> {
None
}
}
pub trait ParentElement: Extend<Box<dyn Element>> + Sized {
#[cfg_attr(debug_assertions, track_caller)]
fn add_children(&mut self, children: impl IntoIterator<Item = Box<dyn Element>>) {
self.extend(children);
}
#[cfg_attr(debug_assertions, track_caller)]
fn add_child(&mut self, child: Box<dyn Element>) {
self.extend(Some(child))
}
#[cfg_attr(debug_assertions, track_caller)]
fn with_children(mut self, children: impl IntoIterator<Item = Box<dyn Element>>) -> Self {
self.add_children(children);
self
}
#[cfg_attr(debug_assertions, track_caller)]
fn with_child(self, child: Box<dyn Element>) -> Self {
self.with_children(Some(child))
}
}
impl<T> ParentElement for T where T: Extend<Box<dyn Element>> {}
#[derive(Clone, Debug)]
pub struct SelectionFragment {
pub text: String,
pub origin: Point,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Point {
xy: Vector2F,
z_index: ZIndex,
}
impl Point {
pub fn new(x: f32, y: f32, z_index: ZIndex) -> Self {
Self {
xy: vec2f(x, y),
z_index,
}
}
pub fn from_vec2f(xy: Vector2F, z_index: ZIndex) -> Self {
Self { xy, z_index }
}
pub fn x(&self) -> f32 {
self.xy.x()
}
pub fn y(&self) -> f32 {
self.xy.y()
}
pub fn xy(&self) -> Vector2F {
self.xy
}
pub fn z_index(&self) -> ZIndex {
self.z_index
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Axis {
Horizontal,
Vertical,
}
impl Axis {
pub fn invert(self) -> Self {
match self {
Self::Horizontal => Self::Vertical,
Self::Vertical => Self::Horizontal,
}
}
pub fn to_point(self, pos_along_main_axis: f32, pos_along_inverse_axis: f32) -> Vector2F {
match self {
Self::Horizontal => vec2f(pos_along_main_axis, pos_along_inverse_axis),
Self::Vertical => vec2f(pos_along_inverse_axis, pos_along_main_axis),
}
}
}
pub enum AxisOrientation {
Normal,
Reverse,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Fill {
#[default]
None,
Solid(ColorU),
Gradient {
start: Vector2F,
end: Vector2F,
start_color: ColorU,
end_color: ColorU,
},
}
impl From<ColorU> for Fill {
fn from(color: ColorU) -> Self {
Fill::Solid(color)
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct Margin {
top: f32,
left: f32,
bottom: f32,
right: f32,
}
impl Margin {
pub const fn uniform(margin: f32) -> Self {
Margin {
top: margin,
left: margin,
bottom: margin,
right: margin,
}
}
pub const fn with_left(mut self, margin: f32) -> Self {
self.left = margin;
self
}
pub const fn with_right(mut self, margin: f32) -> Self {
self.right = margin;
self
}
pub const fn with_top(mut self, margin: f32) -> Self {
self.top = margin;
self
}
pub const fn with_bottom(mut self, margin: f32) -> Self {
self.bottom = margin;
self
}
pub fn top(&self) -> f32 {
self.top
}
pub fn left(&self) -> f32 {
self.left
}
pub fn bottom(&self) -> f32 {
self.bottom
}
pub fn right(&self) -> f32 {
self.right
}
}
#[derive(Default, Debug, Clone, Copy, PartialEq)]
pub struct Padding {
top: f32,
left: f32,
bottom: f32,
right: f32,
}
impl Padding {
pub const fn uniform(padding: f32) -> Self {
Self {
top: padding,
left: padding,
bottom: padding,
right: padding,
}
}
pub const fn with_top(mut self, padding: f32) -> Self {
self.top = padding;
self
}
pub const fn with_left(mut self, padding: f32) -> Self {
self.left = padding;
self
}
pub const fn with_bottom(mut self, padding: f32) -> Self {
self.bottom = padding;
self
}
pub const fn with_right(mut self, padding: f32) -> Self {
self.right = padding;
self
}
pub fn with_vertical(mut self, vertical: f32) -> Self {
self.top = vertical;
self.bottom = vertical;
self
}
pub fn with_horizontal(mut self, horizontal: f32) -> Self {
self.left = horizontal;
self.right = horizontal;
self
}
pub fn top(&self) -> f32 {
self.top
}
pub fn left(&self) -> f32 {
self.left
}
pub fn bottom(&self) -> f32 {
self.bottom
}
pub fn right(&self) -> f32 {
self.right
}
}
#[derive(Default)]
pub struct Overdraw {
top: f32,
left: f32,
bottom: f32,
right: f32,
}
impl Border {
pub const fn new(width: f32) -> Self {
Self {
width,
color: Fill::None,
top: false,
left: false,
bottom: false,
right: false,
dash: None,
}
}
pub fn all(width: f32) -> Self {
Self {
width,
color: Fill::None,
top: true,
left: true,
bottom: true,
right: true,
dash: None,
}
}
pub fn top(width: f32) -> Self {
let mut border = Self::new(width);
border.top = true;
border
}
pub fn left(width: f32) -> Self {
let mut border = Self::new(width);
border.left = true;
border
}
pub fn bottom(width: f32) -> Self {
let mut border = Self::new(width);
border.bottom = true;
border
}
pub fn right(width: f32) -> Self {
let mut border = Self::new(width);
border.right = true;
border
}
pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self {
self.top = top;
self.left = left;
self.bottom = bottom;
self.right = right;
self
}
pub fn with_border_fill<F>(mut self, fill: F) -> Self
where
F: Into<Fill>,
{
self.color = fill.into();
self
}
pub fn with_border_color(mut self, color: ColorU) -> Self {
self.color = Fill::Solid(color);
self
}
pub fn with_horizontal_border_gradient(mut self, gradient: Gradient) -> Self {
self.color = Fill::Gradient {
start: vec2f(0.0, 0.0),
end: vec2f(1.0, 0.0),
start_color: gradient.start,
end_color: gradient.end,
};
self
}
pub fn with_border_gradient(
mut self,
start: Vector2F,
end: Vector2F,
gradient: Gradient,
) -> Self {
self.color = Fill::Gradient {
start,
end,
start_color: gradient.start,
end_color: gradient.end,
};
self
}
/// Note: only implemented for sharp corners. ***DO NOT*** use for elements with corner radius != 0, as this causes visual bugs.
pub fn with_dashed_border(mut self, dash: Dash) -> Self {
self.dash = Some(dash);
self
}
}
impl From<ColorU> for Border {
fn from(value: ColorU) -> Self {
Border::all(1.).with_border_color(value)
}
}
impl Fill {
pub fn start(&self) -> Vector2F {
match self {
Self::Gradient { start, .. } => *start,
_ => vec2f(0.0, 0.0),
}
}
pub fn end(&self) -> Vector2F {
match self {
Self::Gradient { end, .. } => *end,
_ => vec2f(1.0, 0.0),
}
}
pub fn start_color(&self) -> ColorU {
match self {
Self::Gradient { start_color, .. } => *start_color,
Self::Solid(color) => *color,
Self::None => ColorU::transparent_black(),
}
}
pub fn end_color(&self) -> ColorU {
match self {
Self::Gradient { end_color, .. } => *end_color,
Self::Solid(color) => *color,
Self::None => ColorU::transparent_black(),
}
}
}
/// Extends the `Vector2F` API to provider richer APIs for
/// element-related computations.
pub trait Vector2FExt {
/// Converts the 2D vector to a scalar according to the given `axis`.
fn along(self, axis: Axis) -> f32;
/// Projects the 2D vector onto the given `axis`.
/// e.g. (5, 2) -> (5, 0), along the x-axis.
fn project_onto(self, axis: Axis) -> Vector2F;
/// [`fmt::Display`] impl to format this `Vector2F` as a point.
fn display_point(self) -> Vector2FDisplayPoint;
/// [`fmt::Display`] impl to format this `Vector2F` as a size.
fn display_size(self) -> Vector2FDisplaySize;
}
impl Vector2FExt for Vector2F {
fn along(self, axis: Axis) -> f32 {
match axis {
Axis::Horizontal => self.x(),
Axis::Vertical => self.y(),
}
}
fn project_onto(self, axis: Axis) -> Vector2F {
match axis {
Axis::Horizontal => vec2f(self.x(), 0.),
Axis::Vertical => vec2f(0., self.y()),
}
}
fn display_point(self) -> Vector2FDisplayPoint {
Vector2FDisplayPoint(self)
}
fn display_size(self) -> Vector2FDisplaySize {
Vector2FDisplaySize(self)
}
}
pub struct Vector2FDisplaySize(Vector2F);
impl fmt::Display for Vector2FDisplaySize {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// We have to call .fmt directly so that formatting options are propagated.
self.0.x().fmt(f)?;
f.write_str("x")?;
self.0.y().fmt(f)
}
}
pub struct Vector2FDisplayPoint(Vector2F);
impl fmt::Display for Vector2FDisplayPoint {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// We have to call .fmt directly so that formatting options are propagated.
f.write_str("(")?;
self.0.x().fmt(f)?;
f.write_str(", ")?;
self.0.y().fmt(f)?;
f.write_str(")")
}
}
/// Extends the `f32` API to provider richer APIs for
/// element-related computations.
pub trait F32Ext {
/// Converts the `f32` to a 2D vector along the provided `axis`.
fn along(self, axis: Axis) -> Vector2F;
}
impl F32Ext for f32 {
fn along(self, axis: Axis) -> Vector2F {
match axis {
Axis::Horizontal => vec2f(self, 0.),
Axis::Vertical => vec2f(0., self),
}
}
}
/// Extends the `RectF` API to provider richer APIs for
/// element-related computations.
pub trait RectFExt {
/// Returns the minimum value along the given `axis`.
fn min_along(self, axis: Axis) -> f32;
/// Returns the maximum value along the given `axis`.
fn max_along(self, axis: Axis) -> f32;
}
impl RectFExt for RectF {
fn min_along(self, axis: Axis) -> f32 {
match axis {
Axis::Horizontal => self.min_x(),
Axis::Vertical => self.min_y(),
}
}
fn max_along(self, axis: Axis) -> f32 {
match axis {
Axis::Horizontal => self.max_x(),
Axis::Vertical => self.max_y(),
}
}
}
pub fn try_rect(origin: Option<Vector2F>, size: Option<Vector2F>) -> Option<RectF> {
origin.and_then(|origin| size.map(|size| RectF::new(origin, size)))
}
pub fn try_rect_with_z(origin: Option<Point>, size: Option<Vector2F>) -> Option<RectF> {
origin.and_then(|origin| size.map(|size| RectF::new(origin.xy(), size)))
}
/// The click handler provides the caller with the clicked text chunk index in
/// the provided clickable char ranges and the string corresponds to that chunk,
/// if one of the clickable chunks were clicked
pub type ClickHandler = Box<dyn FnMut(&ModifiersState, &mut EventContext, &AppContext)>;
/// The hover handler is called when the mouse either hovers or unhovers over a
/// hoverable char range, with the first argument being is_hovering.
pub type HoverHandler = Box<dyn FnMut(bool, &mut EventContext, &AppContext)>;
pub(crate) struct ClickableCharRange {
pub(crate) char_range: Range<usize>,
pub(crate) click_handler: ClickHandler,
}
pub(crate) struct HoverableCharRange {
pub(crate) char_range: Range<usize>,
pub(crate) hover_handler: HoverHandler,
pub(crate) cursor_on_hover: Option<Cursor>,
pub(crate) mouse_state: MouseStateHandle,
}
impl HoverableCharRange {
fn mouse_state(&self) -> MutexGuard<'_, MouseState> {
self.mouse_state
.lock()
.expect("The hoverable range should lock mouse state")
}
}
/// SecretRange is used to store both the char range and byte range of a secret.
/// We need to do this since several APIs e.g. hover/click APIs, use char ranges,
/// whereas text-related APIs e.g. Regex and replace_range, use byte ranges.
#[derive(Debug, Eq, PartialEq, Hash, Clone)]
pub struct SecretRange {
pub char_range: Range<usize>,
pub byte_range: Range<usize>,
}
impl SecretRange {
/// Extends the current range to include the provided range.
pub fn extend_range_end(&mut self, other: &SecretRange) {
self.char_range.end = self.char_range.end.max(other.char_range.end);
self.byte_range.end = self.byte_range.end.max(other.byte_range.end);
}
}
pub trait PartialClickableElement {
/// clickable_char_ranges is the vector of char ranges that the caller can
/// specify, where the callback will be called if any character in one of
/// those char ranges was clicked
fn with_clickable_char_range<F>(
self,
_clickable_char_range: Range<usize>,
_callback: F,
) -> Self
where
F: 'static + FnMut(&ModifiersState, &mut EventContext, &AppContext);
/// Registers a callback that is called when a character in the given hoverable_char_range
/// is hovered or unhovered.
fn with_hoverable_char_range<F>(
self,
hoverable_char_range: Range<usize>,
mouse_state: MouseStateHandle,
cursor_on_hover: Option<Cursor>,
callback: F,
) -> Self
where
F: 'static + FnMut(bool, &mut EventContext, &AppContext);
/// Replace in the given range of the text with the replacement text.
fn replace_text_range(&mut self, range: SecretRange, replacement: Cow<'static, str>);
}
/// An element that can be selected, for use with the SelectableArea element.
/// It is expected that an element implementing this trait (i.e. Text)
/// also implements as_selectable_element().
pub trait SelectableElement {
/// Return the element's selected fragments.
fn get_selection(
&self,
_selection_start: Vector2F,
_selection_end: Vector2F,
_is_rect: IsRect,
) -> Option<Vec<SelectionFragment>>;
/// Semantically expands the absolute selection point based on the unit.
/// Does nothing if the unit is Char because there is no need to expand.
/// Expands to the start of the unit if expand_to_start is true, otherwise
/// expands to the end of the unit.
/// If the absolute point before the element's bounds and expand_to_start is true,
/// should expand to the start of the element. Similarly, if the absolute point is after
/// the element's bounds and expand_to_start is false, should expand to the end of the element.
/// Otherwise, should return None.
fn expand_selection(
&self,
_absolute_point: Vector2F,
_direction: SelectionDirection,
_unit: SelectionType,
_word_boundaries_policy: &WordBoundariesPolicy,
) -> Option<Vector2F>;
/// Returns None if neither point is in the element.
fn is_point_semantically_before(
&self,
_absolute_point: Vector2F,
_absolute_point_other: Vector2F,
) -> Option<bool>;
/// Runs smart selection on a point.
/// Should return None if the point is outside the element vertically,
/// but should snap to the nearest line if out of bounds horizontally.
fn smart_select(
&self,
_absolute_point: Vector2F,
_smart_select_fn: SmartSelectFn,
) -> Option<(Vector2F, Vector2F)>;
/// The union of the returned regions defines the area within which a mouse click is considered
/// to be a click performed on the element's selection. Should return an empty vector for
/// elements that don't define any selection-specific click behaviors.
fn calculate_clickable_bounds(&self, _current_selection: Option<Selection>) -> Vec<RectF>;
}