From 23ad470daf82eb370f959eef6144313d6eea6972 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 22 Oct 2024 16:33:59 -0300 Subject: [PATCH] ssh: Add UI refinements to the remote modals (#19558) This PR polishes spacing, borders, header design, font size, etc. Release Notes: - N/A --- crates/recent_projects/src/remote_servers.rs | 124 +++++++++++------- crates/recent_projects/src/ssh_connections.rs | 70 +++++----- crates/vim/src/replace.rs | 1 + 3 files changed, 106 insertions(+), 89 deletions(-) diff --git a/crates/recent_projects/src/remote_servers.rs b/crates/recent_projects/src/remote_servers.rs index 0709516ed5..f0d4adc171 100644 --- a/crates/recent_projects/src/remote_servers.rs +++ b/crates/recent_projects/src/remote_servers.rs @@ -18,8 +18,8 @@ use gpui::ClipboardItem; use gpui::Task; use gpui::WeakView; use gpui::{ - AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, FontWeight, - Model, PromptLevel, ScrollHandle, View, ViewContext, + AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model, + PromptLevel, ScrollHandle, View, ViewContext, }; use picker::Picker; use project::terminals::wrap_for_ssh; @@ -33,10 +33,10 @@ use task::HideStrategy; use task::RevealStrategy; use task::SpawnInTerminal; use terminal_view::terminal_panel::TerminalPanel; -use ui::Scrollbar; -use ui::ScrollbarState; -use ui::Section; -use ui::{prelude::*, IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Tooltip}; +use ui::{ + prelude::*, IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Scrollbar, + ScrollbarState, Section, Tooltip, +}; use util::ResultExt; use workspace::notifications::NotificationId; use workspace::OpenOptions; @@ -316,7 +316,12 @@ impl gpui::Render for ProjectPicker { } .render(cx), ) - .child(self.picker.clone()) + .child( + div() + .border_t_1() + .border_color(cx.theme().colors().border_variant) + .child(self.picker.clone()), + ) } } enum Mode { @@ -366,18 +371,34 @@ impl RemoteServerProjects { } } + fn scroll_to_selected(&self, _: &mut ViewContext) { + if let Mode::Default(scroll_state) = &self.mode { + if let ui::ScrollableHandle::NonUniform(scroll_handle) = scroll_state.scroll_handle() { + if let Some(active_item) = self.selectable_items.active_item { + scroll_handle.scroll_to_item(active_item); + } + } + } + } + fn next_item(&mut self, _: &menu::SelectNext, cx: &mut ViewContext) { if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) { return; } self.selectable_items.next(cx); + cx.notify(); + self.scroll_to_selected(cx); } + fn prev_item(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext) { if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) { return; } self.selectable_items.prev(cx); + cx.notify(); + self.scroll_to_selected(cx); } + pub fn project_picker( ix: usize, connection_options: remote::SshConnectionOptions, @@ -654,7 +675,6 @@ impl RemoteServerProjects { .child( Label::new(main_label) .size(LabelSize::Small) - .weight(FontWeight::SEMIBOLD) .color(Color::Muted), ) .children( @@ -951,7 +971,8 @@ impl RemoteServerProjects { ) .child( v_flex() - .py_1() + .pb_1() + .child(ListSeparator) .child({ self.selectable_items.add_item(Box::new({ move |this, cx| { @@ -1135,7 +1156,13 @@ impl RemoteServerProjects { } .render(cx), ) - .child(h_flex().p_2().child(state.editor.clone())) + .child( + h_flex() + .p_2() + .border_t_1() + .border_color(cx.theme().colors().border_variant) + .child(state.editor.clone()), + ) } fn render_default( @@ -1178,26 +1205,21 @@ impl RemoteServerProjects { .size_full() .child(connect_button) .child( - h_flex().child( - List::new() - .empty_message( - v_flex() - .child(ListSeparator) - .child( - div().px_3().child( - Label::new("No remote servers registered yet.") - .color(Color::Muted), - ), - ) - .into_any_element(), - ) - .children(ssh_connections.iter().cloned().enumerate().map( - |(ix, connection)| { - self.render_ssh_connection(ix, connection, cx) - .into_any_element() - }, - )), - ), + List::new() + .empty_message( + v_flex() + // .child(ListSeparator) + .child(div().px_3().child( + Label::new("No remote servers registered yet.").color(Color::Muted), + )) + .into_any_element(), + ) + .children(ssh_connections.iter().cloned().enumerate().map( + |(ix, connection)| { + self.render_ssh_connection(ix, connection, cx) + .into_any_element() + }, + )), ) .into_any_element(); @@ -1208,36 +1230,38 @@ impl RemoteServerProjects { ) .section( Section::new().padded(false).child( - h_flex() + v_flex() .min_h(rems(20.)) + .group("remote-projects-section") .size_full() + .relative() + .child(ListSeparator) .child( - v_flex().size_full().child(ListSeparator).child( - canvas( - |bounds, cx| { - modal_section.prepaint_as_root( - bounds.origin, - bounds.size.into(), - cx, - ); - modal_section - }, - |_, mut modal_section, cx| { - modal_section.paint(cx); - }, - ) - .size_full(), - ), + canvas( + |bounds, cx| { + modal_section.prepaint_as_root( + bounds.origin, + bounds.size.into(), + cx, + ); + modal_section + }, + |_, mut modal_section, cx| { + modal_section.paint(cx); + }, + ) + .size_full(), ) .child( div() + .visible_on_hover("remote-projects-section") .occlude() .h_full() .absolute() - .right_1() .top_1() .bottom_1() - .w(px(12.)) + .right_1() + .w(px(8.)) .children(Scrollbar::vertical(scroll_state)), ), ), @@ -1268,6 +1292,7 @@ impl Render for RemoteServerProjects { div() .track_focus(&self.focus_handle) .elevation_3(cx) + .w(rems(34.)) .key_context("RemoteServerModal") .on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::confirm)) @@ -1281,7 +1306,6 @@ impl Render for RemoteServerProjects { cx.emit(DismissEvent) } })) - .w(rems(34.)) .child(match &self.mode { Mode::Default(state) => self.render_default(state.clone(), cx).into_any_element(), Mode::ViewServerOptions(index, connection) => self diff --git a/crates/recent_projects/src/ssh_connections.rs b/crates/recent_projects/src/ssh_connections.rs index 7c5d0920cd..0a0fa596bf 100644 --- a/crates/recent_projects/src/ssh_connections.rs +++ b/crates/recent_projects/src/ssh_connections.rs @@ -5,7 +5,7 @@ use auto_update::AutoUpdater; use editor::Editor; use futures::channel::oneshot; use gpui::{ - percentage, px, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent, + percentage, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent, EventEmitter, FocusableView, ParentElement as _, PromptLevel, Render, SemanticVersion, SharedString, Task, TextStyleRefinement, Transformation, View, }; @@ -20,9 +20,8 @@ use serde::{Deserialize, Serialize}; use settings::{Settings, SettingsSources}; use theme::ThemeSettings; use ui::{ - div, h_flex, prelude::*, v_flex, ActiveTheme, Color, Icon, IconName, IconSize, - InteractiveElement, IntoElement, Label, LabelCommon, Styled, ViewContext, VisualContext, - WindowContext, + prelude::*, ActiveTheme, Color, Icon, IconName, IconSize, InteractiveElement, IntoElement, + Label, LabelCommon, Styled, ViewContext, VisualContext, WindowContext, }; use workspace::{AppState, ModalView, Workspace}; @@ -52,6 +51,7 @@ impl SshSettings { }) .next() } + pub fn nickname_for( &self, host: &str, @@ -86,6 +86,7 @@ pub struct SshConnection { #[serde(default)] pub args: Vec, } + impl From for SshConnectionOptions { fn from(val: SshConnection) -> Self { SshConnectionOptions { @@ -205,15 +206,17 @@ impl SshPrompt { impl Render for SshPrompt { fn render(&mut self, cx: &mut ViewContext) -> impl IntoElement { let cx = cx.window_context(); - let theme = cx.theme(); + v_flex() .key_context("PasswordPrompt") + .py_2() + .px_3() .size_full() + .text_buffer(cx) .when_some(self.status_message.clone(), |el, status_message| { el.child( h_flex() - .p_2() - .flex() + .gap_1() .child( Icon::new(IconName::ArrowCircle) .size(IconSize::Medium) @@ -225,9 +228,12 @@ impl Render for SshPrompt { }, ), ) - .child(div().ml_1().text_ellipsis().overflow_x_hidden().child( - Label::new(format!("{}…", status_message)).size(LabelSize::Small), - )), + .child( + div() + .text_ellipsis() + .overflow_x_hidden() + .child(format!("{}…", status_message)), + ), ) }) .when_some(self.prompt.as_ref(), |el, prompt| { @@ -235,11 +241,6 @@ impl Render for SshPrompt { div() .size_full() .overflow_hidden() - .p_4() - .border_t_1() - .border_color(theme.colors().border_variant) - .font_buffer(cx) - .text_buffer(cx) .child(prompt.0.clone()) .child(self.editor.clone()), ) @@ -292,29 +293,18 @@ impl RenderOnce for SshConnectionHeader { }; h_flex() - .p_1() + .px(Spacing::XLarge.rems(cx)) + .pt(Spacing::Large.rems(cx)) + .pb(Spacing::Small.rems(cx)) .rounded_t_md() .w_full() - .gap_2() - .justify_center() - .border_b_1() - .border_color(theme.colors().border_variant) - .bg(header_color) + .gap_1p5() .child(Icon::new(IconName::Server).size(IconSize::XSmall)) .child( h_flex() .gap_1() - .child( - Label::new(main_label) - .size(ui::LabelSize::Small) - .single_line(), - ) - .children(meta_label.map(|label| { - Label::new(label) - .size(ui::LabelSize::Small) - .single_line() - .color(Color::Muted) - })), + .child(Headline::new(main_label).size(HeadlineSize::XSmall)) + .children(meta_label.map(|label| Label::new(label).color(Color::Muted))), ) } } @@ -323,18 +313,18 @@ impl Render for SshConnectionModal { fn render(&mut self, cx: &mut ui::ViewContext) -> impl ui::IntoElement { let nickname = self.prompt.read(cx).nickname.clone(); let connection_string = self.prompt.read(cx).connection_string.clone(); - let theme = cx.theme(); + let theme = cx.theme().clone(); let body_color = theme.colors().editor_background; v_flex() .elevation_3(cx) + .w(rems(34.)) + .border_1() + .border_color(theme.colors().border) .track_focus(&self.focus_handle(cx)) .on_action(cx.listener(Self::dismiss)) .on_action(cx.listener(Self::confirm)) - .w(px(500.)) - .border_1() - .border_color(theme.colors().border) .child( SshConnectionHeader { connection_string, @@ -343,10 +333,12 @@ impl Render for SshConnectionModal { .render(cx), ) .child( - h_flex() - .rounded_b_md() - .bg(body_color) + div() .w_full() + .rounded_b_lg() + .bg(body_color) + .border_t_1() + .border_color(theme.colors().border_variant) .child(self.prompt.clone()), ) } diff --git a/crates/vim/src/replace.rs b/crates/vim/src/replace.rs index fd4dbaae43..6d4e5eeb67 100644 --- a/crates/vim/src/replace.rs +++ b/crates/vim/src/replace.rs @@ -145,6 +145,7 @@ mod test { } #[gpui::test] + #[cfg(not(target_os = "linux"))] async fn test_replace_mode(cx: &mut gpui::TestAppContext) { let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;