diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 743b8083541cbeffed718b6e51fe422ceee9ba98..9a6eb1116c1bcec556a35f875a9e0f046dbed54b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -32,8 +32,8 @@ use gpui::{ json::{self, ToJson}, platform::{CursorStyle, Modifiers, MouseButton, MouseButtonEvent, MouseMovedEvent}, text_layout::{self, Line, RunStyle, TextLayoutCache}, - AnyElement, Axis, Border, CursorRegion, Element, EventContext, FontCache, LayoutContext, - MouseRegion, PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext, + AnyElement, Axis, CursorRegion, Element, EventContext, FontCache, LayoutContext, MouseRegion, + PaintContext, Quad, SceneBuilder, SizeConstraint, ViewContext, WindowContext, }; use itertools::Itertools; use json::json; @@ -539,13 +539,13 @@ impl EditorElement { scene.push_quad(Quad { bounds: gutter_bounds, background: Some(self.style.gutter_background), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: Default::default(), }); scene.push_quad(Quad { bounds: text_bounds, background: Some(self.style.background), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: Default::default(), }); @@ -573,7 +573,7 @@ impl EditorElement { scene.push_quad(Quad { bounds: RectF::new(origin, size), background: Some(self.style.active_line_background), - border: Border::default(), + border: Border::default().into(), corner_radii: Default::default(), }); } @@ -593,7 +593,7 @@ impl EditorElement { scene.push_quad(Quad { bounds: RectF::new(origin, size), background: Some(self.style.highlighted_line_background), - border: Border::default(), + border: Border::default().into(), corner_radii: Default::default(), }); } @@ -623,7 +623,7 @@ impl EditorElement { vec2f(1., text_bounds.height()), ), background: Some(color), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: Default::default(), }); } @@ -724,7 +724,7 @@ impl EditorElement { scene.push_quad(Quad { bounds: highlight_bounds, background: Some(diff_style.modified), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: (1. * line_height).into(), }); @@ -757,7 +757,7 @@ impl EditorElement { scene.push_quad(Quad { bounds: highlight_bounds, background: Some(diff_style.deleted), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: (1. * line_height).into(), }); @@ -779,7 +779,7 @@ impl EditorElement { scene.push_quad(Quad { bounds: highlight_bounds, background: Some(color), - border: Border::new(0., Color::transparent_black()), + border: Border::new(0., Color::transparent_black()).into(), corner_radii: (diff_style.corner_radius * line_height).into(), }); } @@ -1149,7 +1149,7 @@ impl EditorElement { if layout.show_scrollbars { scene.push_quad(Quad { bounds: track_bounds, - border: style.track.border, + border: style.track.border.into(), background: style.track.background_color, ..Default::default() }); @@ -1180,7 +1180,7 @@ impl EditorElement { scene.push_quad(Quad { bounds, background: Some(color), - border, + border: border.into(), corner_radii: style.thumb.corner_radii.into(), }) }; @@ -1240,7 +1240,7 @@ impl EditorElement { scene.push_quad(Quad { bounds, background: Some(color), - border, + border: border.into(), corner_radii: style.thumb.corner_radii.into(), }) } @@ -1248,7 +1248,7 @@ impl EditorElement { scene.push_quad(Quad { bounds: thumb_bounds, - border: style.thumb.border, + border: style.thumb.border.into(), background: style.thumb.background_color, corner_radii: style.thumb.corner_radii.into(), }); @@ -2891,7 +2891,7 @@ impl Cursor { scene.push_quad(Quad { bounds, background: None, - border: Border::all(1., self.color), + border: Border::all(1., self.color).into(), corner_radii: Default::default(), }); } else { diff --git a/crates/gpui/src/elements/container.rs b/crates/gpui/src/elements/container.rs index 9a590f3a5a5bedae21273011f237a2cce0c9eea3..82bf260fe15dfb8e29c7799a87f8a76d6f1ef6d3 100644 --- a/crates/gpui/src/elements/container.rs +++ b/crates/gpui/src/elements/container.rs @@ -9,7 +9,7 @@ use crate::{ }, json::ToJson, platform::CursorStyle, - scene::{self, Border, CornerRadii, CursorRegion, Quad}, + scene::{self, CornerRadii, CursorRegion, Quad}, AnyElement, Element, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext, }; use schemars::JsonSchema; @@ -206,6 +206,163 @@ impl Container { } } +#[derive(Copy, Clone, Debug, Default, JsonSchema)] +pub struct Border { + pub color: Color, + pub width: f32, + pub overlay: bool, + pub top: bool, + pub bottom: bool, + pub left: bool, + pub right: bool, +} + +impl Into for Border { + fn into(self) -> scene::Border { + scene::Border { + color: self.color, + left: if self.left { self.width } else { 0.0 }, + right: if self.right { self.width } else { 0.0 }, + top: if self.top { self.width } else { 0.0 }, + bottom: if self.bottom { self.width } else { 0.0 }, + } + } +} + +impl Border { + pub fn new(width: f32, color: Color) -> Self { + Self { + width, + color, + overlay: false, + top: false, + left: false, + bottom: false, + right: false, + } + } + + pub fn all(width: f32, color: Color) -> Self { + Self { + width, + color, + overlay: false, + top: true, + left: true, + bottom: true, + right: true, + } + } + + pub fn top(width: f32, color: Color) -> Self { + let mut border = Self::new(width, color); + border.top = true; + border + } + + pub fn left(width: f32, color: Color) -> Self { + let mut border = Self::new(width, color); + border.left = true; + border + } + + pub fn bottom(width: f32, color: Color) -> Self { + let mut border = Self::new(width, color); + border.bottom = true; + border + } + + pub fn right(width: f32, color: Color) -> Self { + let mut border = Self::new(width, color); + border.right = true; + border + } + + pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self { + self.top = top; + self.left = left; + self.bottom = bottom; + self.right = right; + self + } + + pub fn top_width(&self) -> f32 { + if self.top { + self.width + } else { + 0.0 + } + } + + pub fn left_width(&self) -> f32 { + if self.left { + self.width + } else { + 0.0 + } + } +} + +impl<'de> Deserialize<'de> for Border { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + struct BorderData { + pub width: f32, + pub color: Color, + #[serde(default)] + pub overlay: bool, + #[serde(default)] + pub top: bool, + #[serde(default)] + pub right: bool, + #[serde(default)] + pub bottom: bool, + #[serde(default)] + pub left: bool, + } + + let data = BorderData::deserialize(deserializer)?; + let mut border = Border { + width: data.width, + color: data.color, + overlay: data.overlay, + top: data.top, + bottom: data.bottom, + left: data.left, + right: data.right, + }; + if !border.top && !border.bottom && !border.left && !border.right { + border.top = true; + border.bottom = true; + border.left = true; + border.right = true; + } + Ok(border) + } +} + +impl ToJson for Border { + fn to_json(&self) -> serde_json::Value { + let mut value = json!({}); + if self.top { + value["top"] = json!(self.width); + } + if self.right { + value["right"] = json!(self.width); + } + if self.bottom { + value["bottom"] = json!(self.width); + } + if self.left { + value["left"] = json!(self.width); + } + value + } +} + impl Element for Container { type LayoutState = (); type PaintState = (); @@ -278,7 +435,7 @@ impl Element for Container { scene.push_quad(Quad { bounds: quad_bounds, background: self.style.overlay_color, - border: self.style.border, + border: self.style.border.into(), corner_radii: self.style.corner_radii.into(), }); scene.pop_layer(); @@ -286,7 +443,7 @@ impl Element for Container { scene.push_quad(Quad { bounds: quad_bounds, background: self.style.background_color, - border: self.style.border, + border: self.style.border.into(), corner_radii: self.style.corner_radii.into(), }); diff --git a/crates/gpui/src/elements/image.rs b/crates/gpui/src/elements/image.rs index 6723227efe4171bf5ccb8625c7f5fd3d4988395d..aabf5469371ba17f9e34784cfbff5c3c92eb8d96 100644 --- a/crates/gpui/src/elements/image.rs +++ b/crates/gpui/src/elements/image.rs @@ -1,11 +1,11 @@ -use super::constrain_size_preserving_aspect_ratio; +use super::{constrain_size_preserving_aspect_ratio, Border}; use crate::{ geometry::{ rect::RectF, vector::{vec2f, Vector2F}, }, json::{json, ToJson}, - scene, Border, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, + scene, Element, ImageData, LayoutContext, PaintContext, SceneBuilder, SizeConstraint, ViewContext, }; use schemars::JsonSchema; @@ -102,7 +102,7 @@ impl Element for Image { if let Some(data) = layout { scene.push_image(scene::Image { bounds, - border: self.style.border, + border: self.style.border.into(), corner_radii: self.style.corner_radius.into(), grayscale: self.style.grayscale, data: data.clone(), diff --git a/crates/gpui/src/geometry.rs b/crates/gpui/src/geometry.rs index c6312b18c440bd7caf20f1396481c01a0cccc9c9..b05ec63f9714534c3b6bacc0c6b9421cc3e6cfac 100644 --- a/crates/gpui/src/geometry.rs +++ b/crates/gpui/src/geometry.rs @@ -230,7 +230,16 @@ pub struct Edges { pub left: T, } -impl Edges { +impl Edges { + pub fn auto() -> Self { + Self { + top: Length::Auto, + right: Length::Auto, + bottom: Length::Auto, + left: Length::Auto, + } + } + pub fn zero() -> Self { Self { top: pixels(0.), @@ -240,7 +249,10 @@ impl Edges { } } - pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect { + pub fn to_taffy( + &self, + rem_size: f32, + ) -> taffy::geometry::Rect { taffy::geometry::Rect { top: self.top.to_taffy(rem_size), right: self.right.to_taffy(rem_size), @@ -250,16 +262,27 @@ impl Edges { } } -impl Edges { - pub fn auto() -> Self { +impl Edges { + pub fn zero() -> Self { Self { - top: Length::Auto, - right: Length::Auto, - bottom: Length::Auto, - left: Length::Auto, + top: pixels(0.), + right: pixels(0.), + bottom: pixels(0.), + left: pixels(0.), } } + pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect { + taffy::geometry::Rect { + top: self.top.to_taffy(rem_size), + right: self.right.to_taffy(rem_size), + bottom: self.bottom.to_taffy(rem_size), + left: self.left.to_taffy(rem_size), + } + } +} + +impl Edges { pub fn zero() -> Self { Self { top: pixels(0.), @@ -269,10 +292,7 @@ impl Edges { } } - pub fn to_taffy( - &self, - rem_size: f32, - ) -> taffy::geometry::Rect { + pub fn to_taffy(&self, rem_size: f32) -> taffy::geometry::Rect { taffy::geometry::Rect { top: self.top.to_taffy(rem_size), right: self.right.to_taffy(rem_size), @@ -280,6 +300,21 @@ impl Edges { left: self.left.to_taffy(rem_size), } } + + pub fn to_pixels(&self, rem_size: f32) -> Edges { + Edges { + top: self.top.to_pixels(rem_size), + right: self.right.to_pixels(rem_size), + bottom: self.bottom.to_pixels(rem_size), + left: self.left.to_pixels(rem_size), + } + } +} + +impl Edges { + pub fn is_empty(&self) -> bool { + self.top == 0.0 && self.right == 0.0 && self.bottom == 0.0 && self.left == 0.0 + } } #[derive(Clone, Copy)] @@ -295,6 +330,13 @@ impl AbsoluteLength { AbsoluteLength::Rems(rems) => rems * rem_size, } } + + pub fn to_taffy(&self, rem_size: f32) -> taffy::style::LengthPercentage { + match self { + AbsoluteLength::Pixels(pixels) => taffy::style::LengthPercentage::Length(*pixels), + AbsoluteLength::Rems(rems) => taffy::style::LengthPercentage::Length(rems * rem_size), + } + } } impl Default for AbsoluteLength { diff --git a/crates/gpui/src/platform/mac/renderer.rs b/crates/gpui/src/platform/mac/renderer.rs index 5fe9e34ee484ba1b4285e430eeb6f519cbce7b9c..55ec3e9e9a2f3f72c03103adf045721105d3da83 100644 --- a/crates/gpui/src/platform/mac/renderer.rs +++ b/crates/gpui/src/platform/mac/renderer.rs @@ -577,7 +577,6 @@ impl Renderer { }; for (ix, quad) in quads.iter().enumerate() { let bounds = quad.bounds * scale_factor; - let border_width = quad.border.width * scale_factor; let shader_quad = shaders::GPUIQuad { origin: bounds.origin().round().to_float2(), size: bounds.size().round().to_float2(), @@ -585,10 +584,10 @@ impl Renderer { .background .unwrap_or_else(Color::transparent_black) .to_uchar4(), - border_top: border_width * (quad.border.top as usize as f32), - border_right: border_width * (quad.border.right as usize as f32), - border_bottom: border_width * (quad.border.bottom as usize as f32), - border_left: border_width * (quad.border.left as usize as f32), + border_top: quad.border.top * scale_factor, + border_right: quad.border.right * scale_factor, + border_bottom: quad.border.bottom * scale_factor, + border_left: quad.border.left * scale_factor, border_color: quad.border.color.to_uchar4(), corner_radius_top_left: quad.corner_radii.top_left * scale_factor, corner_radius_top_right: quad.corner_radii.top_right * scale_factor, @@ -746,7 +745,6 @@ impl Renderer { let origin = image.bounds.origin() * scale_factor; let target_size = image.bounds.size() * scale_factor; let corner_radii = image.corner_radii * scale_factor; - let border_width = image.border.width * scale_factor; let (alloc_id, atlas_bounds) = self.image_cache.render(&image.data); images_by_atlas .entry(alloc_id.atlas_id) @@ -756,10 +754,10 @@ impl Renderer { target_size: target_size.to_float2(), source_size: atlas_bounds.size().to_float2(), atlas_origin: atlas_bounds.origin().to_float2(), - border_top: border_width * (image.border.top as usize as f32), - border_right: border_width * (image.border.right as usize as f32), - border_bottom: border_width * (image.border.bottom as usize as f32), - border_left: border_width * (image.border.left as usize as f32), + border_top: image.border.top * scale_factor, + border_right: image.border.right * scale_factor, + border_bottom: image.border.bottom * scale_factor, + border_left: image.border.left * scale_factor, border_color: image.border.color.to_uchar4(), corner_radius_top_left: corner_radii.top_left, corner_radius_top_right: corner_radii.top_right, diff --git a/crates/gpui/src/scene.rs b/crates/gpui/src/scene.rs index 3beb60a9fe4427996b5d15533695441128bcd453..e8b9936d8109c6c3acf22d156e8f360034218f16 100644 --- a/crates/gpui/src/scene.rs +++ b/crates/gpui/src/scene.rs @@ -8,7 +8,6 @@ use derive_more::Mul; use schemars::JsonSchema; use serde::Deserialize; use serde_derive::Serialize; -use serde_json::json; use std::{ any::{Any, TypeId}, borrow::Cow, @@ -20,7 +19,6 @@ use crate::{ color::Color, fonts::{FontId, GlyphId}, geometry::{rect::RectF, vector::Vector2F}, - json::ToJson, platform::{current::Surface, CursorStyle}, ImageData, WindowContext, }; @@ -171,15 +169,13 @@ pub struct Icon { pub color: Color, } -#[derive(Clone, Copy, Default, Debug, JsonSchema)] +#[derive(Clone, Copy, Default, Debug)] pub struct Border { - pub width: f32, pub color: Color, - pub overlay: bool, - pub top: bool, - pub right: bool, - pub bottom: bool, - pub left: bool, + pub top: f32, + pub right: f32, + pub bottom: f32, + pub left: f32, } #[derive(Clone, Copy, Default, Debug)] @@ -191,47 +187,6 @@ pub struct Underline { pub squiggly: bool, } -impl<'de> Deserialize<'de> for Border { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - #[derive(Deserialize)] - struct BorderData { - pub width: f32, - pub color: Color, - #[serde(default)] - pub overlay: bool, - #[serde(default)] - pub top: bool, - #[serde(default)] - pub right: bool, - #[serde(default)] - pub bottom: bool, - #[serde(default)] - pub left: bool, - } - - let data = BorderData::deserialize(deserializer)?; - let mut border = Border { - width: data.width, - color: data.color, - overlay: data.overlay, - top: data.top, - bottom: data.bottom, - left: data.left, - right: data.right, - }; - if !border.top && !border.bottom && !border.left && !border.right { - border.top = true; - border.bottom = true; - border.left = true; - border.right = true; - } - Ok(border) - } -} - #[derive(Debug)] pub struct Path { pub bounds: RectF, @@ -606,99 +561,6 @@ impl Layer { } } -impl Border { - pub fn new(width: f32, color: Color) -> Self { - Self { - width, - color, - overlay: false, - top: false, - left: false, - bottom: false, - right: false, - } - } - - pub fn all(width: f32, color: Color) -> Self { - Self { - width, - color, - overlay: false, - top: true, - left: true, - bottom: true, - right: true, - } - } - - pub fn top(width: f32, color: Color) -> Self { - let mut border = Self::new(width, color); - border.top = true; - border - } - - pub fn left(width: f32, color: Color) -> Self { - let mut border = Self::new(width, color); - border.left = true; - border - } - - pub fn bottom(width: f32, color: Color) -> Self { - let mut border = Self::new(width, color); - border.bottom = true; - border - } - - pub fn right(width: f32, color: Color) -> Self { - let mut border = Self::new(width, color); - border.right = true; - border - } - - pub fn with_sides(mut self, top: bool, left: bool, bottom: bool, right: bool) -> Self { - self.top = top; - self.left = left; - self.bottom = bottom; - self.right = right; - self - } - - pub fn top_width(&self) -> f32 { - if self.top { - self.width - } else { - 0.0 - } - } - - pub fn left_width(&self) -> f32 { - if self.left { - self.width - } else { - 0.0 - } - } -} - -impl ToJson for Border { - fn to_json(&self) -> serde_json::Value { - let mut value = json!({}); - if self.top { - value["top"] = json!(self.width); - } - if self.right { - value["right"] = json!(self.width); - } - if self.bottom { - value["bottom"] = json!(self.width); - } - if self.left { - value["left"] = json!(self.width); - } - value - } -} - impl MouseRegion { pub fn id(&self) -> MouseRegionId { self.id diff --git a/crates/gpui2/src/style.rs b/crates/gpui2/src/style.rs index a0797fcb2a2a292ab06623cd1dbb4bf48b16702d..90b8cdf49b02356e4f31a7c311733106835f58d1 100644 --- a/crates/gpui2/src/style.rs +++ b/crates/gpui2/src/style.rs @@ -16,7 +16,7 @@ use gpui::{ rect::RectF, relative, AbsoluteLength, DefiniteLength, Edges, EdgesRefinement, Length, Point, PointRefinement, Size, SizeRefinement, }, - taffy, WindowContext, + scene, taffy, WindowContext, }; use gpui2_macros::styleable_helpers; use refineable::{Refineable, RefinementCascade}; @@ -63,7 +63,7 @@ pub struct Style { pub padding: Edges, /// How large should the border be on each side? #[refineable] - pub border: Edges, + pub border_widths: Edges, // Alignment properties /// How this node's children aligned in the cross/block axis? @@ -92,6 +92,10 @@ pub struct Style { /// 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, @@ -143,7 +147,7 @@ impl Style { aspect_ratio: self.aspect_ratio, margin: self.margin.to_taffy(rem_size), padding: self.padding.to_taffy(rem_size), - border: self.border.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, @@ -159,7 +163,6 @@ impl Style { } /// Paints the background of an element styled with this style. - /// Return the bounds in which to paint the content. pub fn paint_background(&self, bounds: RectF, cx: &mut PaintContext) { let rem_size = cx.rem_size(); if let Some(color) = self.fill.as_ref().and_then(Fill::color) { @@ -171,6 +174,29 @@ impl Style { }); } } + + /// Paints the foreground of an element styled with this style. + pub fn paint_foreground(&self, bounds: RectF, cx: &mut PaintContext) { + 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(rem_size), + border: scene::Border { + color: color.into(), + top: border.top, + right: border.right, + bottom: border.bottom, + left: border.left, + }, + }); + } + } + } } impl Default for Style { @@ -186,7 +212,7 @@ impl Default for Style { inset: Edges::auto(), margin: Edges::::zero(), padding: Edges::::zero(), - border: Edges::::zero(), + border_widths: Edges::::zero(), size: Size::auto(), min_size: Size::auto(), max_size: Size::auto(), @@ -204,6 +230,7 @@ impl Default for Style { flex_shrink: 1.0, flex_basis: Length::Auto, fill: None, + border_color: None, corner_radii: CornerRadii::default(), text_color: None, font_size: Some(1.), diff --git a/crates/theme/src/theme.rs b/crates/theme/src/theme.rs index 5699f8df40de74ad91903cf00b34fcf1f7f47d7f..8edd795ffc4aa4f1a4aac5e554108b2041e32061 100644 --- a/crates/theme/src/theme.rs +++ b/crates/theme/src/theme.rs @@ -6,9 +6,9 @@ pub mod ui; use components::{action_button::ButtonStyle, disclosure::DisclosureStyle, ToggleIconButtonStyle}; use gpui::{ color::Color, - elements::{ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle}, + elements::{Border, ContainerStyle, ImageStyle, LabelStyle, Shadow, SvgStyle, TooltipStyle}, fonts::{HighlightStyle, TextStyle}, - platform, AppContext, AssetSource, Border, MouseState, + platform, AppContext, AssetSource, MouseState, }; use parking_lot::Mutex; use schemars::JsonSchema; diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 93fb484214fc181d4636845cf1e00a19760b2fb3..aa6ca143dfc4b982f8d458e19112edfe47c13387 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -9,7 +9,7 @@ use gpui::{ elements::*, geometry::{rect::RectF, vector::Vector2F}, platform::{CursorStyle, MouseButton}, - AnyViewHandle, Axis, Border, ModelHandle, ViewContext, ViewHandle, + AnyViewHandle, Axis, ModelHandle, ViewContext, ViewHandle, }; use project::Project; use serde::Deserialize;