@@ -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,
+}
@@ -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()
}
}
@@ -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,
-}