245 lines
5.4 KiB
Rust
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()
|
|
}
|
|
}
|