Add rough versions of all 3 modals

Mikayla Maki created

Change summary

crates/copilot/src/copilot.rs   |  37 --
crates/copilot/src/sign_in.rs   | 514 ++++++++++++++++++++++++----------
crates/gpui/src/app.rs          |   6 
crates/theme/src/theme.rs       |  31 +
crates/theme/src/ui.rs          |  17 
styles/src/styleTree/copilot.ts |  79 ++++
6 files changed, 479 insertions(+), 205 deletions(-)

Detailed changes

crates/copilot/src/copilot.rs 🔗

@@ -155,7 +155,10 @@ impl Copilot {
                 SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => {
                     Task::ready(Ok(())).shared()
                 }
-                SignInStatus::SigningIn { task, .. } => task.clone(),
+                SignInStatus::SigningIn { task, .. } => {
+                    cx.notify(); // To re-show the prompt, just in case.
+                    task.clone()
+                }
                 SignInStatus::SignedOut => {
                     let server = server.clone();
                     let task = cx
@@ -463,35 +466,3 @@ async fn get_lsp_binary(http: Arc<dyn HttpClient>) -> anyhow::Result<PathBuf> {
         }
     }
 }
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use gpui::TestAppContext;
-    use util::http;
-
-    #[gpui::test]
-    async fn test_smoke(cx: &mut TestAppContext) {
-        Settings::test_async(cx);
-        let http = http::client();
-        let node_runtime = NodeRuntime::new(http.clone(), cx.background());
-        let copilot = cx.add_model(|cx| Copilot::start(http, node_runtime, cx));
-        smol::Timer::after(std::time::Duration::from_secs(2)).await;
-        copilot
-            .update(cx, |copilot, cx| copilot.sign_in(cx))
-            .await
-            .unwrap();
-        copilot.read_with(cx, |copilot, _| copilot.status());
-
-        let buffer = cx.add_model(|cx| language::Buffer::new(0, "fn foo() -> ", cx));
-        dbg!(copilot
-            .update(cx, |copilot, cx| copilot.completion(&buffer, 12, cx))
-            .await
-            .unwrap());
-        dbg!(copilot
-            .update(cx, |copilot, cx| copilot
-                .completions_cycling(&buffer, 12, cx))
-            .await
-            .unwrap());
-    }
-}

crates/copilot/src/sign_in.rs 🔗

@@ -13,72 +13,318 @@ struct OpenGithub;
 
 impl_internal_actions!(copilot_sign_in, [CopyUserCode, OpenGithub]);
 
+const _COPILOT_SIGN_UP_URL: &'static str = "https://github.com/features/copilot";
+
+enum SignInContents {
+    PromptingUser(PromptUserDeviceFlow),
+    Unauthorized,
+    Enabled,
+}
+
 pub fn init(cx: &mut MutableAppContext) {
     let copilot = Copilot::global(cx).unwrap();
 
-    let mut code_verification_window_id = None;
+    let mut code_verification_window_id: Option<(usize, SignInContents)> = None;
     cx.observe(&copilot, move |copilot, cx| {
         match copilot.read(cx).status() {
             crate::Status::SigningIn {
                 prompt: Some(prompt),
             } => {
-                if let Some(window_id) = code_verification_window_id.take() {
-                    cx.remove_window(window_id);
-                }
+                let window_id = match code_verification_window_id.take() {
+                    Some((window_id, SignInContents::PromptingUser(current_prompt)))
+                        if current_prompt == prompt =>
+                    {
+                        if cx.window_ids().find(|item| item == &window_id).is_some() {
+                            window_id
+                        } else {
+                            CopilotCodeVerification::prompting(prompt.clone(), cx)
+                        }
+                    }
+                    Some((window_id, _)) => {
+                        cx.remove_window(window_id);
+                        CopilotCodeVerification::prompting(prompt.clone(), cx)
+                    }
+                    None => CopilotCodeVerification::prompting(prompt.clone(), cx),
+                };
 
-                let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
-
-                let (window_id, _) = cx.add_window(
-                    WindowOptions {
-                        bounds: gpui::WindowBounds::Fixed(RectF::new(
-                            Default::default(),
-                            window_size,
-                        )),
-                        titlebar: None,
-                        center: true,
-                        focus: false,
-                        kind: WindowKind::Normal,
-                        is_movable: true,
-                        screen: None,
-                    },
-                    |_| CopilotCodeVerification::new(prompt),
-                );
-                code_verification_window_id = Some(window_id);
+                code_verification_window_id =
+                    Some((window_id, SignInContents::PromptingUser(prompt)));
 
                 cx.activate_window(window_id);
             }
+            crate::Status::Authorized => match code_verification_window_id.take() {
+                Some((window_id, sign_in_contents)) => {
+                    match sign_in_contents {
+                        SignInContents::PromptingUser(_) => cx.remove_window(window_id),
+                        SignInContents::Unauthorized => cx.remove_window(window_id),
+                        SignInContents::Enabled => {
+                            if cx.has_window(window_id) {
+                                code_verification_window_id =
+                                    Some((window_id, SignInContents::Enabled))
+                            }
+                            return;
+                        }
+                    }
+                    let window_id = CopilotCodeVerification::enabled(cx);
+                    code_verification_window_id = Some((window_id, SignInContents::Enabled));
+                    cx.activate_window(window_id);
+                }
+                None => return,
+            },
+            crate::Status::Unauthorized => match code_verification_window_id.take() {
+                Some((window_id, sign_in_contents)) => {
+                    match sign_in_contents {
+                        SignInContents::PromptingUser(_) => cx.remove_window(window_id), // Show prompt
+                        SignInContents::Unauthorized => {
+                            if cx.has_window(window_id) {
+                                code_verification_window_id =
+                                    Some((window_id, SignInContents::Unauthorized))
+                            }
+                            return;
+                        } //Do nothing
+                        SignInContents::Enabled => cx.remove_window(window_id),          //
+                    }
+
+                    let window_id = CopilotCodeVerification::unauthorized(cx);
+                    code_verification_window_id = Some((window_id, SignInContents::Unauthorized));
+                    cx.activate_window(window_id);
+                }
+                None => return,
+            },
             _ => {
-                if let Some(window_id) = code_verification_window_id.take() {
+                if let Some((window_id, _)) = code_verification_window_id.take() {
                     cx.remove_window(window_id);
                 }
             }
         }
     })
     .detach();
-
-    // let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
-
-    // let (_window_id, _) = cx.add_window(
-    //     WindowOptions {
-    //         bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
-    //         titlebar: None,
-    //         center: true,
-    //         focus: false,
-    //         kind: WindowKind::PopUp,
-    //         is_movable: true,
-    //         screen: None,
-    //     },
-    //     |_| {
-    //         CopilotCodeVerification::new(PromptUserDeviceFlow {
-    //             user_code: "ABCD-1234".to_string(),
-    //             verification_uri: "https://github.com/login/device".to_string(),
-    //         })
-    //     },
-    // );
 }
 
 pub struct CopilotCodeVerification {
-    prompt: PromptUserDeviceFlow,
+    prompt: SignInContents,
+}
+
+impl CopilotCodeVerification {
+    pub fn prompting(prompt: PromptUserDeviceFlow, cx: &mut MutableAppContext) -> usize {
+        let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
+
+        let (window_id, _) = cx.add_window(
+            WindowOptions {
+                bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
+                titlebar: None,
+                center: true,
+                focus: false,
+                kind: WindowKind::Normal,
+                is_movable: true,
+                screen: None,
+            },
+            |_| CopilotCodeVerification {
+                prompt: SignInContents::PromptingUser(prompt),
+            },
+        );
+
+        window_id
+    }
+
+    pub fn unauthorized(cx: &mut MutableAppContext) -> usize {
+        let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
+
+        let (window_id, _) = cx.add_window(
+            WindowOptions {
+                bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
+                titlebar: None,
+                center: true,
+                focus: false,
+                kind: WindowKind::Normal,
+                is_movable: true,
+                screen: None,
+            },
+            |_| CopilotCodeVerification {
+                prompt: SignInContents::Unauthorized,
+            },
+        );
+
+        window_id
+    }
+
+    pub fn enabled(cx: &mut MutableAppContext) -> usize {
+        let window_size = cx.global::<Settings>().theme.copilot.modal.dimensions();
+
+        let (window_id, _) = cx.add_window(
+            WindowOptions {
+                bounds: gpui::WindowBounds::Fixed(RectF::new(Default::default(), window_size)),
+                titlebar: None,
+                center: true,
+                focus: false,
+                kind: WindowKind::Normal,
+                is_movable: true,
+                screen: None,
+            },
+            |_| CopilotCodeVerification {
+                prompt: SignInContents::Enabled,
+            },
+        );
+
+        window_id
+    }
+
+    fn render_device_code(
+        data: &PromptUserDeviceFlow,
+        style: &theme::Copilot,
+        cx: &mut gpui::RenderContext<Self>,
+    ) -> ElementBox {
+        let copied = cx
+            .read_from_clipboard()
+            .map(|item| item.text() == &data.user_code)
+            .unwrap_or(false);
+
+        Flex::column()
+            .with_children([
+                MouseEventHandler::<Self>::new(0, cx, |state, _cx| {
+                    Flex::row()
+                        .with_children([
+                            Label::new(data.user_code.clone(), style.auth.device_code.clone())
+                                .aligned()
+                                .contained()
+                                .with_style(style.auth.device_code_left_container)
+                                .constrained()
+                                .with_width(style.auth.device_code_left)
+                                .boxed(),
+                            Empty::new()
+                                .constrained()
+                                .with_width(1.)
+                                .with_height(style.auth.device_code_seperator_height)
+                                .contained()
+                                .with_background_color(
+                                    style
+                                        .auth
+                                        .cta_button
+                                        .style_for(state, false)
+                                        .container
+                                        .border
+                                        .color,
+                                )
+                                .boxed(),
+                            Label::new(
+                                if copied { "Copied!" } else { "Copy" },
+                                style.auth.cta_button.style_for(state, false).text.clone(),
+                            )
+                            .aligned()
+                            .contained()
+                            .with_style(style.auth.device_code_right_container)
+                            .constrained()
+                            .with_width(style.auth.device_code_right)
+                            .boxed(),
+                        ])
+                        .contained()
+                        .with_style(style.auth.device_code_cta.style_for(state, false).container)
+                        .constrained()
+                        .with_width(style.auth.content_width)
+                        .boxed()
+                })
+                .on_click(gpui::MouseButton::Left, {
+                    let user_code = data.user_code.clone();
+                    move |_, cx| {
+                        cx.platform()
+                            .write_to_clipboard(ClipboardItem::new(user_code.clone()));
+                        cx.notify();
+                    }
+                })
+                .with_cursor_style(gpui::CursorStyle::PointingHand)
+                .boxed(),
+                Flex::column()
+                    .with_children([
+                        Label::new(
+                            "Paste this code into GitHub after",
+                            style.auth.hint.text.clone(),
+                        )
+                        .boxed(),
+                        Label::new("clicking the button below.", style.auth.hint.text.clone())
+                            .boxed(),
+                    ])
+                    .align_children_center()
+                    .contained()
+                    .with_style(style.auth.hint.container.clone())
+                    .boxed(),
+            ])
+            .align_children_center()
+            .contained()
+            .with_style(style.auth.device_code_group)
+            .aligned()
+            .boxed()
+    }
+
+    fn render_not_authorized_warning(style: &theme::Copilot) -> ElementBox {
+        Flex::column()
+            .with_children([
+                Flex::column()
+                    .with_children([
+                        Label::new(
+                            "You must have an active copilot",
+                            style.auth.warning.text.to_owned(),
+                        )
+                        .aligned()
+                        .boxed(),
+                        Label::new(
+                            "license to use it in Zed.",
+                            style.auth.warning.text.to_owned(),
+                        )
+                        .aligned()
+                        .boxed(),
+                    ])
+                    .align_children_center()
+                    .contained()
+                    .with_style(style.auth.warning.container)
+                    .boxed(),
+                Flex::column()
+                    .with_children([
+                        Label::new(
+                            "Try connecting again once you",
+                            style.auth.hint.text.to_owned(),
+                        )
+                        .aligned()
+                        .boxed(),
+                        Label::new(
+                            "have activated a Copilot license.",
+                            style.auth.hint.text.to_owned(),
+                        )
+                        .aligned()
+                        .boxed(),
+                    ])
+                    .align_children_center()
+                    .contained()
+                    .with_style(style.auth.not_authorized_hint)
+                    .boxed(),
+            ])
+            .align_children_center()
+            .boxed()
+    }
+
+    fn render_copilot_enabled(style: &theme::Copilot) -> ElementBox {
+        Flex::column()
+            .with_children([
+                Label::new(
+                    "You can update your settings or",
+                    style.auth.hint.text.clone(),
+                )
+                .aligned()
+                .boxed(),
+                Label::new(
+                    "sign out from the Copilot menu in",
+                    style.auth.hint.text.clone(),
+                )
+                .aligned()
+                .boxed(),
+                Label::new("the status bar.", style.auth.hint.text.clone())
+                    .aligned()
+                    .boxed(),
+            ])
+            .align_children_center()
+            .contained()
+            .with_style(style.auth.enabled_hint)
+            .boxed()
+    }
 }
 
 impl Entity for CopilotCodeVerification {
@@ -94,137 +340,107 @@ impl View for CopilotCodeVerification {
         cx.notify()
     }
 
+    fn focus_out(&mut self, _: gpui::AnyViewHandle, cx: &mut gpui::ViewContext<Self>) {
+        cx.notify()
+    }
+
     fn render(&mut self, cx: &mut gpui::RenderContext<'_, Self>) -> gpui::ElementBox {
         let style = cx.global::<Settings>().theme.copilot.clone();
 
-        let copied = cx
-            .read_from_clipboard()
-            .map(|item| item.text() == &self.prompt.user_code)
-            .unwrap_or(false);
-
-        theme::ui::modal("Authenticate Copilot", &style.modal, cx, |cx| {
+        theme::ui::modal("Connect Copilot to Zed", &style.modal, cx, |cx| {
             Flex::column()
-                .align_children_center()
                 .with_children([
                     Flex::column()
                         .with_children([
                             Flex::row()
                                 .with_children([
                                     theme::ui::svg(&style.auth.copilot_icon).boxed(),
-                                    theme::ui::svg(&style.auth.plus_icon).boxed(),
+                                    theme::ui::icon(&style.auth.plus_icon).boxed(),
                                     theme::ui::svg(&style.auth.zed_icon).boxed(),
                                 ])
                                 .boxed(),
-                            Label::new("Copilot for Zed", style.auth.header_text.clone()).boxed(),
-                        ])
-                        .align_children_center()
-                        .contained()
-                        .with_style(style.auth.header_group)
-                        .aligned()
-                        .boxed(),
-                    Flex::column()
-                        .with_children([
-                            Label::new(
-                                "Here is your code to authenticate with github",
-                                style.auth.instruction_text.clone(),
-                            )
-                            .boxed(),
-                            MouseEventHandler::<Self>::new(0, cx, |state, _cx| {
-                                Flex::row()
-                                    .with_children([
-                                        Label::new(
-                                            self.prompt.user_code.clone(),
-                                            style.auth.device_code.clone(),
-                                        )
-                                        .aligned()
-                                        .contained()
-                                        .with_style(style.auth.device_code_left_container)
-                                        .constrained()
-                                        .with_width(style.auth.device_code_left)
-                                        .boxed(),
-                                        Empty::new()
-                                            .constrained()
-                                            .with_width(1.)
-                                            .with_height(style.auth.device_code_seperator_height)
-                                            .contained()
-                                            .with_background_color(
-                                                style
-                                                    .auth
-                                                    .cta_button
-                                                    .style_for(state, false)
-                                                    .container
-                                                    .border
-                                                    .color,
+                            match self.prompt {
+                                SignInContents::PromptingUser(_) | SignInContents::Unauthorized => {
+                                    Flex::column()
+                                        .with_children([
+                                            Label::new(
+                                                "Enable Copilot by connecting",
+                                                style.auth.enable_text.clone(),
+                                            )
+                                            .boxed(),
+                                            Label::new(
+                                                "your existing license.",
+                                                style.auth.enable_text.clone(),
                                             )
                                             .boxed(),
-                                        Label::new(
-                                            if copied { "Copied!" } else { "Copy" },
-                                            style
-                                                .auth
-                                                .cta_button
-                                                .style_for(state, false)
-                                                .text
-                                                .clone(),
-                                        )
-                                        .aligned()
+                                        ])
+                                        .align_children_center()
                                         .contained()
-                                        .with_style(style.auth.device_code_right_container)
-                                        .constrained()
-                                        .with_width(style.auth.device_code_right)
-                                        .boxed(),
-                                    ])
-                                    .contained()
-                                    .with_style(
-                                        style
-                                            .auth
-                                            .device_code_cta
-                                            .style_for(state, false)
-                                            .container,
-                                    )
-                                    .constrained()
-                                    .with_width(style.auth.content_width)
-                                    .boxed()
-                            })
-                            .on_click(gpui::MouseButton::Left, {
-                                let user_code = self.prompt.user_code.clone();
-                                move |_, cx| {
-                                    cx.platform()
-                                        .write_to_clipboard(ClipboardItem::new(user_code.clone()));
-                                    cx.notify();
+                                        .with_style(style.auth.enable_group.clone())
+                                        .boxed()
                                 }
-                            })
-                            .with_cursor_style(gpui::CursorStyle::PointingHand)
-                            .boxed(),
+                                SignInContents::Enabled => {
+                                    Label::new("Copilot Enabled!", style.auth.enable_text.clone())
+                                        .boxed()
+                                }
+                            },
                         ])
                         .align_children_center()
                         .contained()
-                        .with_style(style.auth.device_code_group)
+                        .with_style(style.auth.header_group)
                         .aligned()
                         .boxed(),
+                    match &self.prompt {
+                        SignInContents::PromptingUser(data) => {
+                            Self::render_device_code(data, &style, cx)
+                        }
+                        SignInContents::Unauthorized => Self::render_not_authorized_warning(&style),
+                        SignInContents::Enabled => Self::render_copilot_enabled(&style),
+                    },
                     Flex::column()
-                        .with_children([
-                            Label::new(
-                                "Copy it and enter it on GitHub",
-                                style.auth.instruction_text.clone(),
-                            )
-                            .boxed(),
-                            theme::ui::cta_button_with_click(
-                                "Go to Github",
-                                style.auth.content_width,
-                                &style.auth.cta_button,
-                                cx,
-                                {
-                                    let verification_uri = self.prompt.verification_uri.clone();
-                                    move |_, cx| cx.platform().open_url(&verification_uri)
-                                },
-                            ),
-                        ])
+                        .with_child({
+                            match &self.prompt {
+                                SignInContents::PromptingUser(data) => {
+                                    theme::ui::cta_button_with_click(
+                                        "Connect to GitHub",
+                                        style.auth.content_width,
+                                        &style.auth.cta_button,
+                                        cx,
+                                        {
+                                            let verification_uri = data.verification_uri.clone();
+                                            move |_, cx| cx.platform().open_url(&verification_uri)
+                                        },
+                                    )
+                                }
+                                SignInContents::Unauthorized => theme::ui::cta_button_with_click(
+                                    "Close",
+                                    style.auth.content_width,
+                                    &style.auth.cta_button,
+                                    cx,
+                                    |_, cx| {
+                                        let window_id = cx.window_id();
+                                        cx.remove_window(window_id)
+                                    },
+                                ),
+                                SignInContents::Enabled => theme::ui::cta_button_with_click(
+                                    "Done",
+                                    style.auth.content_width,
+                                    &style.auth.cta_button,
+                                    cx,
+                                    |_, cx| {
+                                        let window_id = cx.window_id();
+                                        cx.remove_window(window_id)
+                                    },
+                                ),
+                            }
+                        })
                         .align_children_center()
                         .contained()
                         .with_style(style.auth.github_group)
                         .aligned()
                         .boxed(),
                 ])
+                .align_children_center()
                 .constrained()
                 .with_width(style.auth.content_width)
                 .aligned()
@@ -232,9 +448,3 @@ impl View for CopilotCodeVerification {
         })
     }
 }
-
-impl CopilotCodeVerification {
-    pub fn new(prompt: PromptUserDeviceFlow) -> Self {
-        CopilotCodeVerification { prompt }
-    }
-}

crates/gpui/src/app.rs 🔗

@@ -765,6 +765,12 @@ impl MutableAppContext {
             })
     }
 
+    pub fn has_window(&self, window_id: usize) -> bool {
+        self.window_ids()
+            .find(|window| window == &window_id)
+            .is_some()
+    }
+
     pub fn window_ids(&self) -> impl Iterator<Item = usize> + '_ {
         self.cx.windows.keys().copied()
     }

crates/theme/src/theme.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
 use serde::{de::DeserializeOwned, Deserialize};
 use serde_json::Value;
 use std::{collections::HashMap, sync::Arc};
-use ui::{ButtonStyle, CheckboxStyle, ModalStyle, SvgStyle};
+use ui::{ButtonStyle, CheckboxStyle, IconStyle, ModalStyle, SvgStyle};
 
 pub mod ui;
 
@@ -124,13 +124,14 @@ pub struct Copilot {
 
 #[derive(Deserialize, Default, Clone)]
 pub struct CopilotAuth {
+    pub enable_group: ContainerStyle,
+    pub enable_text: TextStyle,
     pub instruction_text: TextStyle,
     pub cta_button: ButtonStyle,
     pub content_width: f32,
     pub copilot_icon: SvgStyle,
-    pub plus_icon: SvgStyle,
+    pub plus_icon: IconStyle,
     pub zed_icon: SvgStyle,
-    pub header_text: TextStyle,
     pub device_code_group: ContainerStyle,
     pub github_group: ContainerStyle,
     pub header_group: ContainerStyle,
@@ -141,6 +142,10 @@ pub struct CopilotAuth {
     pub device_code_right: f32,
     pub device_code_right_container: ContainerStyle,
     pub device_code_seperator_height: f32,
+    pub hint: ContainedText,
+    pub enabled_hint: ContainerStyle,
+    pub not_authorized_hint: ContainerStyle,
+    pub warning: ContainedText,
 }
 
 #[derive(Deserialize, Default)]
@@ -720,7 +725,9 @@ pub struct DiffStyle {
 pub struct Interactive<T> {
     pub default: T,
     pub hover: Option<T>,
+    pub hover_and_active: Option<T>,
     pub clicked: Option<T>,
+    pub click_and_active: Option<T>,
     pub active: Option<T>,
     pub disabled: Option<T>,
 }
@@ -728,7 +735,17 @@ pub struct Interactive<T> {
 impl<T> Interactive<T> {
     pub fn style_for(&self, state: &mut MouseState, active: bool) -> &T {
         if active {
-            self.active.as_ref().unwrap_or(&self.default)
+            if state.hovered() {
+                self.hover_and_active
+                    .as_ref()
+                    .unwrap_or(self.active.as_ref().unwrap_or(&self.default))
+            } else if state.clicked() == Some(gpui::MouseButton::Left) && self.clicked.is_some() {
+                self.click_and_active
+                    .as_ref()
+                    .unwrap_or(self.active.as_ref().unwrap_or(&self.default))
+            } else {
+                self.active.as_ref().unwrap_or(&self.default)
+            }
         } else if state.clicked() == Some(gpui::MouseButton::Left) && self.clicked.is_some() {
             self.clicked.as_ref().unwrap()
         } else if state.hovered() {
@@ -753,7 +770,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
             #[serde(flatten)]
             default: Value,
             hover: Option<Value>,
+            hover_and_active: Option<Value>,
             clicked: Option<Value>,
+            click_and_active: Option<Value>,
             active: Option<Value>,
             disabled: Option<Value>,
         }
@@ -780,7 +799,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
         };
 
         let hover = deserialize_state(json.hover)?;
+        let hover_and_active = deserialize_state(json.hover_and_active)?;
         let clicked = deserialize_state(json.clicked)?;
+        let click_and_active = deserialize_state(json.click_and_active)?;
         let active = deserialize_state(json.active)?;
         let disabled = deserialize_state(json.disabled)?;
         let default = serde_json::from_value(json.default).map_err(serde::de::Error::custom)?;
@@ -788,7 +809,9 @@ impl<'de, T: DeserializeOwned> Deserialize<'de> for Interactive<T> {
         Ok(Interactive {
             default,
             hover,
+            hover_and_active,
             clicked,
+            click_and_active,
             active,
             disabled,
         })

crates/theme/src/ui.rs 🔗

@@ -9,7 +9,7 @@ use gpui::{
     fonts::TextStyle,
     geometry::vector::{vec2f, Vector2F},
     scene::MouseClick,
-    Action, Element, ElementBox, EventContext, MouseButton, RenderContext, View,
+    Action, Element, ElementBox, EventContext, MouseButton, MouseState, RenderContext, View,
 };
 use serde::Deserialize;
 
@@ -220,7 +220,7 @@ pub struct ModalStyle {
     close_icon: Interactive<IconStyle>,
     container: ContainerStyle,
     titlebar: ContainerStyle,
-    title_text: TextStyle,
+    title_text: Interactive<TextStyle>,
     dimensions: Dimensions,
 }
 
@@ -241,14 +241,23 @@ where
     I: Into<Cow<'static, str>>,
     F: FnOnce(&mut gpui::RenderContext<V>) -> ElementBox,
 {
+    let active = cx.window_is_active(cx.window_id());
+
     Flex::column()
         .with_child(
             Stack::new()
                 .with_children([
-                    Label::new(title, style.title_text.clone()).boxed(),
+                    Label::new(
+                        title,
+                        style
+                            .title_text
+                            .style_for(&mut MouseState::default(), active)
+                            .clone(),
+                    )
+                    .boxed(),
                     // FIXME: Get a better tag type
                     MouseEventHandler::<V>::new(999999, cx, |state, _cx| {
-                        let style = style.close_icon.style_for(state, false);
+                        let style = style.close_icon.style_for(state, active);
                         icon(style).boxed()
                     })
                     .on_click(gpui::MouseButton::Left, move |_, cx| {

styles/src/styleTree/copilot.ts 🔗

@@ -31,12 +31,17 @@ export default function copilot(colorScheme: ColorScheme) {
 
     return {
         modal: {
-            titleText: text(layer, "sans", { size: "md" }),
+            titleText: {
+                ...text(layer, "sans", { size: "md", color: background(layer, "default") }),
+                active: {
+                    ...text(layer, "sans", { size: "md" }),
+                }
+            },
             titlebar: {
                 border: border(layer, "active"),
                 padding: {
-                    top: 4,
-                    bottom: 4,
+                    top: 8,
+                    bottom: 8,
                     left: 8,
                     right: 8,
                 },
@@ -44,7 +49,7 @@ export default function copilot(colorScheme: ColorScheme) {
                     top: 0,
                     left: 0,
                     right: 0,
-                    bottom: 8
+                    bottom: 16
                 }
             },
             container: {
@@ -54,6 +59,7 @@ export default function copilot(colorScheme: ColorScheme) {
             closeIcon: {
                 icon: svg(background(layer, "on"), "icons/x_mark_16.svg", 16, 16),
                 container: {
+                    cornerRadius: 2,
                     padding: {
                         top: 3,
                         bottom: 3,
@@ -61,8 +67,14 @@ export default function copilot(colorScheme: ColorScheme) {
                         right: 0,
                     }
                 },
-                hover: {
-                    icon: svg(foreground(layer, "on"), "icons/x_mark_16.svg", 16, 16),
+                active: {
+                    icon: svg(foreground(colorScheme.lowest, "warning"), "icons/x_mark_16.svg", 16, 16),
+                },
+                hoverAndActive: {
+                    icon: svg(foreground(layer, "on", "hovered"), "icons/x_mark_16.svg", 16, 16),
+                },
+                clickedAndactive: {
+                    icon: svg(foreground(layer, "on", "pressed"), "icons/x_mark_16.svg", 16, 16),
                 }
             },
             dimensions: {
@@ -81,17 +93,35 @@ export default function copilot(colorScheme: ColorScheme) {
                     right: 0
                 }
             },
-            headerText: text(layer, "sans", { size: "lg" }),
-            copilotIcon: svg(foreground(layer, "default"), "icons/github-copilot-dummy.svg", 36, 36),
-            plusIcon: svg(foreground(layer, "default"), "icons/plus_16.svg", 36, 36),
-            zedIcon: svg(foreground(layer, "default"), "icons/logo_96.svg", 36, 36),
+            copilotIcon: svg(foreground(layer, "default"), "icons/github-copilot-dummy.svg", 32, 32),
+            plusIcon: {
+                icon: svg(foreground(layer, "default"), "icons/plus_12.svg", 12, 12),
+                container: {
+                    padding: {
+                        top: 12,
+                        bottom: 12,
+                        left: 12,
+                        right: 12,
+                    }
+                }
+            },
+            zedIcon: svg(foreground(layer, "default"), "icons/logo_96.svg", 32, 32),
+            enableText: text(layer, "sans", { size: "md" }),
+            enableGroup: {
+                margin: {
+                    top: 5,
+                    bottom: 5,
+                    left: 0,
+                    right: 0
+                }
+            },
 
             instructionText: text(layer, "sans"),
 
             deviceCodeGroup: {
                 margin: {
-                    top: 5,
-                    bottom: 5,
+                    top: 20,
+                    bottom: 20,
                     left: 0,
                     right: 0
                 }
@@ -127,6 +157,31 @@ export default function copilot(colorScheme: ColorScheme) {
                 },
             },
             deviceCodeSeperatorHeight: 0,
+            hint: {
+                ...text(layer, "sans", { size: "xs" }),
+                margin: {
+                    top: -5,
+                }
+            },
+            enabledHint: {
+                margin: {
+                    top: 10,
+                    bottom: 10
+                }
+            },
+            notAuthorizedHint: {
+                margin: {
+                    top: 10,
+                    bottom: 10
+                }
+            },
+
+            warning: {
+                ...text(layer, "sans", { size: "md", color: foreground(layer, "warning") }),
+                border: border(layer, "warning"),
+                background_color: background(layer, "warning"),
+                cornerRadius: 2,
+            },
 
             githubGroup: {
                 margin: {