ssh: Add UI refinements to the remote modals (#19558)

This PR polishes spacing, borders, header design, font size, etc.

Release Notes:

- N/A
This commit is contained in:
Danilo Leal 2024-10-22 16:33:59 -03:00 committed by GitHub
parent 6dcec47235
commit 23ad470daf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 106 additions and 89 deletions

View file

@ -18,8 +18,8 @@ use gpui::ClipboardItem;
use gpui::Task; use gpui::Task;
use gpui::WeakView; use gpui::WeakView;
use gpui::{ use gpui::{
AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, FontWeight, AnyElement, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView, Model,
Model, PromptLevel, ScrollHandle, View, ViewContext, PromptLevel, ScrollHandle, View, ViewContext,
}; };
use picker::Picker; use picker::Picker;
use project::terminals::wrap_for_ssh; use project::terminals::wrap_for_ssh;
@ -33,10 +33,10 @@ use task::HideStrategy;
use task::RevealStrategy; use task::RevealStrategy;
use task::SpawnInTerminal; use task::SpawnInTerminal;
use terminal_view::terminal_panel::TerminalPanel; use terminal_view::terminal_panel::TerminalPanel;
use ui::Scrollbar; use ui::{
use ui::ScrollbarState; prelude::*, IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Scrollbar,
use ui::Section; ScrollbarState, Section, Tooltip,
use ui::{prelude::*, IconButtonShape, List, ListItem, ListSeparator, Modal, ModalHeader, Tooltip}; };
use util::ResultExt; use util::ResultExt;
use workspace::notifications::NotificationId; use workspace::notifications::NotificationId;
use workspace::OpenOptions; use workspace::OpenOptions;
@ -316,7 +316,12 @@ impl gpui::Render for ProjectPicker {
} }
.render(cx), .render(cx),
) )
.child(self.picker.clone()) .child(
div()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(self.picker.clone()),
)
} }
} }
enum Mode { enum Mode {
@ -366,18 +371,34 @@ impl RemoteServerProjects {
} }
} }
fn scroll_to_selected(&self, _: &mut ViewContext<Self>) {
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<Self>) { fn next_item(&mut self, _: &menu::SelectNext, cx: &mut ViewContext<Self>) {
if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) { if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) {
return; return;
} }
self.selectable_items.next(cx); self.selectable_items.next(cx);
cx.notify();
self.scroll_to_selected(cx);
} }
fn prev_item(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) { fn prev_item(&mut self, _: &menu::SelectPrev, cx: &mut ViewContext<Self>) {
if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) { if !matches!(self.mode, Mode::Default(_) | Mode::ViewServerOptions(_, _)) {
return; return;
} }
self.selectable_items.prev(cx); self.selectable_items.prev(cx);
cx.notify();
self.scroll_to_selected(cx);
} }
pub fn project_picker( pub fn project_picker(
ix: usize, ix: usize,
connection_options: remote::SshConnectionOptions, connection_options: remote::SshConnectionOptions,
@ -654,7 +675,6 @@ impl RemoteServerProjects {
.child( .child(
Label::new(main_label) Label::new(main_label)
.size(LabelSize::Small) .size(LabelSize::Small)
.weight(FontWeight::SEMIBOLD)
.color(Color::Muted), .color(Color::Muted),
) )
.children( .children(
@ -951,7 +971,8 @@ impl RemoteServerProjects {
) )
.child( .child(
v_flex() v_flex()
.py_1() .pb_1()
.child(ListSeparator)
.child({ .child({
self.selectable_items.add_item(Box::new({ self.selectable_items.add_item(Box::new({
move |this, cx| { move |this, cx| {
@ -1135,7 +1156,13 @@ impl RemoteServerProjects {
} }
.render(cx), .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( fn render_default(
@ -1178,26 +1205,21 @@ impl RemoteServerProjects {
.size_full() .size_full()
.child(connect_button) .child(connect_button)
.child( .child(
h_flex().child( List::new()
List::new() .empty_message(
.empty_message( v_flex()
v_flex() // .child(ListSeparator)
.child(ListSeparator) .child(div().px_3().child(
.child( Label::new("No remote servers registered yet.").color(Color::Muted),
div().px_3().child( ))
Label::new("No remote servers registered yet.") .into_any_element(),
.color(Color::Muted), )
), .children(ssh_connections.iter().cloned().enumerate().map(
) |(ix, connection)| {
.into_any_element(), self.render_ssh_connection(ix, connection, cx)
) .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(); .into_any_element();
@ -1208,36 +1230,38 @@ impl RemoteServerProjects {
) )
.section( .section(
Section::new().padded(false).child( Section::new().padded(false).child(
h_flex() v_flex()
.min_h(rems(20.)) .min_h(rems(20.))
.group("remote-projects-section")
.size_full() .size_full()
.relative()
.child(ListSeparator)
.child( .child(
v_flex().size_full().child(ListSeparator).child( canvas(
canvas( |bounds, cx| {
|bounds, cx| { modal_section.prepaint_as_root(
modal_section.prepaint_as_root( bounds.origin,
bounds.origin, bounds.size.into(),
bounds.size.into(), cx,
cx, );
); modal_section
modal_section },
}, |_, mut modal_section, cx| {
|_, mut modal_section, cx| { modal_section.paint(cx);
modal_section.paint(cx); },
}, )
) .size_full(),
.size_full(),
),
) )
.child( .child(
div() div()
.visible_on_hover("remote-projects-section")
.occlude() .occlude()
.h_full() .h_full()
.absolute() .absolute()
.right_1()
.top_1() .top_1()
.bottom_1() .bottom_1()
.w(px(12.)) .right_1()
.w(px(8.))
.children(Scrollbar::vertical(scroll_state)), .children(Scrollbar::vertical(scroll_state)),
), ),
), ),
@ -1268,6 +1292,7 @@ impl Render for RemoteServerProjects {
div() div()
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.elevation_3(cx) .elevation_3(cx)
.w(rems(34.))
.key_context("RemoteServerModal") .key_context("RemoteServerModal")
.on_action(cx.listener(Self::cancel)) .on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
@ -1281,7 +1306,6 @@ impl Render for RemoteServerProjects {
cx.emit(DismissEvent) cx.emit(DismissEvent)
} }
})) }))
.w(rems(34.))
.child(match &self.mode { .child(match &self.mode {
Mode::Default(state) => self.render_default(state.clone(), cx).into_any_element(), Mode::Default(state) => self.render_default(state.clone(), cx).into_any_element(),
Mode::ViewServerOptions(index, connection) => self Mode::ViewServerOptions(index, connection) => self

View file

@ -5,7 +5,7 @@ use auto_update::AutoUpdater;
use editor::Editor; use editor::Editor;
use futures::channel::oneshot; use futures::channel::oneshot;
use gpui::{ use gpui::{
percentage, px, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent, percentage, Animation, AnimationExt, AnyWindowHandle, AsyncAppContext, DismissEvent,
EventEmitter, FocusableView, ParentElement as _, PromptLevel, Render, SemanticVersion, EventEmitter, FocusableView, ParentElement as _, PromptLevel, Render, SemanticVersion,
SharedString, Task, TextStyleRefinement, Transformation, View, SharedString, Task, TextStyleRefinement, Transformation, View,
}; };
@ -20,9 +20,8 @@ use serde::{Deserialize, Serialize};
use settings::{Settings, SettingsSources}; use settings::{Settings, SettingsSources};
use theme::ThemeSettings; use theme::ThemeSettings;
use ui::{ use ui::{
div, h_flex, prelude::*, v_flex, ActiveTheme, Color, Icon, IconName, IconSize, prelude::*, ActiveTheme, Color, Icon, IconName, IconSize, InteractiveElement, IntoElement,
InteractiveElement, IntoElement, Label, LabelCommon, Styled, ViewContext, VisualContext, Label, LabelCommon, Styled, ViewContext, VisualContext, WindowContext,
WindowContext,
}; };
use workspace::{AppState, ModalView, Workspace}; use workspace::{AppState, ModalView, Workspace};
@ -52,6 +51,7 @@ impl SshSettings {
}) })
.next() .next()
} }
pub fn nickname_for( pub fn nickname_for(
&self, &self,
host: &str, host: &str,
@ -86,6 +86,7 @@ pub struct SshConnection {
#[serde(default)] #[serde(default)]
pub args: Vec<String>, pub args: Vec<String>,
} }
impl From<SshConnection> for SshConnectionOptions { impl From<SshConnection> for SshConnectionOptions {
fn from(val: SshConnection) -> Self { fn from(val: SshConnection) -> Self {
SshConnectionOptions { SshConnectionOptions {
@ -205,15 +206,17 @@ impl SshPrompt {
impl Render for SshPrompt { impl Render for SshPrompt {
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let cx = cx.window_context(); let cx = cx.window_context();
let theme = cx.theme();
v_flex() v_flex()
.key_context("PasswordPrompt") .key_context("PasswordPrompt")
.py_2()
.px_3()
.size_full() .size_full()
.text_buffer(cx)
.when_some(self.status_message.clone(), |el, status_message| { .when_some(self.status_message.clone(), |el, status_message| {
el.child( el.child(
h_flex() h_flex()
.p_2() .gap_1()
.flex()
.child( .child(
Icon::new(IconName::ArrowCircle) Icon::new(IconName::ArrowCircle)
.size(IconSize::Medium) .size(IconSize::Medium)
@ -225,9 +228,12 @@ impl Render for SshPrompt {
}, },
), ),
) )
.child(div().ml_1().text_ellipsis().overflow_x_hidden().child( .child(
Label::new(format!("{}", status_message)).size(LabelSize::Small), div()
)), .text_ellipsis()
.overflow_x_hidden()
.child(format!("{}", status_message)),
),
) )
}) })
.when_some(self.prompt.as_ref(), |el, prompt| { .when_some(self.prompt.as_ref(), |el, prompt| {
@ -235,11 +241,6 @@ impl Render for SshPrompt {
div() div()
.size_full() .size_full()
.overflow_hidden() .overflow_hidden()
.p_4()
.border_t_1()
.border_color(theme.colors().border_variant)
.font_buffer(cx)
.text_buffer(cx)
.child(prompt.0.clone()) .child(prompt.0.clone())
.child(self.editor.clone()), .child(self.editor.clone()),
) )
@ -292,29 +293,18 @@ impl RenderOnce for SshConnectionHeader {
}; };
h_flex() h_flex()
.p_1() .px(Spacing::XLarge.rems(cx))
.pt(Spacing::Large.rems(cx))
.pb(Spacing::Small.rems(cx))
.rounded_t_md() .rounded_t_md()
.w_full() .w_full()
.gap_2() .gap_1p5()
.justify_center()
.border_b_1()
.border_color(theme.colors().border_variant)
.bg(header_color)
.child(Icon::new(IconName::Server).size(IconSize::XSmall)) .child(Icon::new(IconName::Server).size(IconSize::XSmall))
.child( .child(
h_flex() h_flex()
.gap_1() .gap_1()
.child( .child(Headline::new(main_label).size(HeadlineSize::XSmall))
Label::new(main_label) .children(meta_label.map(|label| Label::new(label).color(Color::Muted))),
.size(ui::LabelSize::Small)
.single_line(),
)
.children(meta_label.map(|label| {
Label::new(label)
.size(ui::LabelSize::Small)
.single_line()
.color(Color::Muted)
})),
) )
} }
} }
@ -323,18 +313,18 @@ impl Render for SshConnectionModal {
fn render(&mut self, cx: &mut ui::ViewContext<Self>) -> impl ui::IntoElement { fn render(&mut self, cx: &mut ui::ViewContext<Self>) -> impl ui::IntoElement {
let nickname = self.prompt.read(cx).nickname.clone(); let nickname = self.prompt.read(cx).nickname.clone();
let connection_string = self.prompt.read(cx).connection_string.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; let body_color = theme.colors().editor_background;
v_flex() v_flex()
.elevation_3(cx) .elevation_3(cx)
.w(rems(34.))
.border_1()
.border_color(theme.colors().border)
.track_focus(&self.focus_handle(cx)) .track_focus(&self.focus_handle(cx))
.on_action(cx.listener(Self::dismiss)) .on_action(cx.listener(Self::dismiss))
.on_action(cx.listener(Self::confirm)) .on_action(cx.listener(Self::confirm))
.w(px(500.))
.border_1()
.border_color(theme.colors().border)
.child( .child(
SshConnectionHeader { SshConnectionHeader {
connection_string, connection_string,
@ -343,10 +333,12 @@ impl Render for SshConnectionModal {
.render(cx), .render(cx),
) )
.child( .child(
h_flex() div()
.rounded_b_md()
.bg(body_color)
.w_full() .w_full()
.rounded_b_lg()
.bg(body_color)
.border_t_1()
.border_color(theme.colors().border_variant)
.child(self.prompt.clone()), .child(self.prompt.clone()),
) )
} }

View file

@ -145,6 +145,7 @@ mod test {
} }
#[gpui::test] #[gpui::test]
#[cfg(not(target_os = "linux"))]
async fn test_replace_mode(cx: &mut gpui::TestAppContext) { async fn test_replace_mode(cx: &mut gpui::TestAppContext) {
let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await; let mut cx: NeovimBackedTestContext = NeovimBackedTestContext::new(cx).await;