Add `TrafficLights` component

Marshall Bowers created

Change summary

crates/storybook2/src/stories/components.rs                |  1 
crates/storybook2/src/stories/components/traffic_lights.rs | 28 ++
crates/storybook2/src/story_selector.rs                    |  2 
crates/ui2/src/components.rs                               |  2 
crates/ui2/src/components/traffic_lights.rs                | 84 ++++++++
5 files changed, 117 insertions(+)

Detailed changes

crates/storybook2/src/stories/components/traffic_lights.rs 🔗

@@ -0,0 +1,28 @@
+use std::marker::PhantomData;
+
+use ui::prelude::*;
+use ui::TrafficLights;
+
+use crate::story::Story;
+
+#[derive(Element)]
+pub struct TrafficLightsStory<S: 'static + Send + Sync> {
+    state_type: PhantomData<S>,
+}
+
+impl<S: 'static + Send + Sync> TrafficLightsStory<S> {
+    pub fn new() -> Self {
+        Self {
+            state_type: PhantomData,
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        Story::container(cx)
+            .child(Story::title_for::<_, TrafficLights<S>>(cx))
+            .child(Story::label(cx, "Default"))
+            .child(TrafficLights::new())
+            .child(Story::label(cx, "Unfocused"))
+            .child(TrafficLights::new().window_has_focus(false))
+    }
+}

crates/storybook2/src/story_selector.rs 🔗

@@ -46,6 +46,7 @@ pub enum ComponentStory {
     TabBar,
     Terminal,
     Toolbar,
+    TrafficLights,
     Workspace,
 }
 
@@ -66,6 +67,7 @@ impl ComponentStory {
             Self::TabBar => components::tab_bar::TabBarStory::new().into_any(),
             Self::Terminal => components::terminal::TerminalStory::new().into_any(),
             Self::Toolbar => components::toolbar::ToolbarStory::new().into_any(),
+            Self::TrafficLights => components::traffic_lights::TrafficLightsStory::new().into_any(),
             Self::Workspace => components::workspace::WorkspaceStory::new().into_any(),
         }
     }

crates/ui2/src/components.rs 🔗

@@ -13,6 +13,7 @@ mod tab;
 mod tab_bar;
 mod terminal;
 mod toolbar;
+mod traffic_lights;
 mod workspace;
 
 pub use assistant_panel::*;
@@ -30,4 +31,5 @@ pub use tab::*;
 pub use tab_bar::*;
 pub use terminal::*;
 pub use toolbar::*;
+pub use traffic_lights::*;
 pub use workspace::*;

crates/ui2/src/components/traffic_lights.rs 🔗

@@ -0,0 +1,84 @@
+use std::marker::PhantomData;
+
+use crate::prelude::*;
+use crate::{theme, token, SystemColor};
+
+#[derive(Clone, Copy)]
+enum TrafficLightColor {
+    Red,
+    Yellow,
+    Green,
+}
+
+#[derive(Element)]
+struct TrafficLight<S: 'static + Send + Sync> {
+    state_type: PhantomData<S>,
+    color: TrafficLightColor,
+    window_has_focus: bool,
+}
+
+impl<S: 'static + Send + Sync> TrafficLight<S> {
+    fn new(color: TrafficLightColor, window_has_focus: bool) -> Self {
+        Self {
+            state_type: PhantomData,
+            color,
+            window_has_focus,
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+        let system_color = SystemColor::new();
+
+        let fill = match (self.window_has_focus, self.color) {
+            (true, TrafficLightColor::Red) => system_color.mac_os_traffic_light_red,
+            (true, TrafficLightColor::Yellow) => system_color.mac_os_traffic_light_yellow,
+            (true, TrafficLightColor::Green) => system_color.mac_os_traffic_light_green,
+            (false, _) => theme.lowest.base.active.background,
+        };
+
+        div().w_3().h_3().rounded_full().fill(fill)
+    }
+}
+
+#[derive(Element)]
+pub struct TrafficLights<S: 'static + Send + Sync> {
+    state_type: PhantomData<S>,
+    window_has_focus: bool,
+}
+
+impl<S: 'static + Send + Sync> TrafficLights<S> {
+    pub fn new() -> Self {
+        Self {
+            state_type: PhantomData,
+            window_has_focus: true,
+        }
+    }
+
+    pub fn window_has_focus(mut self, window_has_focus: bool) -> Self {
+        self.window_has_focus = window_has_focus;
+        self
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+        let token = token();
+
+        div()
+            .flex()
+            .items_center()
+            .gap_2()
+            .child(TrafficLight::new(
+                TrafficLightColor::Red,
+                self.window_has_focus,
+            ))
+            .child(TrafficLight::new(
+                TrafficLightColor::Yellow,
+                self.window_has_focus,
+            ))
+            .child(TrafficLight::new(
+                TrafficLightColor::Green,
+                self.window_has_focus,
+            ))
+    }
+}