Get a red box on screen

Nathan Sobo and Derek Briggs created

Co-Authored-By: Derek Briggs <derek.briggs@me.com>

Change summary

crates/gpui/playground/src/elements.rs   | 652 ++++++++++++++++++++++++++
crates/gpui/playground/src/playground.rs |  73 --
crates/gpui/src/app/window.rs            |   2 
crates/gpui/src/elements.rs              |   1 
crates/gpui/src/elements/cell.rs         | 141 -----
5 files changed, 668 insertions(+), 201 deletions(-)

Detailed changes

crates/gpui/playground/src/elements.rs 🔗

@@ -0,0 +1,652 @@
+use gpui::{
+    color::Color,
+    geometry::{
+        rect::RectF,
+        vector::{vec2f, Vector2F},
+    },
+    json::{json, ToJson},
+    scene,
+    serde_json::Value,
+    AnyElement, Element, LayoutContext, Quad, SceneBuilder, SizeConstraint, View, ViewContext,
+};
+use std::{any::Any, ops::Range, rc::Rc};
+
+pub struct Atom<V: View> {
+    style: Rc<AtomStyle>,
+    children: Vec<AnyElement<V>>,
+}
+
+impl<V: View> Atom<V> {
+    pub fn new(style: impl Into<Rc<AtomStyle>>) -> Self {
+        Self {
+            style: style.into(),
+            children: Vec::new(),
+        }
+    }
+
+    fn inner_constraint(&self, mut constraint: SizeConstraint) -> SizeConstraint {
+        // Constrain width
+        constraint
+            .max
+            .set_x(constraint.max.x().min(self.style.width.max()));
+
+        // Constrain height
+        constraint
+            .max
+            .set_y(constraint.max.y().min(self.style.height.max()));
+
+        // Account for margin, border, and padding
+        let inset = self.inset_size();
+        SizeConstraint {
+            min: (constraint.min - inset).max(Vector2F::zero()),
+            max: (constraint.max - inset).max(Vector2F::zero()),
+        }
+    }
+
+    // fn layout_2d_children(
+    //     &mut self,
+    //     orientation: Axis2d,
+    //     constraint: SizeConstraint,
+    //     view: &mut V,
+    //     cx: &mut LayoutContext<V>,
+    // ) -> Vector2F {
+    //     let mut total_flex: Option<f32> = None;
+    //     let mut total_size = 0.0;
+    //     let mut cross_axis_max: f32 = 0.0;
+
+    //     // First pass: Layout non-flex children only
+    //     for child in &mut self.children {
+    //         if let Some(child_flex) = child.metadata::<AtomStyle>().and_then(|style| style.flex) {
+    //             *total_flex.get_or_insert(0.) += child_flex;
+    //         } else {
+    //             let child_constraint = match orientation {
+    //                 Axis2d::X => SizeConstraint::new(
+    //                     vec2f(0.0, constraint.min.y()),
+    //                     vec2f(INFINITY, constraint.max.y()),
+    //                 ),
+    //                 Axis2d::Y => SizeConstraint::new(
+    //                     vec2f(constraint.min.x(), 0.0),
+    //                     vec2f(constraint.max.x(), INFINITY),
+    //                 ),
+    //             };
+    //             let child_size = child.layout(child_constraint, view, cx);
+    //             total_size += match orientation {
+    //                 Axis3d::Horizontal => {
+    //                     cross_axis_max = cross_axis_max.max(child_size.y());
+    //                     child_size.x()
+    //                 }
+    //                 Axis3d::Vertical => {
+    //                     cross_axis_max = cross_axis_max.max(child_size.x());
+    //                     child_size.y()
+    //                 }
+    //             };
+    //         }
+    //     }
+
+    //     let remaining_space = match orientation {
+    //         Axis3d::Vertical => constraint.max.y() - total_size,
+    //         Axis3d::Horizontal => constraint.max.x() - total_size,
+    //     };
+
+    //     // Second pass: Layout flexible children
+    //     if let Some(total_flex) = total_flex {
+    //         if total_flex > 0. {
+    //             let space_per_flex = remaining_space.max(0.) / total_flex;
+
+    //             for child in &mut self.children {
+    //                 if let Some(child_flex) =
+    //                     child.metadata::<AtomStyle>().and_then(|style| style.flex)
+    //                 {
+    //                     let child_max = space_per_flex * child_flex;
+    //                     let mut child_constraint = constraint;
+    //                     match orientation {
+    //                         Axis3d::Vertical => {
+    //                             child_constraint.min.set_y(0.0);
+    //                             child_constraint.max.set_y(child_max);
+    //                         }
+    //                         Axis3d::Horizontal => {
+    //                             child_constraint.min.set_x(0.0);
+    //                             child_constraint.max.set_x(child_max);
+    //                         }
+    //                     }
+
+    //                     let child_size = child.layout(child_constraint, view, cx);
+
+    //                     cross_axis_max = match orientation {
+    //                         Axis3d::Vertical => {
+    //                             total_size += child_size.y();
+    //                             cross_axis_max.max(child_size.x())
+    //                         }
+    //                         Axis3d::Horizontal => {
+    //                             total_size += child_size.x();
+    //                             cross_axis_max.max(child_size.y())
+    //                         }
+    //                     };
+    //                 }
+    //             }
+    //         }
+    //     }
+
+    //     let size = match orientation {
+    //         Axis3d::Vertical => vec2f(cross_axis_max, total_size),
+    //         Axis3d::Horizontal => vec2f(total_size, cross_axis_max),
+    //     };
+    //     size
+    // }
+
+    // fn layout_stacked_children(
+    //     &mut self,
+    //     constraint: SizeConstraint,
+    //     view: &mut V,
+    //     cx: &mut LayoutContext<V>,
+    // ) -> Vector2F {
+    //     let mut size = Vector2F::zero();
+
+    //     for child in &mut self.children {
+    //         let child_size = child.layout(constraint, view, cx);
+    //         size.set_x(size.x().max(child_size.x()));
+    //         size.set_y(size.y().max(child_size.y()));
+    //     }
+
+    //     size
+    // }
+
+    fn inset_size(&self) -> Vector2F {
+        self.padding_size() + self.border_size() + self.margin_size()
+    }
+
+    fn margin_size(&self) -> Vector2F {
+        vec2f(
+            self.style.margin.left + self.style.margin.right,
+            self.style.margin.top + self.style.margin.bottom,
+        )
+    }
+
+    fn padding_size(&self) -> Vector2F {
+        vec2f(
+            self.style.padding.left + self.style.padding.right,
+            self.style.padding.top + self.style.padding.bottom,
+        )
+    }
+
+    fn border_size(&self) -> Vector2F {
+        let mut x = 0.0;
+        if self.style.border.left {
+            x += self.style.border.width;
+        }
+        if self.style.border.right {
+            x += self.style.border.width;
+        }
+
+        let mut y = 0.0;
+        if self.style.border.top {
+            y += self.style.border.width;
+        }
+        if self.style.border.bottom {
+            y += self.style.border.width;
+        }
+
+        vec2f(x, y)
+    }
+}
+
+impl<V: View> Element<V> for Atom<V> {
+    type LayoutState = Vector2F; // Content size
+    type PaintState = ();
+
+    fn layout(
+        &mut self,
+        constraint: SizeConstraint,
+        view: &mut V,
+        cx: &mut LayoutContext<V>,
+    ) -> (Vector2F, Self::LayoutState) {
+        let inner_constraint = self.inner_constraint(constraint);
+        // let size_of_children = match self.style.axis {
+        //     // Axis3d::X => self.layout_2d_children(Axis2d::X, constraint, view, cx),
+        //     // Axis3d::Y => self.layout_2d_children(Axis2d::Y, constraint, view, cx),
+        //     // Axis3d::Z => self.layout_stacked_children(constraint, view, cx),
+        // };
+        let size_of_children = inner_constraint.max; // TODO!
+
+        // Add back space for padding, border, and margin.
+        let mut size = size_of_children + self.inset_size();
+
+        // Impose horizontal constraints
+        if constraint.min.x().is_finite() {
+            size.set_x(size.x().max(constraint.min.x()));
+        }
+        if size.x() > constraint.max.x() {
+            size.set_x(constraint.max.x());
+        }
+
+        // Impose vertical constraints
+        if constraint.min.y().is_finite() {
+            size.set_y(size.y().max(constraint.min.y()));
+        }
+        if size.y() > constraint.max.y() {
+            size.set_y(constraint.max.y());
+        }
+
+        (size, size_of_children)
+    }
+
+    fn paint(
+        &mut self,
+        scene: &mut SceneBuilder,
+        bounds: RectF,
+        visible_bounds: RectF,
+        size_of_children: &mut Vector2F,
+        view: &mut V,
+        cx: &mut ViewContext<V>,
+    ) -> Self::PaintState {
+        let margin = &self.style.margin;
+
+        // Account for margins
+        let content_bounds = RectF::from_points(
+            bounds.origin() + vec2f(margin.left, margin.top),
+            bounds.lower_right() - vec2f(margin.right, margin.bottom),
+        );
+
+        // Paint drop shadow
+        for shadow in &self.style.shadows {
+            scene.push_shadow(scene::Shadow {
+                bounds: content_bounds + shadow.offset,
+                corner_radius: self.style.corner_radius,
+                sigma: shadow.blur,
+                color: shadow.color,
+            });
+        }
+
+        // // Paint cursor style
+        // if let Some(hit_bounds) = content_bounds.intersection(visible_bounds) {
+        //     if let Some(style) = self.style.cursor {
+        //         scene.push_cursor_region(CursorRegion {
+        //             bounds: hit_bounds,
+        //             style,
+        //         });
+        //     }
+        // }
+
+        // Render the background and/or the border (if it not an overlay border).
+        match self.style.fill {
+            Fill::Color(fill_color) => {}
+        }
+        if let Fill::Color(fill_color) = self.style.fill {
+            let is_fill_visible = !fill_color.is_fully_transparent();
+            if is_fill_visible || self.style.border.is_visible() {
+                scene.push_quad(Quad {
+                    bounds: content_bounds,
+                    background: is_fill_visible.then_some(fill_color),
+                    border: scene::Border {
+                        width: self.style.border.width,
+                        color: self.style.border.color,
+                        overlay: false,
+                        top: self.style.border.top,
+                        right: self.style.border.right,
+                        bottom: self.style.border.bottom,
+                        left: self.style.border.left,
+                    },
+                    corner_radius: self.style.corner_radius,
+                });
+            }
+        }
+
+        if !self.children.is_empty() {
+            // Account for padding first.
+            let padding = &self.style.padding;
+            let padded_bounds = RectF::from_points(
+                content_bounds.origin() + vec2f(padding.left, padding.top),
+                content_bounds.lower_right() - vec2f(padding.right, padding.top),
+            );
+            let parent_size = padded_bounds.size();
+
+            // Now paint the children accourding to the orientation.
+            let child_aligment = self.style.align;
+            // match self.style.orientation {
+            //     Orientation::Axial(axis) => {
+            //         let mut child_origin = padded_bounds.origin();
+            //         // Align all children together along the primary axis
+            //         match axis {
+            //             Axis3d::Horizontal => align_child(
+            //                 &mut child_origin,
+            //                 parent_size,
+            //                 *size_of_children,
+            //                 child_aligment,
+            //                 true,
+            //                 false,
+            //             ),
+            //             Axis3d::Vertical => align_child(
+            //                 &mut child_origin,
+            //                 parent_size,
+            //                 *size_of_children,
+            //                 child_aligment,
+            //                 false,
+            //                 true,
+            //             ),
+            //         };
+
+            //         for child in &mut self.children {
+            //             // Align each child along the cross axis
+            //             match axis {
+            //                 Axis3d::Horizontal => {
+            //                     child_origin.set_y(padded_bounds.origin_y());
+            //                     align_child(
+            //                         &mut child_origin,
+            //                         parent_size,
+            //                         child.size(),
+            //                         child_aligment,
+            //                         false,
+            //                         true,
+            //                     );
+            //                 }
+            //                 Axis3d::Vertical => {
+            //                     child_origin.set_x(padded_bounds.origin_x());
+            //                     align_child(
+            //                         &mut child_origin,
+            //                         parent_size,
+            //                         child.size(),
+            //                         child_aligment,
+            //                         true,
+            //                         false,
+            //                     );
+            //                 }
+            //             }
+
+            //             child.paint(scene, child_origin, visible_bounds, view, cx);
+
+            //             // Advance along the cross axis by the size of this child
+            //             match axis {
+            //                 Axis3d::Horizontal => {
+            //                     child_origin.set_x(child_origin.x() + child.size().x())
+            //                 }
+            //                 Axis3d::Vertical => {
+            //                     child_origin.set_y(child_origin.x() + child.size().y())
+            //                 }
+            //             }
+            //         }
+            //     }
+            //     Orientation::Stacked => {
+            //         for child in &mut self.children {
+            //             let mut child_origin = padded_bounds.origin();
+            //             align_child(
+            //                 &mut child_origin,
+            //                 parent_size,
+            //                 child.size(),
+            //                 child_aligment,
+            //                 true,
+            //                 true,
+            //             );
+
+            //             scene.paint_layer(None, |scene| {
+            //                 child.paint(scene, child_origin, visible_bounds, view, cx);
+            //             });
+            //         }
+            //     }
+            // }
+        }
+    }
+
+    fn rect_for_text_range(
+        &self,
+        range_utf16: Range<usize>,
+        _: RectF,
+        _: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        view: &V,
+        cx: &ViewContext<V>,
+    ) -> Option<RectF> {
+        self.children
+            .iter()
+            .find_map(|child| child.rect_for_text_range(range_utf16.clone(), view, cx))
+    }
+
+    fn debug(
+        &self,
+        bounds: RectF,
+        _: &Self::LayoutState,
+        _: &Self::PaintState,
+        view: &V,
+        cx: &ViewContext<V>,
+    ) -> Value {
+        json!({
+            "type": "Cell",
+            "bounds": bounds.to_json(),
+            "children": self.children.iter().map(|child| child.debug(view, cx)).collect::<Vec<Value>>()
+        })
+    }
+
+    fn metadata(&self) -> Option<&dyn Any> {
+        Some(&self.style)
+    }
+}
+
+fn align_child(
+    child_origin: &mut Vector2F,
+    parent_size: Vector2F,
+    child_size: Vector2F,
+    alignment: Vector2F,
+    horizontal: bool,
+    vertical: bool,
+) {
+    let parent_center = parent_size / 2.;
+    let parent_target = parent_center + parent_center * alignment;
+    let child_center = child_size / 2.;
+    let child_target = child_center + child_center * alignment;
+
+    if horizontal {
+        child_origin.set_x(child_origin.x() + parent_target.x() - child_target.x())
+    }
+    if vertical {
+        child_origin.set_y(child_origin.y() + parent_target.y() - child_target.y());
+    }
+}
+
+struct Interactive<Style> {
+    default: Style,
+    hovered: Style,
+    active: Style,
+    disabled: Style,
+}
+
+#[derive(Clone, Default)]
+pub struct AtomStyle {
+    axis: Axis3d,
+    wrap: bool,
+    align: Vector2F,
+    overflow_x: Overflow,
+    overflow_y: Overflow,
+    gap_x: Gap,
+    gap_y: Gap,
+
+    width: Length,
+    height: Length,
+    margin: Edges<f32>,
+    padding: Edges<f32>,
+
+    text_color: Option<Color>,
+    font_size: Option<f32>,
+    font_style: Option<FontStyle>,
+    font_weight: Option<FontWeight>,
+
+    opacity: f32,
+    fill: Fill,
+    border: Border,
+    corner_radius: f32,
+    shadows: Vec<Shadow>,
+}
+
+impl AtomStyle {
+    pub fn width(mut self, width: impl Into<Length>) -> Self {
+        self.width = width.into();
+        self
+    }
+
+    pub fn height(mut self, height: impl Into<Length>) -> Self {
+        self.height = height.into();
+        self
+    }
+
+    pub fn fill(mut self, fill: impl Into<Fill>) -> Self {
+        self.fill = fill.into();
+        self
+    }
+}
+
+#[derive(Clone, Default)]
+struct Edges<T> {
+    top: T,
+    bottom: T,
+    left: T,
+    right: T,
+}
+
+#[derive(Clone, Default)]
+struct CornerRadii {
+    top_left: f32,
+    top_right: f32,
+    bottom_right: f32,
+    bottom_left: f32,
+}
+
+#[derive(Clone)]
+enum Fill {
+    Color(Color),
+}
+
+impl From<Color> for Fill {
+    fn from(value: Color) -> Self {
+        Fill::Color(value)
+    }
+}
+
+impl Default for Fill {
+    fn default() -> Self {
+        Fill::Color(Color::default())
+    }
+}
+
+#[derive(Clone, Default)]
+struct Border {
+    color: Color,
+    width: f32,
+    top: bool,
+    bottom: bool,
+    left: bool,
+    right: bool,
+}
+
+impl Border {
+    fn is_visible(&self) -> bool {
+        self.width > 0.
+            && !self.color.is_fully_transparent()
+            && (self.top || self.bottom || self.left || self.right)
+    }
+}
+
+#[derive(Clone, Copy)]
+enum Length {
+    Fixed(f32),
+    Auto { flex: f32, min: f32, max: f32 },
+}
+
+impl Default for Length {
+    fn default() -> Self {
+        Length::Auto {
+            flex: 1.,
+            min: 0.,
+            max: f32::INFINITY,
+        }
+    }
+}
+
+impl From<f32> for Length {
+    fn from(value: f32) -> Self {
+        Length::Fixed(value)
+    }
+}
+
+impl Length {
+    pub fn max(&self) -> f32 {
+        match self {
+            Length::Fixed(value) => *value,
+            Length::Auto { max, .. } => *max,
+        }
+    }
+}
+
+#[derive(Clone, Copy, Default)]
+enum Axis3d {
+    X,
+    #[default]
+    Y,
+    Z,
+}
+
+impl Axis3d {
+    fn to_2d(self) -> Option<Axis2d> {
+        match self {
+            Axis3d::X => Some(Axis2d::X),
+            Axis3d::Y => Some(Axis2d::Y),
+            Axis3d::Z => None,
+        }
+    }
+}
+
+#[derive(Clone, Copy, Default)]
+enum Axis2d {
+    X,
+    #[default]
+    Y,
+}
+
+#[derive(Clone, Copy, Default)]
+enum Overflow {
+    #[default]
+    Visible,
+    Hidden,
+    Auto,
+}
+
+#[derive(Clone, Copy)]
+enum Gap {
+    Fixed(f32),
+    Around,
+    Between,
+    Even,
+}
+
+impl Default for Gap {
+    fn default() -> Self {
+        Gap::Fixed(0.)
+    }
+}
+
+#[derive(Clone, Copy, Default)]
+struct Shadow {
+    offset: Vector2F,
+    blur: f32,
+    color: Color,
+}
+
+#[derive(Clone, Copy, Default)]
+enum FontStyle {
+    #[default]
+    Normal,
+    Italic,
+    Oblique,
+}
+
+#[derive(Clone, Copy, Default)]
+enum FontWeight {
+    Thin,
+    ExtraLight,
+    Light,
+    #[default]
+    Normal,
+    Medium,
+    Semibold,
+    Bold,
+    ExtraBold,
+    Black,
+}

crates/gpui/playground/src/playground.rs 🔗

@@ -1,80 +1,37 @@
-use gpui::{
-    color::Color,
-    elements::Text,
-    fonts::{HighlightStyle, TextStyle},
-    platform::{CursorStyle, MouseButton},
-    AnyElement, CursorRegion, Element, MouseRegion,
-};
+use elements::{Atom, AtomStyle};
+use gpui::{color::Color, AnyElement, Element, Entity, View};
 use log::LevelFilter;
 use simplelog::SimpleLogger;
 
+mod elements;
+
 fn main() {
     SimpleLogger::init(LevelFilter::Info, Default::default()).expect("could not initialize logger");
 
     gpui::App::new(()).unwrap().run(|cx| {
         cx.platform().activate(true);
-        cx.add_window(Default::default(), |_| TextView);
+        cx.add_window(Default::default(), |_| PlaygroundView);
     });
 }
 
-struct TextView;
+struct PlaygroundView;
 
-impl gpui::Entity for TextView {
+impl Entity for PlaygroundView {
     type Event = ();
 }
 
-impl gpui::View for TextView {
+impl View for PlaygroundView {
     fn ui_name() -> &'static str {
-        "View"
+        "PlaygroundView"
     }
 
-    fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> AnyElement<TextView> {
-        let font_size = 12.;
-        let family = cx
-            .font_cache
-            .load_family(&["Monaco"], &Default::default())
-            .unwrap();
-        let font_id = cx
-            .font_cache
-            .select_font(family, &Default::default())
-            .unwrap();
-        let view_id = cx.view_id();
-
-        let underline = HighlightStyle {
-            underline: Some(gpui::fonts::Underline {
-                thickness: 1.0.into(),
-                ..Default::default()
-            }),
-            ..Default::default()
-        };
-
-        Text::new(
-            "The text:\nHello, beautiful world, hello!",
-            TextStyle {
-                font_id,
-                font_size,
-                color: Color::red(),
-                font_family_name: "".into(),
-                font_family_id: family,
-                underline: Default::default(),
-                font_properties: Default::default(),
-            },
+    fn render(&mut self, _: &mut gpui::ViewContext<Self>) -> AnyElement<PlaygroundView> {
+        Atom::new(
+            AtomStyle::default()
+                .width(100.)
+                .height(100.)
+                .fill(Color::red()),
         )
-        .with_highlights(vec![(17..26, underline), (34..40, underline)])
-        .with_custom_runs(vec![(17..26), (34..40)], move |ix, bounds, scene, _| {
-            scene.push_cursor_region(CursorRegion {
-                bounds,
-                style: CursorStyle::PointingHand,
-            });
-            scene.push_mouse_region(
-                MouseRegion::new::<Self>(view_id, ix, bounds).on_click::<Self, _>(
-                    MouseButton::Left,
-                    move |_, _, _| {
-                        eprintln!("clicked link {ix}");
-                    },
-                ),
-            );
-        })
         .into_any()
     }
 }

crates/gpui/src/app/window.rs 🔗

@@ -912,7 +912,7 @@ impl<'a> WindowContext<'a> {
         let mut new_parents = HashMap::default();
         let mut views_to_notify_if_ancestors_change = HashMap::default();
         rendered_root.layout(
-            SizeConstraint::strict(window_size),
+            SizeConstraint::new(Vector2F::zero(), window_size),
             &mut new_parents,
             &mut views_to_notify_if_ancestors_change,
             refreshing,

crates/gpui/src/elements/cell.rs 🔗

@@ -1,141 +0,0 @@
-use crate::{color::Color, geometry::vector::Vector2F};
-
-struct Cell {}
-
-impl Cell {
-    fn new(style: CellStyle) -> Self {
-        Self {}
-    }
-}
-
-struct Interactive<Style> {
-    default: Style,
-    hovered: Style,
-    active: Style,
-    disabled: Style,
-}
-
-#[derive(Clone, Default)]
-struct CellStyle {
-    axis: Axis,
-    wrap: bool,
-    align: Vector2F,
-    overflow_x: Overflow,
-    overflow_y: Overflow,
-    gap_x: Gap,
-    gap_y: Gap,
-
-    width: Length,
-    height: Length,
-
-    text_color: Option<Color>,
-    font_size: Option<f32>,
-    font_style: Option<FontStyle>,
-    font_weight: Option<FontWeight>,
-
-    opacity: f32,
-    fill: Fill,
-    border: Border,
-    corner_radii: CornerRadii,
-    shadows: Vec<Shadow>,
-}
-
-#[derive(Clone, Default)]
-struct CornerRadii {
-    top_left: f32,
-    top_right: f32,
-    bottom_right: f32,
-    bottom_left: f32,
-}
-
-#[derive(Clone)]
-enum Fill {
-    Color(Color),
-}
-
-impl Default for Fill {
-    fn default() -> Self {
-        Fill::Color(Color::default())
-    }
-}
-
-#[derive(Clone, Default)]
-struct Border {
-    color: Color,
-    width: f32,
-    top: bool,
-    bottom: bool,
-    left: bool,
-    right: bool,
-}
-
-#[derive(Clone, Copy)]
-enum Length {
-    Fixed(f32),
-    Auto(f32),
-}
-
-impl Default for Length {
-    fn default() -> Self {
-        Length::Auto(1.)
-    }
-}
-
-#[derive(Clone, Copy, Default)]
-enum Axis {
-    X,
-    #[default]
-    Y,
-    Z,
-}
-
-#[derive(Clone, Copy, Default)]
-enum Overflow {
-    #[default]
-    Visible,
-    Hidden,
-    Auto,
-}
-
-#[derive(Clone, Copy)]
-enum Gap {
-    Fixed(f32),
-    Around,
-    Between,
-    Even,
-}
-
-impl Default for Gap {
-    fn default() -> Self {
-        Gap::Fixed(0.)
-    }
-}
-
-#[derive(Clone, Copy, Default)]
-struct Shadow {
-    offset: Vector2F,
-    blur: f32,
-    color: Color,
-}
-
-#[derive(Clone, Copy, Default)]
-enum FontStyle {
-    #[default]
-    Normal,
-    Italic,
-    Oblique,
-}
-
-#[derive(Clone, Copy, Default)]
-enum FontWeight {
-    Thin,
-    ExtraLight,
-    Light,
-    #[default]
-    Normal,
-    Medium,
-    Semibold,
-    Bold,
-    ExtraBold,
-    Black,
-}