From 378b2fbd9e8bd9f782a2eb86ba3ff502345cc4d4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 14 Sep 2023 16:52:04 -0600 Subject: [PATCH] WIP --- crates/gpui2/src/style.rs | 2 +- crates/storybook/src/gpui3/color.rs | 178 +++++++++++++++ crates/storybook/src/gpui3/geometry.rs | 8 +- crates/storybook/src/gpui3/mod.rs | 2 + crates/storybook/src/gpui3/style.rs | 302 ++++++++++++++++++++++++- crates/storybook/src/gpui3/taffy.rs | 1 + 6 files changed, 487 insertions(+), 6 deletions(-) create mode 100644 crates/storybook/src/gpui3/color.rs diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index 7cd497e85c4175128cf7a0aa3765e2c3a84f199f..50779f45827e1f6a4f06961e5e59700753a420a6 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -131,7 +131,7 @@ impl Style { color: self.text_color.map(Into::into), font_family: self.font_family.clone(), font_size: self.font_size.map(|size| size * cx.rem_size()), - font_weight: self.font_weight, + font_weight: self.font_weight.map(Into::into), font_style: self.font_style, underline: None, }) diff --git a/crates/storybook/src/gpui3/color.rs b/crates/storybook/src/gpui3/color.rs new file mode 100644 index 0000000000000000000000000000000000000000..698a812e37280f4eb9fe62d87833714c329d2507 --- /dev/null +++ b/crates/storybook/src/gpui3/color.rs @@ -0,0 +1,178 @@ +#![allow(dead_code)] + +use serde::de::{self, Deserialize, Deserializer, Visitor}; +use std::fmt; +use std::num::ParseIntError; + +pub fn rgb>(hex: u32) -> C { + let r = ((hex >> 16) & 0xFF) as f32 / 255.0; + let g = ((hex >> 8) & 0xFF) as f32 / 255.0; + let b = (hex & 0xFF) as f32 / 255.0; + Rgba { r, g, b, a: 1.0 }.into() +} + +#[derive(Clone, Copy, Default, Debug)] +pub struct Rgba { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +struct RgbaVisitor; + +impl<'de> Visitor<'de> for RgbaVisitor { + type Value = Rgba; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string in the format #rrggbb or #rrggbbaa") + } + + fn visit_str(self, value: &str) -> Result { + if value.len() == 7 || value.len() == 9 { + let r = u8::from_str_radix(&value[1..3], 16).unwrap() as f32 / 255.0; + let g = u8::from_str_radix(&value[3..5], 16).unwrap() as f32 / 255.0; + let b = u8::from_str_radix(&value[5..7], 16).unwrap() as f32 / 255.0; + let a = if value.len() == 9 { + u8::from_str_radix(&value[7..9], 16).unwrap() as f32 / 255.0 + } else { + 1.0 + }; + Ok(Rgba { r, g, b, a }) + } else { + Err(E::custom( + "Bad format for RGBA. Expected #rrggbb or #rrggbbaa.", + )) + } + } +} + +impl<'de> Deserialize<'de> for Rgba { + fn deserialize>(deserializer: D) -> Result { + deserializer.deserialize_str(RgbaVisitor) + } +} + +impl From for Rgba { + fn from(color: Hsla) -> Self { + let h = color.h; + let s = color.s; + let l = color.l; + + let c = (1.0 - (2.0 * l - 1.0).abs()) * s; + let x = c * (1.0 - ((h * 6.0) % 2.0 - 1.0).abs()); + let m = l - c / 2.0; + let cm = c + m; + let xm = x + m; + + let (r, g, b) = match (h * 6.0).floor() as i32 { + 0 | 6 => (cm, xm, m), + 1 => (xm, cm, m), + 2 => (m, cm, xm), + 3 => (m, xm, cm), + 4 => (xm, m, cm), + _ => (cm, m, xm), + }; + + Rgba { + r, + g, + b, + a: color.a, + } + } +} + +impl TryFrom<&'_ str> for Rgba { + type Error = ParseIntError; + + fn try_from(value: &'_ str) -> Result { + let r = u8::from_str_radix(&value[1..3], 16)? as f32 / 255.0; + let g = u8::from_str_radix(&value[3..5], 16)? as f32 / 255.0; + let b = u8::from_str_radix(&value[5..7], 16)? as f32 / 255.0; + let a = if value.len() > 7 { + u8::from_str_radix(&value[7..9], 16)? as f32 / 255.0 + } else { + 1.0 + }; + + Ok(Rgba { r, g, b, a }) + } +} + +#[derive(Default, Copy, Clone, Debug, PartialEq)] +pub struct Hsla { + pub h: f32, + pub s: f32, + pub l: f32, + pub a: f32, +} + +pub fn hsla(h: f32, s: f32, l: f32, a: f32) -> Hsla { + Hsla { + h: h.clamp(0., 1.), + s: s.clamp(0., 1.), + l: l.clamp(0., 1.), + a: a.clamp(0., 1.), + } +} + +pub fn black() -> Hsla { + Hsla { + h: 0., + s: 0., + l: 0., + a: 1., + } +} + +impl From for Hsla { + fn from(color: Rgba) -> Self { + let r = color.r; + let g = color.g; + let b = color.b; + + let max = r.max(g.max(b)); + let min = r.min(g.min(b)); + let delta = max - min; + + let l = (max + min) / 2.0; + let s = if l == 0.0 || l == 1.0 { + 0.0 + } else if l < 0.5 { + delta / (2.0 * l) + } else { + delta / (2.0 - 2.0 * l) + }; + + let h = if delta == 0.0 { + 0.0 + } else if max == r { + ((g - b) / delta).rem_euclid(6.0) / 6.0 + } else if max == g { + ((b - r) / delta + 2.0) / 6.0 + } else { + ((r - g) / delta + 4.0) / 6.0 + }; + + Hsla { + h, + s, + l, + a: color.a, + } + } +} + +impl<'de> Deserialize<'de> for Hsla { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + // First, deserialize it into Rgba + let rgba = Rgba::deserialize(deserializer)?; + + // Then, use the From for Hsla implementation to convert it + Ok(Hsla::from(rgba)) + } +} diff --git a/crates/storybook/src/gpui3/geometry.rs b/crates/storybook/src/gpui3/geometry.rs index 2a969d8b7152ead66216d864594329e878806c8e..bee76c2a95390dc01211f731ffd2b6f74acb8b7a 100644 --- a/crates/storybook/src/gpui3/geometry.rs +++ b/crates/storybook/src/gpui3/geometry.rs @@ -3,8 +3,8 @@ use derive_more::{Add, AddAssign, Div, Mul, Sub}; use refineable::Refineable; use std::ops::Mul; -#[derive(Default, Add, AddAssign, Sub, Mul, Div, Copy, Debug, PartialEq, Eq, Hash)] -pub struct Point { +#[derive(Refineable, Default, Add, AddAssign, Sub, Mul, Div, Copy, Debug, PartialEq, Eq, Hash)] +pub struct Point { pub x: T, pub y: T, } @@ -51,13 +51,13 @@ impl Size { } } -#[derive(Clone, Default, Debug)] +#[derive(Refineable, Clone, Default, Debug)] pub struct Bounds { pub origin: Point, pub size: Size, } -#[derive(Clone, Default, Refineable, Debug)] +#[derive(Refineable, Clone, Default, Debug)] pub struct Edges { pub top: T, pub right: T, diff --git a/crates/storybook/src/gpui3/mod.rs b/crates/storybook/src/gpui3/mod.rs index 7349ed1e05a9785738de064b07a41828b937a4b2..02321fc079fb3e4af29a63067401bf58765dd5d5 100644 --- a/crates/storybook/src/gpui3/mod.rs +++ b/crates/storybook/src/gpui3/mod.rs @@ -1,4 +1,5 @@ mod app; +mod color; mod element; mod elements; mod geometry; @@ -11,6 +12,7 @@ pub use gpui2::ArcCow; use gpui2::Reference; pub use app::*; +pub use color::*; pub use element::*; pub use elements::*; pub use geometry::*; diff --git a/crates/storybook/src/gpui3/style.rs b/crates/storybook/src/gpui3/style.rs index b819b33e9934872c7a266ca2effe9cfbd2fbcac5..f36f8b5c65907b0bf146804e593d9681090cc661 100644 --- a/crates/storybook/src/gpui3/style.rs +++ b/crates/storybook/src/gpui3/style.rs @@ -1 +1,301 @@ -pub struct Style; +use gpui2::fonts::TextStyleRefinement; +use refineable::Refineable; +use std::sync::Arc; + +pub use super::taffy::style::{ + AlignContent, AlignItems, AlignSelf, Display, FlexDirection, FlexWrap, JustifyContent, + Overflow, Position, +}; +use super::{ + AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Hsla, Length, Point, PointRefinement, + SharedString, Size, SizeRefinement, WindowContext, +}; +pub use gpui2::style::{FontStyle, FontWeight}; + +#[derive(Clone, Debug)] +pub struct FontSize(f32); + +#[derive(Clone, Refineable, Debug)] +#[refineable(debug)] +pub struct Style { + /// What layout strategy should be used? + pub display: Display, + + // Overflow properties + /// How children overflowing their container should affect layout + #[refineable] + pub overflow: Point, + /// How much space (in points) should be reserved for the scrollbars of `Overflow::Scroll` and `Overflow::Auto` nodes. + pub scrollbar_width: f32, + + // Position properties + /// What should the `position` value of this struct use as a base offset? + pub position: Position, + /// How should the position of this element be tweaked relative to the layout defined? + #[refineable] + pub inset: Edges, + + // Size properies + /// Sets the initial size of the item + #[refineable] + pub size: Size, + /// Controls the minimum size of the item + #[refineable] + pub min_size: Size, + /// Controls the maximum size of the item + #[refineable] + pub max_size: Size, + /// Sets the preferred aspect ratio for the item. The ratio is calculated as width divided by height. + pub aspect_ratio: Option, + + // Spacing Properties + /// How large should the margin be on each side? + #[refineable] + pub margin: Edges, + /// How large should the padding be on each side? + #[refineable] + pub padding: Edges, + /// How large should the border be on each side? + #[refineable] + pub border_widths: Edges, + + // Alignment properties + /// How this node's children aligned in the cross/block axis? + pub align_items: Option, + /// How this node should be aligned in the cross/block axis. Falls back to the parents [`AlignItems`] if not set + pub align_self: Option, + /// How should content contained within this item be aligned in the cross/block axis + pub align_content: Option, + /// How should contained within this item be aligned in the main/inline axis + pub justify_content: Option, + /// How large should the gaps between items in a flex container be? + #[refineable] + pub gap: Size, + + // Flexbox properies + /// Which direction does the main axis flow in? + pub flex_direction: FlexDirection, + /// Should elements wrap, or stay in a single line? + pub flex_wrap: FlexWrap, + /// Sets the initial main axis size of the item + pub flex_basis: Length, + /// The relative rate at which this item grows when it is expanding to fill space, 0.0 is the default value, and this value must be positive. + pub flex_grow: f32, + /// The relative rate at which this item shrinks when it is contracting to fit into space, 1.0 is the default value, and this value must be positive. + pub flex_shrink: f32, + + /// The fill color of this element + pub fill: Option, + + /// The border color of this element + pub border_color: Option, + + /// The radius of the corners of this element + #[refineable] + pub corner_radii: CornerRadii, + + /// The color of text within this element. Cascades to children unless overridden. + pub text_color: Option, + + /// The font size in rems. + pub font_size: Option, + + pub font_family: Option>, + + pub font_weight: Option, + + pub font_style: Option, +} + +#[derive(Clone, Debug)] +pub struct TextStyle { + pub color: Color, + pub font_family_name: SharedString, + pub font_size: FontSize, + pub underline: Underline, + pub soft_wrap: bool, +} + +#[derive(Clone, Default, Debug)] +pub struct Underline { + pub origin: Vector2F, + pub width: f32, + pub thickness: f32, + pub color: Color, + pub squiggly: bool, +} + +impl Style { + pub fn text_style(&self, cx: &WindowContext) -> Option { + if self.text_color.is_none() + && self.font_size.is_none() + && self.font_family.is_none() + && self.font_weight.is_none() + && self.font_style.is_none() + { + return None; + } + + Some(TextStyleRefinement { + color: self.text_color.map(Into::into), + font_family: self.font_family.clone(), + font_size: self.font_size.map(|size| size * cx.rem_size()), + font_weight: self.font_weight.map(Into::into), + font_style: self.font_style, + underline: None, + }) + } + + pub fn to_taffy(&self, rem_size: f32) -> taffy::style::Style { + taffy::style::Style { + display: self.display, + overflow: self.overflow.clone().into(), + scrollbar_width: self.scrollbar_width, + position: self.position, + inset: self.inset.to_taffy(rem_size), + size: self.size.to_taffy(rem_size), + min_size: self.min_size.to_taffy(rem_size), + max_size: self.max_size.to_taffy(rem_size), + aspect_ratio: self.aspect_ratio, + margin: self.margin.to_taffy(rem_size), + padding: self.padding.to_taffy(rem_size), + border: self.border_widths.to_taffy(rem_size), + align_items: self.align_items, + align_self: self.align_self, + align_content: self.align_content, + justify_content: self.justify_content, + gap: self.gap.to_taffy(rem_size), + flex_direction: self.flex_direction, + flex_wrap: self.flex_wrap, + flex_basis: self.flex_basis.to_taffy(rem_size).into(), + flex_grow: self.flex_grow, + flex_shrink: self.flex_shrink, + ..Default::default() // Ignore grid properties for now + } + } + + /// Paints the background of an element styled with this style. + pub fn paint_background(&self, bounds: RectF, cx: &mut ViewContext) { + let rem_size = cx.rem_size(); + if let Some(color) = self.fill.as_ref().and_then(Fill::color) { + cx.scene().push_quad(gpui::Quad { + bounds, + background: Some(color.into()), + corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size), + border: Default::default(), + }); + } + } + + /// Paints the foreground of an element styled with this style. + pub fn paint_foreground(&self, bounds: RectF, cx: &mut ViewContext) { + let rem_size = cx.rem_size(); + + if let Some(color) = self.border_color { + let border = self.border_widths.to_pixels(rem_size); + if !border.is_empty() { + cx.scene().push_quad(gpui::Quad { + bounds, + background: None, + corner_radii: self.corner_radii.to_gpui(bounds.size(), rem_size), + border: scene::Border { + color: color.into(), + top: border.top, + right: border.right, + bottom: border.bottom, + left: border.left, + }, + }); + } + } + } +} + +impl Default for Style { + fn default() -> Self { + Style { + display: Display::Block, + overflow: Point { + x: Overflow::Visible, + y: Overflow::Visible, + }, + scrollbar_width: 0.0, + position: Position::Relative, + inset: Edges::auto(), + margin: Edges::::zero(), + padding: Edges::::zero(), + border_widths: Edges::::zero(), + size: Size::auto(), + min_size: Size::auto(), + max_size: Size::auto(), + aspect_ratio: None, + gap: Size::zero(), + // Aligment + align_items: None, + align_self: None, + align_content: None, + justify_content: None, + // Flexbox + flex_direction: FlexDirection::Row, + flex_wrap: FlexWrap::NoWrap, + flex_grow: 0.0, + flex_shrink: 1.0, + flex_basis: Length::Auto, + fill: None, + border_color: None, + corner_radii: CornerRadii::default(), + text_color: None, + font_size: Some(1.), + font_family: None, + font_weight: None, + font_style: None, + } + } +} + +#[derive(Clone, Debug)] +pub enum Fill { + Color(Hsla), +} + +impl Fill { + pub fn color(&self) -> Option { + match self { + Fill::Color(color) => Some(*color), + } + } +} + +impl Default for Fill { + fn default() -> Self { + Self::Color(Hsla::default()) + } +} + +impl From for Fill { + fn from(color: Hsla) -> Self { + Self::Color(color) + } +} + +#[derive(Clone, Refineable, Default, Debug)] +#[refineable(debug)] +pub struct CornerRadii { + top_left: AbsoluteLength, + top_right: AbsoluteLength, + bottom_left: AbsoluteLength, + bottom_right: AbsoluteLength, +} + +impl CornerRadii { + pub fn to_gpui(&self, box_size: Vector2F, rem_size: f32) -> gpui::scene::CornerRadii { + let max_radius = box_size.x().min(box_size.y()) / 2.; + + gpui::scene::CornerRadii { + top_left: self.top_left.to_pixels(rem_size).min(max_radius), + top_right: self.top_right.to_pixels(rem_size).min(max_radius), + bottom_left: self.bottom_left.to_pixels(rem_size).min(max_radius), + bottom_right: self.bottom_right.to_pixels(rem_size).min(max_radius), + } + } +} diff --git a/crates/storybook/src/gpui3/taffy.rs b/crates/storybook/src/gpui3/taffy.rs index 1efb4e694665344de14374cfa758211092d1c51e..e52f6497939b1e28e77a0e06b0f6d9a527db445e 100644 --- a/crates/storybook/src/gpui3/taffy.rs +++ b/crates/storybook/src/gpui3/taffy.rs @@ -4,6 +4,7 @@ use super::{ }; use gpui2::taffy::{self, Taffy}; use std::fmt::Debug; +pub use taffy::*; pub use gpui2::taffy::tree::NodeId as LayoutId; pub struct TaffyLayoutEngine(Taffy);