Files
ruin/lib/ui/src/tree.rs

245 lines
5.4 KiB
Rust

use crate::scene::Color;
use crate::text::{TextSpan, TextStyle, TextWrap};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct ElementId(u64);
impl ElementId {
pub const fn new(raw: u64) -> Self {
Self(raw)
}
pub const fn raw(self) -> u64 {
self.0
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum FlexDirection {
Row,
Column,
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum CursorIcon {
Default,
Pointer,
Text,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Edges {
pub top: f32,
pub right: f32,
pub bottom: f32,
pub left: f32,
}
impl Edges {
pub const ZERO: Self = Self {
top: 0.0,
right: 0.0,
bottom: 0.0,
left: 0.0,
};
pub const fn all(value: f32) -> Self {
Self {
top: value,
right: value,
bottom: value,
left: value,
}
}
pub const fn symmetric(horizontal: f32, vertical: f32) -> Self {
Self {
top: vertical,
right: horizontal,
bottom: vertical,
left: horizontal,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct Style {
pub direction: FlexDirection,
pub width: Option<f32>,
pub height: Option<f32>,
pub flex_grow: f32,
pub gap: f32,
pub padding: Edges,
pub background: Option<Color>,
pub pointer_events: bool,
pub focusable: bool,
pub cursor: Option<CursorIcon>,
}
impl Default for Style {
fn default() -> Self {
Self {
direction: FlexDirection::Column,
width: None,
height: None,
flex_grow: 0.0,
gap: 0.0,
padding: Edges::ZERO,
background: None,
pointer_events: true,
focusable: false,
cursor: None,
}
}
}
#[derive(Clone, Debug, PartialEq)]
enum ElementContent {
Container,
Text(TextNode),
}
#[derive(Clone, Debug, PartialEq)]
pub(crate) struct TextNode {
pub spans: Vec<TextSpan>,
pub style: TextStyle,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Element {
pub id: Option<ElementId>,
pub style: Style,
pub children: Vec<Element>,
content: ElementContent,
}
impl Element {
pub fn new() -> Self {
Self {
id: None,
style: Style::default(),
children: Vec::new(),
content: ElementContent::Container,
}
}
pub fn text(text: impl Into<String>, style: TextStyle) -> Self {
Self::spans([TextSpan::new(text)], style)
}
pub fn spans(spans: impl IntoIterator<Item = TextSpan>, style: TextStyle) -> Self {
Self {
id: None,
style: Style::default(),
children: Vec::new(),
content: ElementContent::Text(TextNode {
spans: spans.into_iter().collect(),
style,
}),
}
}
pub fn paragraph(text: impl Into<String>, style: TextStyle) -> Self {
Self::text(text, style.with_wrap(TextWrap::Word))
}
pub fn rich_paragraph(spans: impl IntoIterator<Item = TextSpan>, style: TextStyle) -> Self {
Self::spans(spans, style.with_wrap(TextWrap::Word))
}
pub fn row() -> Self {
Self::new().direction(FlexDirection::Row)
}
pub fn column() -> Self {
Self::new().direction(FlexDirection::Column)
}
pub fn direction(mut self, direction: FlexDirection) -> Self {
self.style.direction = direction;
self
}
pub fn width(mut self, width: f32) -> Self {
self.style.width = Some(width);
self
}
pub fn height(mut self, height: f32) -> Self {
self.style.height = Some(height);
self
}
pub fn flex(mut self, flex_grow: f32) -> Self {
self.style.flex_grow = flex_grow.max(0.0);
self
}
pub fn gap(mut self, gap: f32) -> Self {
self.style.gap = gap.max(0.0);
self
}
pub fn padding(mut self, padding: Edges) -> Self {
self.style.padding = padding;
self
}
pub fn background(mut self, color: Color) -> Self {
self.style.background = Some(color);
self
}
pub fn id(mut self, id: ElementId) -> Self {
self.id = Some(id);
self
}
pub fn pointer_events(mut self, pointer_events: bool) -> Self {
self.style.pointer_events = pointer_events;
self
}
pub fn focusable(mut self, focusable: bool) -> Self {
self.style.focusable = focusable;
self
}
pub fn cursor(mut self, cursor: CursorIcon) -> Self {
self.style.cursor = Some(cursor);
self
}
pub fn child(mut self, child: Element) -> Self {
self.assert_container();
self.children.push(child);
self
}
pub fn children(mut self, children: impl IntoIterator<Item = Element>) -> Self {
self.assert_container();
self.children.extend(children);
self
}
pub(crate) fn text_node(&self) -> Option<&TextNode> {
match &self.content {
ElementContent::Text(text) => Some(text),
ElementContent::Container => None,
}
}
fn assert_container(&self) {
assert!(
matches!(self.content, ElementContent::Container),
"text elements cannot contain children"
);
}
}
impl Default for Element {
fn default() -> Self {
Self::new()
}
}