Extend the theme crate to enable stories, add players story

Nate Butler created

Change summary

Cargo.lock                              |   1 
crates/storybook2/src/story_selector.rs |   2 
crates/theme2/Cargo.toml                |   3 
crates/theme2/src/colors.rs             |  35 ------
crates/theme2/src/default_colors.rs     | 145 +++++++++++++++++---------
crates/theme2/src/default_theme.rs      |   6 
crates/theme2/src/players.rs            |  86 ++++++++++++++++
crates/theme2/src/story.rs              |  38 +++++++
crates/theme2/src/theme2.rs             |   7 +
9 files changed, 237 insertions(+), 86 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9005,6 +9005,7 @@ dependencies = [
  "fs2",
  "gpui2",
  "indexmap 1.9.3",
+ "itertools 0.11.0",
  "parking_lot 0.11.2",
  "refineable",
  "schemars",

crates/storybook2/src/story_selector.rs 🔗

@@ -38,6 +38,7 @@ pub enum ComponentStory {
     Palette,
     Panel,
     ProjectPanel,
+    Players,
     RecentProjects,
     Scroll,
     Tab,
@@ -79,6 +80,7 @@ impl ComponentStory {
             Self::MultiBuffer => cx.build_view(|_| ui::MultiBufferStory).into(),
             Self::NotificationsPanel => cx.build_view(|cx| ui::NotificationsPanelStory).into(),
             Self::Palette => cx.build_view(|cx| ui::PaletteStory).into(),
+            Self::Players => cx.build_view(|_| theme2::PlayerStory).into(),
             Self::Panel => cx.build_view(|cx| ui::PanelStory).into(),
             Self::ProjectPanel => cx.build_view(|_| ui::ProjectPanelStory).into(),
             Self::RecentProjects => cx.build_view(|_| ui::RecentProjectsStory).into(),

crates/theme2/Cargo.toml 🔗

@@ -5,6 +5,8 @@ edition = "2021"
 publish = false
 
 [features]
+default = ["stories"]
+stories = ["dep:itertools"]
 test-support = [
     "gpui/test-support",
     "fs/test-support",
@@ -30,6 +32,7 @@ settings = { package = "settings2", path = "../settings2" }
 toml.workspace = true
 uuid.workspace = true
 util = { path = "../util" }
+itertools = { version = "0.11.0", optional = true }
 
 [dev-dependencies]
 gpui = { package = "gpui2", path = "../gpui2", features = ["test-support"] }

crates/theme2/src/colors.rs 🔗

@@ -3,7 +3,7 @@ use std::sync::Arc;
 use gpui::Hsla;
 use refineable::Refineable;
 
-use crate::SyntaxTheme;
+use crate::{PlayerColors, SyntaxTheme};
 
 #[derive(Clone)]
 pub struct SystemColors {
@@ -13,39 +13,6 @@ pub struct SystemColors {
     pub mac_os_traffic_light_green: Hsla,
 }
 
-#[derive(Debug, Clone, Copy)]
-pub struct PlayerColor {
-    pub cursor: Hsla,
-    pub background: Hsla,
-    pub selection: Hsla,
-}
-
-/// A collection of colors that are used to color players in the editor.
-///
-/// The first color is always the local player's color, usually a blue.
-///
-/// The rest of the default colors crisscross back and forth on the
-/// color wheel so that the colors are as distinct as possible.
-#[derive(Clone)]
-pub struct PlayerColors(pub Vec<PlayerColor>);
-
-impl PlayerColors {
-    pub fn local(&self) -> PlayerColor {
-        // todo!("use a valid color");
-        *self.0.first().unwrap()
-    }
-
-    pub fn absent(&self) -> PlayerColor {
-        // todo!("use a valid color");
-        *self.0.last().unwrap()
-    }
-
-    pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
-        let len = self.0.len() - 1;
-        self.0[(participant_index as usize % len) + 1]
-    }
-}
-
 #[derive(Refineable, Clone, Debug)]
 #[refineable(debug)]
 pub struct StatusColors {

crates/theme2/src/default_colors.rs 🔗

@@ -3,12 +3,106 @@ use std::num::ParseIntError;
 use gpui::{hsla, Hsla, Rgba};
 
 use crate::{
-    colors::{GitStatusColors, PlayerColor, PlayerColors, StatusColors, SystemColors, ThemeColors},
+    colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors},
     scale::{ColorScaleSet, ColorScales},
     syntax::SyntaxTheme,
-    ColorScale,
+    ColorScale, PlayerColor, PlayerColors,
 };
 
+impl Default for PlayerColors {
+    fn default() -> Self {
+        Self(vec![
+            PlayerColor {
+                cursor: blue().dark().step_9(),
+                background: blue().dark().step_5(),
+                selection: blue().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: orange().dark().step_9(),
+                background: orange().dark().step_5(),
+                selection: orange().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: pink().dark().step_9(),
+                background: pink().dark().step_5(),
+                selection: pink().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: lime().dark().step_9(),
+                background: lime().dark().step_5(),
+                selection: lime().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: purple().dark().step_9(),
+                background: purple().dark().step_5(),
+                selection: purple().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: amber().dark().step_9(),
+                background: amber().dark().step_5(),
+                selection: amber().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: jade().dark().step_9(),
+                background: jade().dark().step_5(),
+                selection: jade().dark().step_3(),
+            },
+            PlayerColor {
+                cursor: red().dark().step_9(),
+                background: red().dark().step_5(),
+                selection: red().dark().step_3(),
+            },
+        ])
+    }
+}
+
+impl PlayerColors {
+    pub fn default_light() -> Self {
+        Self(vec![
+            PlayerColor {
+                cursor: blue().light().step_9(),
+                background: blue().light().step_4(),
+                selection: blue().light().step_3(),
+            },
+            PlayerColor {
+                cursor: orange().light().step_9(),
+                background: orange().light().step_4(),
+                selection: orange().light().step_3(),
+            },
+            PlayerColor {
+                cursor: pink().light().step_9(),
+                background: pink().light().step_4(),
+                selection: pink().light().step_3(),
+            },
+            PlayerColor {
+                cursor: lime().light().step_9(),
+                background: lime().light().step_4(),
+                selection: lime().light().step_3(),
+            },
+            PlayerColor {
+                cursor: purple().light().step_9(),
+                background: purple().light().step_4(),
+                selection: purple().light().step_3(),
+            },
+            PlayerColor {
+                cursor: amber().light().step_9(),
+                background: amber().light().step_4(),
+                selection: amber().light().step_3(),
+            },
+            PlayerColor {
+                cursor: jade().light().step_9(),
+                background: jade().light().step_4(),
+                selection: jade().light().step_3(),
+            },
+            PlayerColor {
+                cursor: red().light().step_9(),
+                background: red().light().step_4(),
+                selection: red().light().step_3(),
+            },
+        ])
+    }
+}
+
 fn neutral() -> ColorScaleSet {
     slate()
 }
@@ -55,53 +149,6 @@ impl Default for GitStatusColors {
     }
 }
 
-impl Default for PlayerColors {
-    fn default() -> Self {
-        Self(vec![
-            PlayerColor {
-                cursor: blue().dark().step_9(),
-                background: blue().dark().step_4(),
-                selection: blue().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: orange().dark().step_9(),
-                background: orange().dark().step_4(),
-                selection: orange().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: pink().dark().step_9(),
-                background: pink().dark().step_4(),
-                selection: pink().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: lime().dark().step_9(),
-                background: lime().dark().step_4(),
-                selection: lime().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: purple().dark().step_9(),
-                background: purple().dark().step_4(),
-                selection: purple().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: amber().dark().step_9(),
-                background: amber().dark().step_4(),
-                selection: amber().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: jade().dark().step_9(),
-                background: jade().dark().step_4(),
-                selection: jade().dark().step_3(),
-            },
-            PlayerColor {
-                cursor: red().dark().step_9(),
-                background: red().dark().step_4(),
-                selection: red().dark().step_3(),
-            },
-        ])
-    }
-}
-
 impl SyntaxTheme {
     pub fn default_light() -> Self {
         Self {

crates/theme2/src/default_theme.rs 🔗

@@ -1,8 +1,8 @@
 use std::sync::Arc;
 
 use crate::{
-    colors::{GitStatusColors, PlayerColors, StatusColors, SystemColors, ThemeColors, ThemeStyles},
-    default_color_scales, Appearance, SyntaxTheme, Theme, ThemeFamily,
+    colors::{GitStatusColors, StatusColors, SystemColors, ThemeColors, ThemeStyles},
+    default_color_scales, Appearance, PlayerColors, SyntaxTheme, Theme, ThemeFamily,
 };
 
 fn zed_pro_daylight() -> Theme {
@@ -15,7 +15,7 @@ fn zed_pro_daylight() -> Theme {
             colors: ThemeColors::default_light(),
             status: StatusColors::default(),
             git: GitStatusColors::default(),
-            player: PlayerColors::default(),
+            player: PlayerColors::default_light(),
             syntax: Arc::new(SyntaxTheme::default_light()),
         },
     }

crates/theme2/src/players.rs 🔗

@@ -0,0 +1,86 @@
+use gpui::Hsla;
+
+#[derive(Debug, Clone, Copy)]
+pub struct PlayerColor {
+    pub cursor: Hsla,
+    pub background: Hsla,
+    pub selection: Hsla,
+}
+
+/// A collection of colors that are used to color players in the editor.
+///
+/// The first color is always the local player's color, usually a blue.
+///
+/// The rest of the default colors crisscross back and forth on the
+/// color wheel so that the colors are as distinct as possible.
+#[derive(Clone)]
+pub struct PlayerColors(pub Vec<PlayerColor>);
+
+impl PlayerColors {
+    pub fn local(&self) -> PlayerColor {
+        // todo!("use a valid color");
+        *self.0.first().unwrap()
+    }
+
+    pub fn absent(&self) -> PlayerColor {
+        // todo!("use a valid color");
+        *self.0.last().unwrap()
+    }
+
+    pub fn color_for_participant(&self, participant_index: u32) -> PlayerColor {
+        let len = self.0.len() - 1;
+        self.0[(participant_index as usize % len) + 1]
+    }
+}
+
+#[cfg(feature = "stories")]
+pub use stories::*;
+
+#[cfg(feature = "stories")]
+mod stories {
+    use super::*;
+    use crate::{ActiveTheme, Story};
+    use gpui::{div, Div, ParentElement, Render, Styled, ViewContext};
+
+    pub struct PlayerStory;
+
+    impl Render for PlayerStory {
+        type Element = Div<Self>;
+
+        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
+            Story::container(cx)
+                .child(Story::title_for::<_, PlayerColors>(cx))
+                .child(Story::label(cx, "Player Colors"))
+                .child(
+                    div()
+                        .flex()
+                        .flex_col()
+                        .gap_1()
+                        .child(
+                            div().flex().gap_1().children(
+                                cx.theme()
+                                    .players()
+                                    .0
+                                    .clone()
+                                    .iter_mut()
+                                    .map(|color| div().w_8().h_8().rounded_md().bg(color.cursor)),
+                            ),
+                        )
+                        .child(
+                            div().flex().gap_1().children(
+                                cx.theme().players().0.clone().iter_mut().map(|color| {
+                                    div().w_8().h_8().rounded_md().bg(color.background)
+                                }),
+                            ),
+                        )
+                        .child(
+                            div().flex().gap_1().children(
+                                cx.theme().players().0.clone().iter_mut().map(|color| {
+                                    div().w_8().h_8().rounded_md().bg(color.selection)
+                                }),
+                            ),
+                        ),
+                )
+        }
+    }
+}

crates/theme2/src/story.rs 🔗

@@ -0,0 +1,38 @@
+use gpui::{div, Component, Div, ParentElement, Styled, ViewContext};
+
+use crate::ActiveTheme;
+
+pub struct Story {}
+
+impl Story {
+    pub fn container<V: 'static>(cx: &mut ViewContext<V>) -> Div<V> {
+        div()
+            .size_full()
+            .flex()
+            .flex_col()
+            .pt_2()
+            .px_4()
+            .font("Zed Mono")
+            .bg(cx.theme().colors().background)
+    }
+
+    pub fn title<V: 'static>(cx: &mut ViewContext<V>, title: &str) -> impl Component<V> {
+        div()
+            .text_xl()
+            .text_color(cx.theme().colors().text)
+            .child(title.to_owned())
+    }
+
+    pub fn title_for<V: 'static, T>(cx: &mut ViewContext<V>) -> impl Component<V> {
+        Self::title(cx, std::any::type_name::<T>())
+    }
+
+    pub fn label<V: 'static>(cx: &mut ViewContext<V>, label: &str) -> impl Component<V> {
+        div()
+            .mt_4()
+            .mb_2()
+            .text_xs()
+            .text_color(cx.theme().colors().text)
+            .child(label.to_owned())
+    }
+}

crates/theme2/src/theme2.rs 🔗

@@ -1,6 +1,7 @@
 mod colors;
 mod default_colors;
 mod default_theme;
+mod players;
 mod registry;
 mod scale;
 mod settings;
@@ -14,6 +15,7 @@ use ::settings::Settings;
 pub use colors::*;
 pub use default_colors::*;
 pub use default_theme::*;
+pub use players::*;
 pub use registry::*;
 pub use scale::*;
 pub use settings::*;
@@ -120,3 +122,8 @@ pub struct DiagnosticStyle {
     pub hint: Hsla,
     pub ignored: Hsla,
 }
+
+#[cfg(feature = "stories")]
+mod story;
+#[cfg(feature = "stories")]
+pub use story::*;