Use custom color wrapper type everywhere in gpui & zed

Max Brunsfeld and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

gpui/examples/text.rs             |  14 ++--
gpui/src/color.rs                 |  75 ++++++++++++++++++++++
gpui/src/elements/container.rs    |  29 +++++---
gpui/src/elements/label.rs        |  50 ++++++++-------
gpui/src/elements/svg.rs          |   8 +-
gpui/src/fonts.rs                 |  71 ++++++++++++++++++++-
gpui/src/geometry.rs              |  13 +++
gpui/src/platform.rs              |   4 
gpui/src/platform/mac/fonts.rs    |  12 +-
gpui/src/platform/mac/renderer.rs |  18 ++---
gpui/src/scene.rs                 |  36 +++++-----
gpui/src/text_layout.rs           |  16 ++--
zed/src/editor.rs                 |  10 +-
zed/src/editor/display_map.rs     |  10 +-
zed/src/editor/element.rs         |  22 +++---
zed/src/file_finder.rs            |  16 ++--
zed/src/settings.rs               | 108 ++++++++------------------------
zed/src/theme_selector.rs         |  16 ++--
zed/src/workspace/pane.rs         |  14 ++--
zed/src/workspace/pane_group.rs   |  10 --
20 files changed, 319 insertions(+), 233 deletions(-)

Detailed changes

gpui/examples/text.rs 🔗

@@ -1,5 +1,5 @@
 use gpui::{
-    color::ColorU,
+    color::Color,
     fonts::{Properties, Weight},
     DebugContext, Element as _, Quad,
 };
@@ -82,17 +82,17 @@ impl gpui::Element for TextElement {
             text,
             font_size,
             &[
-                (1, normal, ColorU::default()),
-                (1, bold, ColorU::default()),
-                (1, normal, ColorU::default()),
-                (1, bold, ColorU::default()),
-                (text.len() - 4, normal, ColorU::default()),
+                (1, normal, Color::default()),
+                (1, bold, Color::default()),
+                (1, normal, Color::default()),
+                (1, bold, Color::default()),
+                (text.len() - 4, normal, Color::default()),
             ],
         );
 
         cx.scene.push_quad(Quad {
             bounds: bounds,
-            background: Some(ColorU::white()),
+            background: Some(Color::white()),
             ..Default::default()
         });
         line.paint(bounds.origin(), bounds, cx);

gpui/src/color.rs 🔗

@@ -1,9 +1,78 @@
+use std::{
+    fmt,
+    ops::{Deref, DerefMut},
+};
+
 use crate::json::ToJson;
-pub use pathfinder_color::*;
+use pathfinder_color::ColorU;
+use serde::{Deserialize, Deserializer};
 use serde_json::json;
 
-impl ToJson for ColorU {
+#[derive(Clone, Copy, Default, PartialEq, Eq, Hash)]
+#[repr(transparent)]
+pub struct Color(ColorU);
+
+impl Color {
+    pub fn transparent_black() -> Self {
+        Self(ColorU::transparent_black())
+    }
+
+    pub fn black() -> Self {
+        Self(ColorU::black())
+    }
+
+    pub fn white() -> Self {
+        Self(ColorU::white())
+    }
+
+    pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
+        Self(ColorU::new(r, g, b, a))
+    }
+
+    pub fn from_u32(rgba: u32) -> Self {
+        Self(ColorU::from_u32(rgba))
+    }
+}
+
+impl<'de> Deserialize<'de> for Color {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        let mut rgba = u32::deserialize(deserializer)?;
+
+        if rgba <= 0xFFFFFF {
+            rgba = (rgba << 8) + 0xFF;
+        }
+
+        Ok(Self::from_u32(rgba))
+    }
+}
+
+impl ToJson for Color {
     fn to_json(&self) -> serde_json::Value {
-        json!(format!("0x{:x}{:x}{:x}", self.r, self.g, self.b))
+        json!(format!(
+            "0x{:x}{:x}{:x}{:x}",
+            self.0.r, self.0.g, self.0.b, self.0.a
+        ))
+    }
+}
+
+impl Deref for Color {
+    type Target = ColorU;
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl DerefMut for Color {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl fmt::Debug for Color {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0.fmt(f)
     }
 }

gpui/src/elements/container.rs 🔗

@@ -1,20 +1,24 @@
 use pathfinder_geometry::rect::RectF;
+use serde::Deserialize;
 use serde_json::json;
 
 use crate::{
-    color::ColorU,
-    geometry::vector::{vec2f, Vector2F},
+    color::Color,
+    geometry::{
+        deserialize_vec2f,
+        vector::{vec2f, Vector2F},
+    },
     json::ToJson,
     scene::{self, Border, Quad},
     AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
     SizeConstraint,
 };
 
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Deserialize)]
 pub struct ContainerStyle {
     margin: Margin,
     padding: Padding,
-    background_color: Option<ColorU>,
+    background_color: Option<Color>,
     border: Border,
     corner_radius: f32,
     shadow: Option<Shadow>,
@@ -80,8 +84,8 @@ impl Container {
         self
     }
 
-    pub fn with_background_color(mut self, color: impl Into<ColorU>) -> Self {
-        self.style.background_color = Some(color.into());
+    pub fn with_background_color(mut self, color: Color) -> Self {
+        self.style.background_color = Some(color);
         self
     }
 
@@ -95,11 +99,11 @@ impl Container {
         self
     }
 
-    pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: impl Into<ColorU>) -> Self {
+    pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: Color) -> Self {
         self.style.shadow = Some(Shadow {
             offset,
             blur,
-            color: color.into(),
+            color,
         });
         self
     }
@@ -241,7 +245,7 @@ impl ToJson for ContainerStyle {
     }
 }
 
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Deserialize)]
 pub struct Margin {
     top: f32,
     left: f32,
@@ -268,7 +272,7 @@ impl ToJson for Margin {
     }
 }
 
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Deserialize)]
 pub struct Padding {
     top: f32,
     left: f32,
@@ -295,11 +299,12 @@ impl ToJson for Padding {
     }
 }
 
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Deserialize)]
 pub struct Shadow {
+    #[serde(deserialize_with = "deserialize_vec2f")]
     offset: Vector2F,
     blur: f32,
-    color: ColorU,
+    color: Color,
 }
 
 impl ToJson for Shadow {

gpui/src/elements/label.rs 🔗

@@ -1,10 +1,7 @@
-use serde_json::json;
-use smallvec::{smallvec, SmallVec};
-
 use crate::{
-    color::ColorU,
+    color::Color,
     font_cache::FamilyId,
-    fonts::{FontId, Properties},
+    fonts::{deserialize_font_properties, deserialize_option_font_properties, FontId, Properties},
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
@@ -14,6 +11,9 @@ use crate::{
     AfterLayoutContext, DebugContext, Element, Event, EventContext, FontCache, LayoutContext,
     PaintContext, SizeConstraint,
 };
+use serde::Deserialize;
+use serde_json::json;
+use smallvec::{smallvec, SmallVec};
 
 pub struct Label {
     text: String,
@@ -23,12 +23,14 @@ pub struct Label {
     highlight_indices: Vec<usize>,
 }
 
-#[derive(Clone, Debug, Default)]
+#[derive(Clone, Debug, Default, Deserialize)]
 pub struct LabelStyle {
-    pub default_color: ColorU,
-    pub highlight_color: ColorU,
+    pub color: Color,
+    pub highlight_color: Option<Color>,
+    #[serde(deserialize_with = "deserialize_font_properties")]
     pub font_properties: Properties,
-    pub highlight_font_properties: Properties,
+    #[serde(default, deserialize_with = "deserialize_option_font_properties")]
+    pub highlight_font_properties: Option<Properties>,
 }
 
 impl Label {
@@ -47,8 +49,8 @@ impl Label {
         self
     }
 
-    pub fn with_default_color(mut self, color: ColorU) -> Self {
-        self.style.default_color = color;
+    pub fn with_default_color(mut self, color: Color) -> Self {
+        self.style.color = color;
         self
     }
 
@@ -61,13 +63,15 @@ impl Label {
         &self,
         font_cache: &FontCache,
         font_id: FontId,
-    ) -> SmallVec<[(usize, FontId, ColorU); 8]> {
+    ) -> SmallVec<[(usize, FontId, Color); 8]> {
         if self.highlight_indices.is_empty() {
-            return smallvec![(self.text.len(), font_id, self.style.default_color)];
+            return smallvec![(self.text.len(), font_id, self.style.color)];
         }
 
-        let highlight_font_id = font_cache
-            .select_font(self.family_id, &self.style.highlight_font_properties)
+        let highlight_font_id = self
+            .style
+            .highlight_font_properties
+            .and_then(|properties| font_cache.select_font(self.family_id, &properties).ok())
             .unwrap_or(font_id);
 
         let mut highlight_indices = self.highlight_indices.iter().copied().peekable();
@@ -75,11 +79,11 @@ impl Label {
 
         for (char_ix, c) in self.text.char_indices() {
             let mut font_id = font_id;
-            let mut color = self.style.default_color;
+            let mut color = self.style.color;
             if let Some(highlight_ix) = highlight_indices.peek() {
                 if char_ix == *highlight_ix {
                     font_id = highlight_font_id;
-                    color = self.style.highlight_color;
+                    color = self.style.highlight_color.unwrap_or(self.style.color);
                     highlight_indices.next();
                 }
             }
@@ -179,7 +183,7 @@ impl Element for Label {
 impl ToJson for LabelStyle {
     fn to_json(&self) -> Value {
         json!({
-            "default_color": self.default_color.to_json(),
+            "default_color": self.color.to_json(),
             "default_font_properties": self.font_properties.to_json(),
             "highlight_color": self.highlight_color.to_json(),
             "highlight_font_properties": self.highlight_font_properties.to_json(),
@@ -204,14 +208,14 @@ mod tests {
             .font_cache()
             .select_font(menlo, Properties::new().weight(Weight::BOLD))
             .unwrap();
-        let black = ColorU::black();
-        let red = ColorU::new(255, 0, 0, 255);
+        let black = Color::black();
+        let red = Color::new(255, 0, 0, 255);
 
         let label = Label::new(".αβγδε.ⓐⓑⓒⓓⓔ.abcde.".to_string(), menlo, 12.0)
             .with_style(&LabelStyle {
-                default_color: black,
-                highlight_color: red,
-                highlight_font_properties: *Properties::new().weight(Weight::BOLD),
+                color: black,
+                highlight_color: Some(red),
+                highlight_font_properties: Some(*Properties::new().weight(Weight::BOLD)),
                 ..Default::default()
             })
             .with_highlights(vec![

gpui/src/elements/svg.rs 🔗

@@ -3,7 +3,7 @@ use std::borrow::Cow;
 use serde_json::json;
 
 use crate::{
-    color::ColorU,
+    color::Color,
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
@@ -14,18 +14,18 @@ use crate::{
 
 pub struct Svg {
     path: Cow<'static, str>,
-    color: ColorU,
+    color: Color,
 }
 
 impl Svg {
     pub fn new(path: impl Into<Cow<'static, str>>) -> Self {
         Self {
             path: path.into(),
-            color: ColorU::black(),
+            color: Color::black(),
         }
     }
 
-    pub fn with_color(mut self, color: ColorU) -> Self {
+    pub fn with_color(mut self, color: Color) -> Self {
         self.color = color;
         self
     }

gpui/src/fonts.rs 🔗

@@ -1,14 +1,75 @@
-use crate::json::json;
-pub use font_kit::metrics::Metrics;
-pub use font_kit::properties::{Properties, Stretch, Style, Weight};
-
-use crate::json::ToJson;
+use crate::json::{json, ToJson};
+pub use font_kit::{
+    metrics::Metrics,
+    properties::{Properties, Stretch, Style, Weight},
+};
+use serde::{Deserialize, Deserializer};
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
 pub struct FontId(pub usize);
 
 pub type GlyphId = u32;
 
+#[allow(non_camel_case_types)]
+#[derive(Deserialize)]
+enum WeightJson {
+    thin,
+    extra_light,
+    light,
+    normal,
+    medium,
+    semibold,
+    bold,
+    extra_bold,
+    black,
+}
+
+#[derive(Deserialize)]
+struct PropertiesJson {
+    weight: Option<WeightJson>,
+    #[serde(default)]
+    italic: bool,
+}
+
+impl Into<Properties> for PropertiesJson {
+    fn into(self) -> Properties {
+        let mut result = Properties::new();
+        result.weight = match self.weight.unwrap_or(WeightJson::normal) {
+            WeightJson::thin => Weight::THIN,
+            WeightJson::extra_light => Weight::EXTRA_LIGHT,
+            WeightJson::light => Weight::LIGHT,
+            WeightJson::normal => Weight::NORMAL,
+            WeightJson::medium => Weight::MEDIUM,
+            WeightJson::semibold => Weight::SEMIBOLD,
+            WeightJson::bold => Weight::BOLD,
+            WeightJson::extra_bold => Weight::EXTRA_BOLD,
+            WeightJson::black => Weight::BLACK,
+        };
+        if self.italic {
+            result.style = Style::Italic;
+        }
+        result
+    }
+}
+
+pub fn deserialize_option_font_properties<'de, D>(
+    deserializer: D,
+) -> Result<Option<Properties>, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let json: Option<PropertiesJson> = Deserialize::deserialize(deserializer)?;
+    Ok(json.map(Into::into))
+}
+
+pub fn deserialize_font_properties<'de, D>(deserializer: D) -> Result<Properties, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let json: PropertiesJson = Deserialize::deserialize(deserializer)?;
+    Ok(json.into())
+}
+
 impl ToJson for Properties {
     fn to_json(&self) -> crate::json::Value {
         json!({

gpui/src/geometry.rs 🔗

@@ -1,7 +1,8 @@
 use super::scene::{Path, PathVertex};
-use crate::{color::ColorU, json::ToJson};
+use crate::{color::Color, json::ToJson};
 pub use pathfinder_geometry::*;
 use rect::RectF;
+use serde::{Deserialize, Deserializer};
 use serde_json::json;
 use vector::{vec2f, Vector2F};
 
@@ -55,7 +56,7 @@ impl PathBuilder {
         self.current = point;
     }
 
-    pub fn build(mut self, color: ColorU, clip_bounds: Option<RectF>) -> Path {
+    pub fn build(mut self, color: Color, clip_bounds: Option<RectF>) -> Path {
         if let Some(clip_bounds) = clip_bounds {
             self.bounds = self
                 .bounds
@@ -108,6 +109,14 @@ impl PathBuilder {
     }
 }
 
+pub fn deserialize_vec2f<'de, D>(deserializer: D) -> Result<Vector2F, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    let [x, y]: [f32; 2] = Deserialize::deserialize(deserializer)?;
+    Ok(vec2f(x, y))
+}
+
 impl ToJson for Vector2F {
     fn to_json(&self) -> serde_json::Value {
         json!([self.x(), self.y()])

gpui/src/platform.rs 🔗

@@ -8,7 +8,7 @@ pub mod current {
 }
 
 use crate::{
-    color::ColorU,
+    color::Color,
     executor,
     fonts::{FontId, GlyphId, Metrics as FontMetrics, Properties as FontProperties},
     geometry::{
@@ -134,7 +134,7 @@ pub trait FontSystem: Send + Sync {
         &self,
         text: &str,
         font_size: f32,
-        runs: &[(usize, FontId, ColorU)],
+        runs: &[(usize, FontId, Color)],
     ) -> LineLayout;
     fn wrap_line(&self, text: &str, font_id: FontId, font_size: f32, width: f32) -> Vec<usize>;
 }

gpui/src/platform/mac/fonts.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    color::ColorU,
+    color::Color,
     fonts::{FontId, GlyphId, Metrics, Properties},
     geometry::{
         rect::{RectF, RectI},
@@ -82,7 +82,7 @@ impl platform::FontSystem for FontSystem {
         &self,
         text: &str,
         font_size: f32,
-        runs: &[(usize, FontId, ColorU)],
+        runs: &[(usize, FontId, Color)],
     ) -> LineLayout {
         self.0.read().layout_line(text, font_size, runs)
     }
@@ -191,7 +191,7 @@ impl FontSystemState {
         &self,
         text: &str,
         font_size: f32,
-        runs: &[(usize, FontId, ColorU)],
+        runs: &[(usize, FontId, Color)],
     ) -> LineLayout {
         let font_id_attr_name = CFString::from_static_string("zed_font_id");
 
@@ -436,9 +436,9 @@ mod tests {
             text,
             16.0,
             &[
-                (9, zapfino_regular, ColorU::default()),
-                (13, menlo_regular, ColorU::default()),
-                (text.len() - 22, zapfino_regular, ColorU::default()),
+                (9, zapfino_regular, Color::default()),
+                (13, menlo_regular, Color::default()),
+                (text.len() - 22, zapfino_regular, Color::default()),
             ],
         );
         assert_eq!(

gpui/src/platform/mac/renderer.rs 🔗

@@ -1,6 +1,6 @@
 use super::{atlas::AtlasAllocator, sprite_cache::SpriteCache};
 use crate::{
-    color::ColorU,
+    color::Color,
     geometry::{
         rect::RectF,
         vector::{vec2f, vec2i, Vector2F},
@@ -11,7 +11,7 @@ use crate::{
 };
 use cocoa::foundation::NSUInteger;
 use metal::{MTLPixelFormat, MTLResourceOptions, NSRange};
-use shaders::{ToFloat2 as _, ToUchar4 as _};
+use shaders::ToFloat2 as _;
 use std::{collections::HashMap, ffi::c_void, iter::Peekable, mem, sync::Arc, vec};
 
 const SHADERS_METALLIB: &'static [u8] =
@@ -438,7 +438,7 @@ impl Renderer {
                 size: bounds.size().round().to_float2(),
                 background_color: quad
                     .background
-                    .unwrap_or(ColorU::transparent_black())
+                    .unwrap_or(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),
@@ -447,7 +447,7 @@ impl Renderer {
                 border_color: quad
                     .border
                     .color
-                    .unwrap_or(ColorU::transparent_black())
+                    .unwrap_or(Color::transparent_black())
                     .to_uchar4(),
                 corner_radius: quad.corner_radius * scene.scale_factor(),
             };
@@ -782,7 +782,7 @@ mod shaders {
 
     use pathfinder_geometry::vector::Vector2I;
 
-    use crate::{color::ColorU, geometry::vector::Vector2F};
+    use crate::{color::Color, geometry::vector::Vector2F};
     use std::mem;
 
     include!(concat!(env!("OUT_DIR"), "/shaders.rs"));
@@ -791,10 +791,6 @@ mod shaders {
         fn to_float2(&self) -> vector_float2;
     }
 
-    pub trait ToUchar4 {
-        fn to_uchar4(&self) -> vector_uchar4;
-    }
-
     impl ToFloat2 for (f32, f32) {
         fn to_float2(&self) -> vector_float2 {
             unsafe {
@@ -823,8 +819,8 @@ mod shaders {
         }
     }
 
-    impl ToUchar4 for ColorU {
-        fn to_uchar4(&self) -> vector_uchar4 {
+    impl Color {
+        pub fn to_uchar4(&self) -> vector_uchar4 {
             let mut vec = self.a as vector_uchar4;
             vec <<= 8;
             vec |= self.b as vector_uchar4;

gpui/src/scene.rs 🔗

@@ -1,9 +1,9 @@
-use std::borrow::Cow;
-
+use serde::Deserialize;
 use serde_json::json;
+use std::borrow::Cow;
 
 use crate::{
-    color::ColorU,
+    color::Color,
     fonts::{FontId, GlyphId},
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
@@ -28,7 +28,7 @@ pub struct Layer {
 #[derive(Default, Debug)]
 pub struct Quad {
     pub bounds: RectF,
-    pub background: Option<ColorU>,
+    pub background: Option<Color>,
     pub border: Border,
     pub corner_radius: f32,
 }
@@ -38,7 +38,7 @@ pub struct Shadow {
     pub bounds: RectF,
     pub corner_radius: f32,
     pub sigma: f32,
-    pub color: ColorU,
+    pub color: Color,
 }
 
 #[derive(Debug)]
@@ -47,20 +47,20 @@ pub struct Glyph {
     pub font_size: f32,
     pub id: GlyphId,
     pub origin: Vector2F,
-    pub color: ColorU,
+    pub color: Color,
 }
 
 pub struct Icon {
     pub bounds: RectF,
     pub svg: usvg::Tree,
     pub path: Cow<'static, str>,
-    pub color: ColorU,
+    pub color: Color,
 }
 
-#[derive(Clone, Copy, Default, Debug)]
+#[derive(Clone, Copy, Default, Debug, Deserialize)]
 pub struct Border {
     pub width: f32,
-    pub color: Option<ColorU>,
+    pub color: Option<Color>,
     pub top: bool,
     pub right: bool,
     pub bottom: bool,
@@ -70,7 +70,7 @@ pub struct Border {
 #[derive(Debug)]
 pub struct Path {
     pub bounds: RectF,
-    pub color: ColorU,
+    pub color: Color,
     pub vertices: Vec<PathVertex>,
 }
 
@@ -193,10 +193,10 @@ impl Layer {
 }
 
 impl Border {
-    pub fn new(width: f32, color: impl Into<ColorU>) -> Self {
+    pub fn new(width: f32, color: Color) -> Self {
         Self {
             width,
-            color: Some(color.into()),
+            color: Some(color),
             top: false,
             left: false,
             bottom: false,
@@ -204,10 +204,10 @@ impl Border {
         }
     }
 
-    pub fn all(width: f32, color: impl Into<ColorU>) -> Self {
+    pub fn all(width: f32, color: Color) -> Self {
         Self {
             width,
-            color: Some(color.into()),
+            color: Some(color),
             top: true,
             left: true,
             bottom: true,
@@ -215,25 +215,25 @@ impl Border {
         }
     }
 
-    pub fn top(width: f32, color: impl Into<ColorU>) -> Self {
+    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: impl Into<ColorU>) -> Self {
+    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: impl Into<ColorU>) -> Self {
+    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: impl Into<ColorU>) -> Self {
+    pub fn right(width: f32, color: Color) -> Self {
         let mut border = Self::new(width, color);
         border.right = true;
         border

gpui/src/text_layout.rs 🔗

@@ -1,5 +1,5 @@
 use crate::{
-    color::ColorU,
+    color::Color,
     fonts::{FontId, GlyphId},
     geometry::{
         rect::RectF,
@@ -43,7 +43,7 @@ impl TextLayoutCache {
         &'a self,
         text: &'a str,
         font_size: f32,
-        runs: &'a [(usize, FontId, ColorU)],
+        runs: &'a [(usize, FontId, Color)],
     ) -> Line {
         let key = &CacheKeyRef {
             text,
@@ -94,7 +94,7 @@ impl<'a> Hash for (dyn CacheKey + 'a) {
 struct CacheKeyValue {
     text: String,
     font_size: OrderedFloat<f32>,
-    runs: SmallVec<[(usize, FontId, ColorU); 1]>,
+    runs: SmallVec<[(usize, FontId, Color); 1]>,
 }
 
 impl CacheKey for CacheKeyValue {
@@ -123,7 +123,7 @@ impl<'a> Borrow<dyn CacheKey + 'a> for CacheKeyValue {
 struct CacheKeyRef<'a> {
     text: &'a str,
     font_size: OrderedFloat<f32>,
-    runs: &'a [(usize, FontId, ColorU)],
+    runs: &'a [(usize, FontId, Color)],
 }
 
 impl<'a> CacheKey for CacheKeyRef<'a> {
@@ -135,7 +135,7 @@ impl<'a> CacheKey for CacheKeyRef<'a> {
 #[derive(Default, Debug)]
 pub struct Line {
     layout: Arc<LineLayout>,
-    color_runs: SmallVec<[(u32, ColorU); 32]>,
+    color_runs: SmallVec<[(u32, Color); 32]>,
 }
 
 #[derive(Default, Debug)]
@@ -162,7 +162,7 @@ pub struct Glyph {
 }
 
 impl Line {
-    fn new(layout: Arc<LineLayout>, runs: &[(usize, FontId, ColorU)]) -> Self {
+    fn new(layout: Arc<LineLayout>, runs: &[(usize, FontId, Color)]) -> Self {
         let mut color_runs = SmallVec::new();
         for (len, _, color) in runs {
             color_runs.push((*len as u32, *color));
@@ -206,7 +206,7 @@ impl Line {
 
         let mut color_runs = self.color_runs.iter();
         let mut color_end = 0;
-        let mut color = ColorU::black();
+        let mut color = Color::black();
 
         for run in &self.layout.runs {
             let max_glyph_width = cx
@@ -230,7 +230,7 @@ impl Line {
                         color = next_run.1;
                     } else {
                         color_end = self.layout.len;
-                        color = ColorU::black();
+                        color = Color::black();
                     }
                 }
 

zed/src/editor.rs 🔗

@@ -16,7 +16,7 @@ pub use display_map::DisplayPoint;
 use display_map::*;
 pub use element::*;
 use gpui::{
-    color::ColorU, font_cache::FamilyId, fonts::Properties as FontProperties,
+    color::Color, font_cache::FamilyId, fonts::Properties as FontProperties,
     geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, Element,
     ElementBox, Entity, FontCache, ModelHandle, MutableAppContext, RenderContext, Task,
     TextLayoutCache, View, ViewContext, WeakViewHandle,
@@ -2349,7 +2349,7 @@ impl Snapshot {
             .layout_str(
                 "1".repeat(digit_count).as_str(),
                 font_size,
-                &[(digit_count, font_id, ColorU::black())],
+                &[(digit_count, font_id, Color::black())],
             )
             .width())
     }
@@ -2374,9 +2374,9 @@ impl Snapshot {
         {
             let display_row = rows.start + ix as u32;
             let color = if active_rows.contains_key(&display_row) {
-                theme.editor.line_number_active.0
+                theme.editor.line_number_active
             } else {
-                theme.editor.line_number.0
+                theme.editor.line_number
             };
             if soft_wrapped {
                 layouts.push(None);
@@ -2485,7 +2485,7 @@ impl Snapshot {
             &[(
                 self.display_snapshot.line_len(row) as usize,
                 font_id,
-                ColorU::black(),
+                Color::black(),
             )],
         ))
     }

zed/src/editor/display_map.rs 🔗

@@ -340,7 +340,7 @@ mod tests {
         util::RandomCharIter,
     };
     use buffer::{History, SelectionGoal};
-    use gpui::{color::ColorU, MutableAppContext};
+    use gpui::{color::Color, MutableAppContext};
     use rand::{prelude::StdRng, Rng};
     use std::{env, sync::Arc};
     use Bias::*;
@@ -656,12 +656,12 @@ mod tests {
             syntax: vec![
                 (
                     "mod.body".to_string(),
-                    ColorU::from_u32(0xff0000ff),
+                    Color::from_u32(0xff0000ff),
                     Default::default(),
                 ),
                 (
                     "fn.name".to_string(),
-                    ColorU::from_u32(0x00ff00ff),
+                    Color::from_u32(0x00ff00ff),
                     Default::default(),
                 ),
             ],
@@ -754,12 +754,12 @@ mod tests {
             syntax: vec![
                 (
                     "mod.body".to_string(),
-                    ColorU::from_u32(0xff0000ff),
+                    Color::from_u32(0xff0000ff),
                     Default::default(),
                 ),
                 (
                     "fn.name".to_string(),
-                    ColorU::from_u32(0x00ff00ff),
+                    Color::from_u32(0x00ff00ff),
                     Default::default(),
                 ),
             ],

zed/src/editor/element.rs 🔗

@@ -1,7 +1,7 @@
 use super::{DisplayPoint, Editor, SelectAction, Snapshot};
 use crate::time::ReplicaId;
 use gpui::{
-    color::ColorU,
+    color::Color,
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
@@ -196,14 +196,14 @@ impl EditorElement {
         let theme = &settings.theme;
         cx.scene.push_quad(Quad {
             bounds: gutter_bounds,
-            background: Some(theme.editor.gutter_background.0),
-            border: Border::new(0., ColorU::transparent_black()),
+            background: Some(theme.editor.gutter_background),
+            border: Border::new(0., Color::transparent_black()),
             corner_radius: 0.,
         });
         cx.scene.push_quad(Quad {
             bounds: text_bounds,
-            background: Some(theme.editor.background.0),
-            border: Border::new(0., ColorU::transparent_black()),
+            background: Some(theme.editor.background),
+            border: Border::new(0., Color::transparent_black()),
             corner_radius: 0.,
         });
 
@@ -229,7 +229,7 @@ impl EditorElement {
                     );
                     cx.scene.push_quad(Quad {
                         bounds: RectF::new(origin, size),
-                        background: Some(theme.editor.active_line_background.0),
+                        background: Some(theme.editor.active_line_background),
                         border: Border::default(),
                         corner_radius: 0.,
                     });
@@ -290,7 +290,7 @@ impl EditorElement {
                     };
 
                     let selection = Selection {
-                        color: replica_theme.selection.0,
+                        color: replica_theme.selection,
                         line_height: layout.line_height,
                         start_y: content_origin.y() + row_range.start as f32 * layout.line_height
                             - scroll_top,
@@ -333,7 +333,7 @@ impl EditorElement {
                             - scroll_left;
                         let y = selection.end.row() as f32 * layout.line_height - scroll_top;
                         cursors.push(Cursor {
-                            color: replica_theme.cursor.0,
+                            color: replica_theme.cursor,
                             origin: content_origin + vec2f(x, y),
                             line_height: layout.line_height,
                         });
@@ -707,7 +707,7 @@ impl PaintState {
 struct Cursor {
     origin: Vector2F,
     line_height: f32,
-    color: ColorU,
+    color: Color,
 }
 
 impl Cursor {
@@ -715,7 +715,7 @@ impl Cursor {
         cx.scene.push_quad(Quad {
             bounds: RectF::new(self.origin, vec2f(2.0, self.line_height)),
             background: Some(self.color),
-            border: Border::new(0., ColorU::black()),
+            border: Border::new(0., Color::black()),
             corner_radius: 0.,
         });
     }
@@ -726,7 +726,7 @@ struct Selection {
     start_y: f32,
     line_height: f32,
     lines: Vec<SelectionLine>,
-    color: ColorU,
+    color: Color,
 }
 
 #[derive(Debug)]

zed/src/file_finder.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     worktree::{match_paths, PathMatch, Worktree},
 };
 use gpui::{
-    color::ColorF,
+    color::Color,
     elements::*,
     fonts::{Properties, Weight},
     geometry::vector::vec2f,
@@ -82,7 +82,7 @@ impl View for FileFinder {
                 .with_uniform_padding(6.0)
                 .with_corner_radius(6.0)
                 .with_background_color(settings.theme.ui.modal_background)
-                .with_shadow(vec2f(0., 4.), 12., ColorF::new(0.0, 0.0, 0.0, 0.5).to_u8())
+                .with_shadow(vec2f(0., 4.), 12., Color::new(0, 0, 0, 128))
                 .boxed(),
             )
             .with_max_width(600.0)
@@ -114,7 +114,7 @@ impl FileFinder {
                     settings.ui_font_family,
                     settings.ui_font_size,
                 )
-                .with_default_color(settings.theme.editor.default_text.0)
+                .with_default_color(settings.theme.editor.default_text)
                 .boxed(),
             )
             .with_margin_top(6.0)
@@ -155,9 +155,9 @@ impl FileFinder {
                 let bold = *Properties::new().weight(Weight::BOLD);
                 let selected_index = self.selected_index();
                 let label_style = LabelStyle {
-                    default_color: theme.modal_match_text.0,
-                    highlight_color: theme.modal_match_text_highlight.0,
-                    highlight_font_properties: bold,
+                    color: theme.modal_match_text,
+                    highlight_color: Some(theme.modal_match_text_highlight),
+                    highlight_font_properties: Some(bold),
                     ..Default::default()
                 };
                 let mut container = Container::new(
@@ -206,9 +206,9 @@ impl FileFinder {
                 )
                 .with_uniform_padding(6.0)
                 .with_background_color(if index == selected_index {
-                    theme.modal_match_background_active.0
+                    theme.modal_match_background_active
                 } else {
-                    theme.modal_match_background.0
+                    theme.modal_match_background
                 });
 
                 if index == selected_index || index < self.matches.len() - 1 {

zed/src/settings.rs 🔗

@@ -1,6 +1,6 @@
 use anyhow::{anyhow, Context, Result};
 use gpui::{
-    color::ColorU,
+    color::Color,
     font_cache::{FamilyId, FontCache},
     fonts::{Properties as FontProperties, Style as FontStyle, Weight as FontWeight},
     AssetSource,
@@ -9,12 +9,7 @@ use parking_lot::Mutex;
 use postage::watch;
 use serde::{de::value::MapDeserializer, Deserialize};
 use serde_json::Value;
-use std::{
-    collections::HashMap,
-    fmt,
-    ops::{Deref, DerefMut},
-    sync::Arc,
-};
+use std::{collections::HashMap, sync::Arc};
 
 const DEFAULT_STYLE_ID: StyleId = StyleId(u32::MAX);
 
@@ -38,7 +33,7 @@ pub struct ThemeRegistry {
 pub struct Theme {
     pub ui: UiTheme,
     pub editor: EditorTheme,
-    pub syntax: Vec<(String, ColorU, FontProperties)>,
+    pub syntax: Vec<(String, Color, FontProperties)>,
 }
 
 #[derive(Deserialize)]
@@ -93,9 +88,6 @@ pub struct ReplicaTheme {
     pub selection: Color,
 }
 
-#[derive(Clone, Copy, Default)]
-pub struct Color(pub ColorU);
-
 #[derive(Clone, Debug)]
 pub struct ThemeMap(Arc<[StyleId]>);
 
@@ -151,7 +143,7 @@ impl ThemeRegistry {
         }
 
         let theme_toml = self.load(name)?;
-        let mut syntax = Vec::<(String, ColorU, FontProperties)>::new();
+        let mut syntax = Vec::<(String, Color, FontProperties)>::new();
         for (key, style) in theme_toml.syntax.iter() {
             let mut color = Color::default();
             let mut properties = FontProperties::new();
@@ -171,7 +163,7 @@ impl ThemeRegistry {
             }
             match syntax.binary_search_by_key(&key, |e| &e.0) {
                 Ok(i) | Err(i) => {
-                    syntax.insert(i, (key.to_string(), color.0, properties));
+                    syntax.insert(i, (key.to_string(), color, properties));
                 }
             }
         }
@@ -234,11 +226,12 @@ impl ThemeRegistry {
 }
 
 impl Theme {
-    pub fn syntax_style(&self, id: StyleId) -> (ColorU, FontProperties) {
-        self.syntax.get(id.0 as usize).map_or(
-            (self.editor.default_text.0, FontProperties::new()),
-            |entry| (entry.1, entry.2),
-        )
+    pub fn syntax_style(&self, id: StyleId) -> (Color, FontProperties) {
+        self.syntax
+            .get(id.0 as usize)
+            .map_or((self.editor.default_text, FontProperties::new()), |entry| {
+                (entry.1, entry.2)
+            })
     }
 
     #[cfg(test)]
@@ -313,53 +306,6 @@ impl Default for StyleId {
     }
 }
 
-impl Color {
-    fn from_u32(rgba: u32) -> Self {
-        Self(ColorU::from_u32(rgba))
-    }
-}
-
-impl<'de> Deserialize<'de> for Color {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let rgb = u32::deserialize(deserializer)?;
-        Ok(Self::from_u32((rgb << 8) + 0xFF))
-    }
-}
-
-impl Into<ColorU> for Color {
-    fn into(self) -> ColorU {
-        self.0
-    }
-}
-
-impl Deref for Color {
-    type Target = ColorU;
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
-
-impl DerefMut for Color {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.0
-    }
-}
-
-impl fmt::Debug for Color {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        self.0.fmt(f)
-    }
-}
-
-impl PartialEq<ColorU> for Color {
-    fn eq(&self, other: &ColorU) -> bool {
-        self.0.eq(other)
-    }
-}
-
 pub fn channel(
     font_cache: &FontCache,
 ) -> Result<(watch::Sender<Settings>, watch::Receiver<Settings>)> {
@@ -478,25 +424,25 @@ mod tests {
         let registry = ThemeRegistry::new(assets);
         let theme = registry.get("my-theme").unwrap();
 
-        assert_eq!(theme.ui.tab_background_active, ColorU::from_u32(0x100000ff));
-        assert_eq!(theme.editor.background, ColorU::from_u32(0x00ed00ff));
-        assert_eq!(theme.editor.line_number, ColorU::from_u32(0xddddddff));
+        assert_eq!(theme.ui.tab_background_active, Color::from_u32(0x100000ff));
+        assert_eq!(theme.editor.background, Color::from_u32(0x00ed00ff));
+        assert_eq!(theme.editor.line_number, Color::from_u32(0xddddddff));
         assert_eq!(
             theme.syntax,
             &[
                 (
                     "alpha.one".to_string(),
-                    ColorU::from_u32(0x112233ff),
+                    Color::from_u32(0x112233ff),
                     *FontProperties::new().weight(FontWeight::BOLD)
                 ),
                 (
                     "beta.two".to_string(),
-                    ColorU::from_u32(0xaabbccff),
+                    Color::from_u32(0xaabbccff),
                     *FontProperties::new().weight(FontWeight::NORMAL)
                 ),
                 (
                     "gamma.three".to_string(),
-                    ColorU::from_u32(0x00000000),
+                    Color::from_u32(0x00000000),
                     *FontProperties::new()
                         .weight(FontWeight::LIGHT)
                         .style(FontStyle::Italic),
@@ -553,10 +499,10 @@ mod tests {
         let registry = ThemeRegistry::new(assets);
         let theme = registry.get("light").unwrap();
 
-        assert_eq!(theme.ui.tab_background, ColorU::from_u32(0x555555ff));
-        assert_eq!(theme.ui.tab_text, ColorU::from_u32(0x333333ff));
-        assert_eq!(theme.editor.background, ColorU::from_u32(0x666666ff));
-        assert_eq!(theme.editor.default_text, ColorU::from_u32(0x444444ff));
+        assert_eq!(theme.ui.tab_background, Color::from_u32(0x555555ff));
+        assert_eq!(theme.ui.tab_text, Color::from_u32(0x333333ff));
+        assert_eq!(theme.editor.background, Color::from_u32(0x666666ff));
+        assert_eq!(theme.editor.default_text, Color::from_u32(0x444444ff));
 
         assert_eq!(
             registry.list().collect::<Vec<_>>(),
@@ -577,12 +523,12 @@ mod tests {
             ui: Default::default(),
             editor: Default::default(),
             syntax: [
-                ("function", ColorU::from_u32(0x100000ff)),
-                ("function.method", ColorU::from_u32(0x200000ff)),
-                ("function.async", ColorU::from_u32(0x300000ff)),
-                ("variable.builtin.self.rust", ColorU::from_u32(0x400000ff)),
-                ("variable.builtin", ColorU::from_u32(0x500000ff)),
-                ("variable", ColorU::from_u32(0x600000ff)),
+                ("function", Color::from_u32(0x100000ff)),
+                ("function.method", Color::from_u32(0x200000ff)),
+                ("function.async", Color::from_u32(0x300000ff)),
+                ("variable.builtin.self.rust", Color::from_u32(0x400000ff)),
+                ("variable.builtin", Color::from_u32(0x500000ff)),
+                ("variable", Color::from_u32(0x600000ff)),
             ]
             .iter()
             .map(|e| (e.0.to_string(), e.1, FontProperties::new()))

zed/src/theme_selector.rs 🔗

@@ -9,7 +9,7 @@ use crate::{
 };
 use futures::lock::Mutex;
 use gpui::{
-    color::ColorF,
+    color::Color,
     elements::{
         Align, ChildView, ConstrainedBox, Container, Expanded, Flex, Label, LabelStyle,
         ParentElement, UniformList, UniformListState,
@@ -199,7 +199,7 @@ impl ThemeSelector {
                     settings.ui_font_family,
                     settings.ui_font_size,
                 )
-                .with_default_color(settings.theme.editor.default_text.0)
+                .with_default_color(settings.theme.editor.default_text)
                 .boxed(),
             )
             .with_margin_top(6.0)
@@ -241,9 +241,9 @@ impl ThemeSelector {
                 settings.ui_font_size,
             )
             .with_style(&LabelStyle {
-                default_color: theme.modal_match_text.0,
-                highlight_color: theme.modal_match_text_highlight.0,
-                highlight_font_properties: *Properties::new().weight(Weight::BOLD),
+                color: theme.modal_match_text,
+                highlight_color: Some(theme.modal_match_text_highlight),
+                highlight_font_properties: Some(*Properties::new().weight(Weight::BOLD)),
                 ..Default::default()
             })
             .with_highlights(theme_match.positions.clone())
@@ -251,9 +251,9 @@ impl ThemeSelector {
         )
         .with_uniform_padding(6.0)
         .with_background_color(if index == self.selected_index {
-            theme.modal_match_background_active.0
+            theme.modal_match_background_active
         } else {
-            theme.modal_match_background.0
+            theme.modal_match_background
         });
 
         if index == self.selected_index || index < self.matches.len() - 1 {
@@ -288,7 +288,7 @@ impl View for ThemeSelector {
                 .with_uniform_padding(6.0)
                 .with_corner_radius(6.0)
                 .with_background_color(settings.theme.ui.modal_background)
-                .with_shadow(vec2f(0., 4.), 12., ColorF::new(0.0, 0.0, 0.0, 0.5).to_u8())
+                .with_shadow(vec2f(0., 4.), 12., Color::new(0, 0, 0, 128))
                 .boxed(),
             )
             .with_max_width(600.0)

zed/src/workspace/pane.rs 🔗

@@ -1,7 +1,7 @@
 use super::{ItemViewHandle, SplitDirection};
 use crate::settings::{Settings, UiTheme};
 use gpui::{
-    color::ColorU,
+    color::Color,
     elements::*,
     geometry::{rect::RectF, vector::vec2f},
     keymap::Binding,
@@ -200,7 +200,7 @@ impl Pane {
                     MouseEventHandler::new::<Tab, _>(item.id(), cx, |mouse_state| {
                         let title = item.title(cx);
 
-                        let mut border = Border::new(1.0, theme.tab_border.0);
+                        let mut border = Border::new(1.0, theme.tab_border);
                         border.left = ix > 0;
                         border.right = ix == last_item_ix;
                         border.bottom = ix != self.active_item;
@@ -215,9 +215,9 @@ impl Pane {
                                             settings.ui_font_size,
                                         )
                                         .with_default_color(if is_active {
-                                            theme.tab_text_active.0
+                                            theme.tab_text_active
                                         } else {
-                                            theme.tab_text.0
+                                            theme.tab_text
                                         })
                                         .boxed(),
                                     )
@@ -317,12 +317,12 @@ impl Pane {
         };
 
         let icon = if tab_hovered {
-            let close_color = current_color.unwrap_or(theme.tab_icon_close).0;
+            let close_color = current_color.unwrap_or(theme.tab_icon_close);
             let icon = Svg::new("icons/x.svg").with_color(close_color);
 
             MouseEventHandler::new::<TabCloseButton, _>(item_id, cx, |mouse_state| {
                 if mouse_state.hovered {
-                    Container::new(icon.with_color(ColorU::white()).boxed())
+                    Container::new(icon.with_color(Color::white()).boxed())
                         .with_background_color(if mouse_state.clicked {
                             clicked_color
                         } else {
@@ -344,7 +344,7 @@ impl Pane {
                         let square = RectF::new(bounds.origin(), vec2f(diameter, diameter));
                         cx.scene.push_quad(Quad {
                             bounds: square,
-                            background: Some(current_color.0),
+                            background: Some(current_color),
                             border: Default::default(),
                             corner_radius: diameter / 2.,
                         });

zed/src/workspace/pane_group.rs 🔗

@@ -1,9 +1,5 @@
 use anyhow::{anyhow, Result};
-use gpui::{
-    color::{rgbu, ColorU},
-    elements::*,
-    Axis, Border,
-};
+use gpui::{color::Color, elements::*, Axis, Border};
 
 #[derive(Clone, Debug, Eq, PartialEq)]
 pub struct PaneGroup {
@@ -388,6 +384,6 @@ fn border_width() -> f32 {
 }
 
 #[inline(always)]
-fn border_color() -> ColorU {
-    rgbu(0xdb, 0xdb, 0xdc)
+fn border_color() -> Color {
+    Color::new(0xdb, 0xdb, 0xdc, 0xff)
 }