mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-10-05 08:02:13 +00:00
feat(ui): carry the v4 chrome into panes, the file viewer and SFTP
- theme: additive helpers for a device-pixel hairline, tabular figures and an inverted neutral button variant. - Pane splits rest as a device-pixel hairline in the divider tone; hover and drag keep the accent at 1px like the other resize edges. - File viewer header: medium file name, 5px unsaved dot, 26px/6px close tile with its glyph on the content inset, divider hairline under it. Status bar: divider hairline, caption size, tabular line/column. - SFTP browser: file-tree rows (26px, 6px corner, 16px caption glyphs), breadcrumb and notes on the 20px text column, a borderless edit well, inverted OK button, and ink-on-track transfer progress. - Forward rows line up with the process and port rows (text at 20px, 6px corner); Add and Reconnect use the inverted neutral fill.
This commit is contained in:
+39
-17
@@ -1360,32 +1360,44 @@ impl Tty7App {
|
||||
row
|
||||
};
|
||||
let menu_app = cx.entity().downgrade();
|
||||
// v4 chrome: the file name in body ink at medium — the one heading the
|
||||
// column has — a hairline in the divider tone under the bar, and the
|
||||
// rail's 26px close tile, so the header reads as part of the plane it
|
||||
// sits in rather than a toolbar bolted on top of it.
|
||||
let (tile, glyph) = (
|
||||
crate::ui::tab_strip::RAIL_TILE,
|
||||
crate::ui::tab_strip::RAIL_TILE_GLYPH,
|
||||
);
|
||||
row.flex_none()
|
||||
.h(px(crate::ui::app::TITLE_BAR_HEIGHT))
|
||||
.items_center()
|
||||
.gap_1p5()
|
||||
.gap(px(8.))
|
||||
.pl(px(lead))
|
||||
.pr(px(crate::ui::app::tile_trailing_inset()))
|
||||
.border_b_1()
|
||||
.border_color(cx.theme().border)
|
||||
// The glyph, not the tile, lands on `CONTENT_INSET`, the column
|
||||
// the file name starts on at the other end of the bar.
|
||||
.pr(px(crate::ui::app::CONTENT_INSET - (tile - glyph) / 2.))
|
||||
.border_b(crate::ui::theme::hairline(window))
|
||||
.border_color(cx.theme().sidebar_border)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_ellipsis()
|
||||
.text_sm()
|
||||
.when(name.is_none(), |d| {
|
||||
d.text_color(cx.theme().muted_foreground)
|
||||
.text_size(gpui::rems(crate::ui::right_panel::TEXT))
|
||||
.map(|d| match name.is_some() {
|
||||
true => d.font_weight(gpui::FontWeight::MEDIUM),
|
||||
false => d.text_color(cx.theme().muted_foreground),
|
||||
})
|
||||
.child(
|
||||
name.unwrap_or_else(|| SharedString::from(t(L10nKey::EditorNoFileOpen))),
|
||||
),
|
||||
)
|
||||
// Unsaved: the sidebar's 5px status dot, in the warning ink.
|
||||
.when(dirty, |d| {
|
||||
d.child(
|
||||
div()
|
||||
.flex_none()
|
||||
.size(px(6.))
|
||||
.size(px(crate::ui::tab_strip::ROW_STATUS_DOT))
|
||||
.rounded_full()
|
||||
.bg(cx.theme().warning),
|
||||
)
|
||||
@@ -1394,12 +1406,12 @@ impl Tty7App {
|
||||
div().occlude().flex_shrink_0().child(
|
||||
crate::ui::tab_strip::chrome_tile_sized(
|
||||
Button::new("editor-panel-close").icon(Icon::new(IconName::Close)),
|
||||
crate::ui::app::TILE_SIZE,
|
||||
crate::ui::app::TILE_GLYPH_LINE,
|
||||
tile,
|
||||
glyph,
|
||||
false,
|
||||
cx,
|
||||
)
|
||||
.rounded_lg()
|
||||
.rounded(px(crate::ui::tab_strip::RAIL_TILE_RADIUS))
|
||||
.tooltip(t(L10nKey::EditorBackToTerminal))
|
||||
.on_click(cx.listener(|this, _, window, cx| {
|
||||
this.toggle_code_panel(window, cx);
|
||||
@@ -1411,7 +1423,7 @@ impl Tty7App {
|
||||
})
|
||||
}
|
||||
|
||||
fn render_code_status_bar(&self, _window: &Window, cx: &mut Context<Self>) -> gpui::Div {
|
||||
fn render_code_status_bar(&self, window: &Window, cx: &mut Context<Self>) -> gpui::Div {
|
||||
// The roots below belong to this window's own machine. A file read
|
||||
// over SFTP is on another one, where they mean nothing, so it shows
|
||||
// its own full path rather than borrowing the local repo's name.
|
||||
@@ -1455,16 +1467,18 @@ impl Tty7App {
|
||||
let is_markdown = active.is_some_and(|f| language_for_path(&f.path) == "markdown");
|
||||
let preview = active.is_some_and(|f| f.preview);
|
||||
|
||||
// Metadata, not a toolbar: caption ink on the plane's own fill, set
|
||||
// off by a hairline in the divider tone rather than a control border.
|
||||
h_flex()
|
||||
.flex_none()
|
||||
.w_full()
|
||||
.h(px(26.))
|
||||
.items_center()
|
||||
.gap_3()
|
||||
.px_3()
|
||||
.border_t_1()
|
||||
.border_color(cx.theme().border)
|
||||
.text_xs()
|
||||
.px(px(crate::ui::app::CONTENT_INSET))
|
||||
.border_t(crate::ui::theme::hairline(window))
|
||||
.border_color(cx.theme().sidebar_border)
|
||||
.text_size(gpui::rems(crate::ui::right_panel::META))
|
||||
.text_color(muted)
|
||||
.when_some(path_text, |this, t| {
|
||||
this.child(div().min_w_0().text_ellipsis().child(t))
|
||||
@@ -1500,7 +1514,15 @@ impl Tty7App {
|
||||
),
|
||||
)
|
||||
})
|
||||
.when_some(cursor, |this, t| this.child(div().child(t)))
|
||||
// Tabular figures, so the position does not jitter sideways as
|
||||
// the caret walks from line 9 to line 10.
|
||||
.when_some(cursor, |this, t| {
|
||||
this.child(
|
||||
div()
|
||||
.font_features(crate::ui::theme::tabular_figures())
|
||||
.child(t),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn render_editor_empty(&self, cx: &Context<Self>) -> gpui::Div {
|
||||
|
||||
+23
-8
@@ -5,7 +5,7 @@ use gpui_component::{ActiveTheme as _, Disableable as _, IconName, Sizable as _,
|
||||
|
||||
use crate::daemon::protocol::{ForwardStatus, ManagedForward, SshForwardKind, SshForwardRule};
|
||||
use crate::terminal::view::TerminalView;
|
||||
use crate::ui::app::{CONTENT_INSET, Tty7App};
|
||||
use crate::ui::app::Tty7App;
|
||||
use crate::ui::i18n::{L10nKey, t, t_fmt};
|
||||
use crate::ui::right_panel::{META, TEXT_MONO};
|
||||
|
||||
@@ -179,6 +179,7 @@ impl Tty7App {
|
||||
|
||||
let theme = cx.theme();
|
||||
let (danger, foreground) = (theme.danger, theme.foreground);
|
||||
let theme_popover = theme.popover;
|
||||
|
||||
let bar = crate::ui::notice::pill(danger, cx)
|
||||
.child(
|
||||
@@ -217,9 +218,12 @@ impl Tty7App {
|
||||
.map(|keys| div().child(format!("· {keys}"))),
|
||||
)
|
||||
.child(
|
||||
// The strip's one action, as v4's primary: inverted neutral on
|
||||
// the popover fill the pill floats on. The red is the edge
|
||||
// and the reason; the button does not repeat it.
|
||||
Button::new("ssh-reconnect")
|
||||
.label(crate::ui::i18n::t(crate::ui::i18n::L10nKey::Reconnect))
|
||||
.primary()
|
||||
.custom(crate::ui::theme::inverted_button(theme_popover, cx))
|
||||
.small()
|
||||
.on_click(
|
||||
cx.listener(|this, _, window, cx| this.restart_ssh_session(window, cx)),
|
||||
@@ -277,9 +281,14 @@ impl Tty7App {
|
||||
.group(group.clone())
|
||||
.items_center()
|
||||
.gap(px(8.))
|
||||
.px(px(4.))
|
||||
.py(px(5.))
|
||||
.rounded(crate::ui::rounding::ROW_RADIUS)
|
||||
// The port and process rows' box: 26px, padded `ROW_INSET` so the
|
||||
// text lands on the panel's 20px column and the trailing tile ends
|
||||
// 20px from the edge, on the panel's row corner. A described
|
||||
// forward grows by its second line instead of being clipped to one.
|
||||
.min_h(px(26.))
|
||||
.py(px(3.))
|
||||
.px(px(crate::ui::right_panel::ROW_INSET))
|
||||
.rounded(crate::ui::right_panel::ROW_FILL_RADIUS)
|
||||
.cursor_pointer()
|
||||
.hover(|s| s.bg(gpui::rgb(sf.hover)))
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
@@ -342,7 +351,7 @@ impl Tty7App {
|
||||
)
|
||||
.w(px(crate::ui::tab_strip::MIN_TARGET))
|
||||
.h(px(crate::ui::tab_strip::MIN_TARGET))
|
||||
.rounded(px(4.))
|
||||
.rounded(px(crate::ui::tab_strip::RAIL_TILE_RADIUS))
|
||||
.tooltip(t(L10nKey::ForwardTooltipRemove))
|
||||
.on_click(cx.listener(
|
||||
move |this, _, _window, cx| {
|
||||
@@ -401,8 +410,9 @@ impl Tty7App {
|
||||
.child(div().w(px(52.)).child(Input::new(port).xsmall()))
|
||||
};
|
||||
|
||||
// Form text, not rows: on the panel's text column.
|
||||
v_flex()
|
||||
.px(px(CONTENT_INSET))
|
||||
.px(px(crate::ui::right_panel::TEXT_INSET))
|
||||
.pt(px(6.))
|
||||
.pb(px(2.))
|
||||
.gap(px(5.))
|
||||
@@ -547,7 +557,12 @@ impl Tty7App {
|
||||
} else {
|
||||
t(L10nKey::ForwardAdd)
|
||||
})
|
||||
.primary()
|
||||
// v4's primary: the panel's ink as the
|
||||
// fill, its opaque surface as the label.
|
||||
.custom(crate::ui::theme::inverted_button(
|
||||
gpui::rgb(sf.base).into(),
|
||||
cx,
|
||||
))
|
||||
.xsmall()
|
||||
.disabled(!complete)
|
||||
.on_click(cx.listener(move |this, _, window, cx| {
|
||||
|
||||
+16
-4
@@ -1080,8 +1080,14 @@ impl Pane<PaneSlot> {
|
||||
let row = *axis == Axis::Horizontal;
|
||||
let r = ratio.get().clamp(MIN_RATIO, MAX_RATIO);
|
||||
|
||||
let idle = cx.theme().border;
|
||||
// At rest the split is a v4 hairline in the divider tone — the
|
||||
// line between two panes that already carry their own content,
|
||||
// not the stronger rule that outlines a floating surface. Hover
|
||||
// and drag keep the accent the sidebar and right-panel edges
|
||||
// light up with, so every resize handle answers the same way.
|
||||
let idle = cx.theme().sidebar_border;
|
||||
let active = cx.theme().drag_border;
|
||||
let hairline = crate::ui::theme::hairline(window);
|
||||
|
||||
let container: Rc<Cell<Option<Bounds<Pixels>>>> = Rc::new(Cell::new(None));
|
||||
|
||||
@@ -1141,6 +1147,9 @@ impl Pane<PaneSlot> {
|
||||
.size_full();
|
||||
|
||||
let line_color = if dragging.get() { active } else { idle };
|
||||
// A lit handle goes back to a whole pixel: a hairline of accent
|
||||
// is too faint to say "this is what you are holding".
|
||||
let line = if dragging.get() { px(1.) } else { hairline };
|
||||
// The gutter stays 5px so the split looks the same; the target
|
||||
// is the 8px the sidebar and right-panel edges already hand
|
||||
// you. The extra 1.5px a side reaches into the pane's own 8px
|
||||
@@ -1182,10 +1191,13 @@ impl Pane<PaneSlot> {
|
||||
.when(!row, |d| d.h(px(DIVIDER_THICKNESS)).w_full())
|
||||
.child(
|
||||
div()
|
||||
.when(row, |d| d.w(px(1.)).h_full())
|
||||
.when(!row, |d| d.h(px(1.)).w_full())
|
||||
.when(row, |d| d.w(line).h_full())
|
||||
.when(!row, |d| d.h(line).w_full())
|
||||
.bg(line_color)
|
||||
.group_hover("split-divider", |s| s.bg(active)),
|
||||
.group_hover("split-divider", move |s| match row {
|
||||
true => s.bg(active).w(px(1.)),
|
||||
false => s.bg(active).h(px(1.)),
|
||||
}),
|
||||
)
|
||||
.child(grab);
|
||||
|
||||
|
||||
+101
-60
@@ -22,7 +22,20 @@ use crate::daemon::ssh::sftp::{remote_basename, remote_join, remote_parent, safe
|
||||
use crate::terminal::RemoteTerminal;
|
||||
use crate::ui::app::{CONTENT_INSET, TILE_GLYPH_SM, TILE_SIZE_SM, Tty7App};
|
||||
use crate::ui::i18n::{L10nKey, t, t_fmt};
|
||||
use crate::ui::right_panel::{META, TEXT};
|
||||
use crate::ui::right_panel::{
|
||||
HEADING, META, ROW_FILL_RADIUS, ROW_GLYPH, ROW_INSET, TEXT, TEXT_INSET,
|
||||
};
|
||||
|
||||
/// A remote directory row: the local file tree's 26px row, on the panel's
|
||||
/// `ROW_FILL_RADIUS`, so the Files tab keeps its rhythm whichever machine it
|
||||
/// is showing.
|
||||
const SFTP_ROW_H: f32 = 26.;
|
||||
|
||||
/// The gap between a row's icon, name and size — the file tree's.
|
||||
const SFTP_ROW_GAP: f32 = 6.;
|
||||
|
||||
/// The transfer tray's disclosure chevron — the file tree's.
|
||||
const TRAY_CHEVRON: f32 = 10.;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum SftpMenuAction {
|
||||
@@ -1357,7 +1370,8 @@ impl Tty7App {
|
||||
.flex_wrap()
|
||||
.items_center()
|
||||
.gap_0p5()
|
||||
.px(px(CONTENT_INSET))
|
||||
// Text, not a row: on the panel's text column.
|
||||
.px(px(TEXT_INSET))
|
||||
.pb(px(4.))
|
||||
.on_double_click(
|
||||
cx.listener(|this, _, window, cx| this.sftp_begin_edit_path(window, cx)),
|
||||
@@ -1395,9 +1409,10 @@ impl Tty7App {
|
||||
}
|
||||
|
||||
fn render_sftp_edit_form(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
|
||||
let secondary = cx.theme().secondary;
|
||||
let border = cx.theme().border;
|
||||
let well = cx.theme().muted;
|
||||
let foreground = cx.theme().foreground;
|
||||
let surface: gpui::Hsla =
|
||||
gpui::rgb(cx.global::<crate::ui::presets::Surfaces>().sidebar.base).into();
|
||||
let (title, input): (String, _) = match self.sftp_panel.editing.as_ref()? {
|
||||
SftpEdit::NewFolder(input) => (t(L10nKey::SftpEditNewFolder).to_string(), input),
|
||||
SftpEdit::NewFile(input) => (t(L10nKey::SftpEditNewFile).to_string(), input),
|
||||
@@ -1416,14 +1431,16 @@ impl Tty7App {
|
||||
Some(
|
||||
v_flex()
|
||||
.id("panel-sftp-edit")
|
||||
.gap(px(5.))
|
||||
.mx(px(CONTENT_INSET - 4.))
|
||||
.gap(px(6.))
|
||||
// Where a row's fill would be, its text on the row's text
|
||||
// column.
|
||||
.mx(px(CONTENT_INSET))
|
||||
.mb(px(4.))
|
||||
.p(px(6.))
|
||||
.bg(secondary)
|
||||
.border_1()
|
||||
.border_color(border)
|
||||
.rounded_md()
|
||||
.p(px(ROW_INSET))
|
||||
// A borderless faint well, the file tree's search shape, not
|
||||
// an outlined card: it is part of the list, not over it.
|
||||
.bg(well)
|
||||
.rounded(px(7.))
|
||||
// Escape backs out of the form, the way it backs out of the
|
||||
// path editor above it and every sheet the app puts up.
|
||||
.on_key_down(cx.listener(|this, ev: &gpui::KeyDownEvent, window, cx| {
|
||||
@@ -1433,7 +1450,7 @@ impl Tty7App {
|
||||
}))
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_size(rems(HEADING))
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.text_color(foreground)
|
||||
.child(title),
|
||||
@@ -1456,7 +1473,7 @@ impl Tty7App {
|
||||
Button::new("sftp-edit-ok")
|
||||
.label(t(L10nKey::Ok))
|
||||
.xsmall()
|
||||
.primary()
|
||||
.custom(crate::ui::theme::inverted_button(surface, cx))
|
||||
.disabled(!can_commit)
|
||||
.on_click(
|
||||
cx.listener(|this, _, w, cx| this.sftp_commit_edit(w, cx)),
|
||||
@@ -1475,12 +1492,12 @@ impl Tty7App {
|
||||
.min_h_0()
|
||||
.overflow_y_scroll()
|
||||
.track_scroll(&self.sftp_panel.scroll)
|
||||
.px(px(CONTENT_INSET - 6.))
|
||||
.px(px(CONTENT_INSET))
|
||||
.pb(px(4.));
|
||||
|
||||
let note = |text: gpui::SharedString, color| {
|
||||
div()
|
||||
.px(px(6.))
|
||||
.px(px(ROW_INSET))
|
||||
.py(px(4.))
|
||||
.text_size(rems(TEXT))
|
||||
.text_color(color)
|
||||
@@ -1521,33 +1538,41 @@ impl Tty7App {
|
||||
}
|
||||
|
||||
fn render_sftp_go_up_row(&self, cx: &mut Context<Self>) -> AnyElement {
|
||||
let foreground = cx.theme().foreground;
|
||||
let theme = cx.theme();
|
||||
let (name_ink, muted) = (theme.sidebar_foreground, theme.muted_foreground);
|
||||
let sf = cx.global::<crate::ui::presets::Surfaces>().popover;
|
||||
h_flex()
|
||||
.id("sftp-go-up")
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.pl(px(6.))
|
||||
.pr_1()
|
||||
.py_1()
|
||||
.rounded(cx.theme().radius)
|
||||
.gap(px(SFTP_ROW_GAP))
|
||||
.h(px(SFTP_ROW_H))
|
||||
.px(px(ROW_INSET))
|
||||
.rounded(ROW_FILL_RADIUS)
|
||||
.cursor_pointer()
|
||||
.hover(|s| s.bg(gpui::rgb(sf.hover)))
|
||||
.child(
|
||||
Icon::new(IconName::FolderOpen)
|
||||
.xsmall()
|
||||
.text_color(foreground),
|
||||
.size(px(ROW_GLYPH))
|
||||
.text_color(muted),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_size(rems(TEXT))
|
||||
.text_color(name_ink)
|
||||
.child(".."),
|
||||
)
|
||||
.child(div().flex_1().min_w_0().text_sm().child(".."))
|
||||
.on_click(cx.listener(|this, _, _w, cx| this.sftp_up(cx)))
|
||||
.into_any_element()
|
||||
}
|
||||
|
||||
fn render_sftp_row(&self, entry: &SftpEntry, cx: &mut Context<Self>) -> AnyElement {
|
||||
let foreground = cx.theme().foreground;
|
||||
// The file tree's row: every glyph in caption ink, the name in the
|
||||
// rail's title ink, the size a tabular caption at the trailing end.
|
||||
let name_ink = cx.theme().sidebar_foreground;
|
||||
let muted = cx.theme().muted_foreground;
|
||||
let dir_color = foreground;
|
||||
let list_hover = cx.theme().list_hover;
|
||||
let list_hover = gpui::rgb(cx.global::<crate::ui::presets::Surfaces>().popover.hover);
|
||||
let entry = entry.clone();
|
||||
let dir_like = is_dir_like(&entry);
|
||||
let icon = if dir_like {
|
||||
@@ -1575,13 +1600,12 @@ impl Tty7App {
|
||||
h_flex()
|
||||
.id(row_id)
|
||||
.items_center()
|
||||
.gap_1()
|
||||
.pl(px(6.))
|
||||
.pr_1()
|
||||
.py_1()
|
||||
.rounded(cx.theme().radius)
|
||||
.gap(px(SFTP_ROW_GAP))
|
||||
.h(px(SFTP_ROW_H))
|
||||
.px(px(ROW_INSET))
|
||||
.rounded(ROW_FILL_RADIUS)
|
||||
.cursor_pointer()
|
||||
.hover(|s| s.bg(list_hover))
|
||||
.hover(move |s| s.bg(list_hover))
|
||||
// Single click, the same gesture the local file tree answers —
|
||||
// this panel used to demand a double click because its open
|
||||
// action was a download, and that caution outlived the download.
|
||||
@@ -1591,21 +1615,24 @@ impl Tty7App {
|
||||
this.sftp_open_entry(open_entry.clone(), window, cx)
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
Icon::new(icon)
|
||||
.xsmall()
|
||||
.text_color(if dir_like { dir_color } else { muted }),
|
||||
)
|
||||
.child(Icon::new(icon).size(px(ROW_GLYPH)).text_color(muted))
|
||||
.child(
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_sm()
|
||||
.text_color(foreground)
|
||||
.text_size(rems(TEXT))
|
||||
.text_color(name_ink)
|
||||
.truncate()
|
||||
.child(name_label),
|
||||
)
|
||||
.child(div().flex_none().text_xs().text_color(muted).child(size))
|
||||
.child(
|
||||
div()
|
||||
.flex_none()
|
||||
.text_size(rems(META))
|
||||
.font_features(crate::ui::theme::tabular_figures())
|
||||
.text_color(muted)
|
||||
.child(size),
|
||||
)
|
||||
.context_menu(move |menu, _window, cx| {
|
||||
let danger = cx.theme().danger;
|
||||
Self::sftp_row_context_menu(menu, &menu_entry, dir_like, is_symlink, danger, &app)
|
||||
@@ -1716,8 +1743,10 @@ impl Tty7App {
|
||||
|
||||
let muted = cx.theme().muted_foreground;
|
||||
let danger = cx.theme().danger;
|
||||
let accent = cx.theme().accent;
|
||||
let border = cx.theme().border;
|
||||
// Progress in v4 is ink on a faint track, not the accent: the bar is a
|
||||
// reading, and blue is kept for the things you can press.
|
||||
let (ink, track) = (cx.theme().foreground, cx.theme().muted);
|
||||
let divider = cx.theme().sidebar_border;
|
||||
let hover = gpui::rgb(cx.global::<crate::ui::presets::Surfaces>().sidebar.hover);
|
||||
let expanded = self.sftp_panel.tray_expanded || history;
|
||||
|
||||
@@ -1769,16 +1798,21 @@ impl Tty7App {
|
||||
.id("sftp-transfers-summary")
|
||||
.items_center()
|
||||
.gap(px(6.))
|
||||
.px(px(CONTENT_INSET))
|
||||
// The disclosure hangs in the gutter so the summary's text starts
|
||||
// on the panel's text column, the way a tree row's chevron does.
|
||||
.pl(px(TEXT_INSET - TRAY_CHEVRON - 6.))
|
||||
.pr(px(CONTENT_INSET))
|
||||
.h(px(28.))
|
||||
.cursor_pointer()
|
||||
.hover(move |s| s.bg(hover))
|
||||
.on_click(cx.listener(|this, _, _w, cx| this.sftp_toggle_tray(cx)))
|
||||
.child(
|
||||
div()
|
||||
.text_size(rems(META))
|
||||
.text_color(muted)
|
||||
.child(if expanded { "⌄" } else { "›" }),
|
||||
Icon::new(match expanded {
|
||||
true => IconName::ChevronDown,
|
||||
false => IconName::ChevronRight,
|
||||
})
|
||||
.size(px(TRAY_CHEVRON))
|
||||
.text_color(muted),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
@@ -1786,6 +1820,7 @@ impl Tty7App {
|
||||
.min_w_0()
|
||||
.truncate()
|
||||
.text_size(rems(META))
|
||||
.font_features(crate::ui::theme::tabular_figures())
|
||||
.text_color(summary_color)
|
||||
.child(summary),
|
||||
)
|
||||
@@ -1803,17 +1838,17 @@ impl Tty7App {
|
||||
)
|
||||
.w(px(crate::ui::tab_strip::MIN_TARGET))
|
||||
.h(px(crate::ui::tab_strip::MIN_TARGET))
|
||||
.rounded(px(4.))
|
||||
.rounded(px(crate::ui::tab_strip::RAIL_TILE_RADIUS))
|
||||
.tooltip(t(L10nKey::Dismiss))
|
||||
.on_click(cx.listener(|this, _, _w, cx| this.sftp_dismiss_tray(cx))),
|
||||
),
|
||||
);
|
||||
|
||||
let underline = div().h(px(2.)).w_full().bg(border).child(
|
||||
let underline = div().h(px(2.)).w_full().bg(track).child(
|
||||
div()
|
||||
.h_full()
|
||||
.w(gpui::relative((pct / 100.0) as f32))
|
||||
.bg(if failed > 0 { danger } else { accent }),
|
||||
.bg(if failed > 0 { danger } else { ink }),
|
||||
);
|
||||
|
||||
let body = expanded.then(|| {
|
||||
@@ -1830,14 +1865,14 @@ impl Tty7App {
|
||||
};
|
||||
v_flex().child(
|
||||
div()
|
||||
.px(px(CONTENT_INSET))
|
||||
.px(px(TEXT_INSET))
|
||||
.py(px(3.))
|
||||
.text_size(rems(META))
|
||||
.text_color(color)
|
||||
.child(text),
|
||||
)
|
||||
} else {
|
||||
let mut list = v_flex().px(px(CONTENT_INSET)).pb(px(6.)).gap(px(6.));
|
||||
let mut list = v_flex().px(px(TEXT_INSET)).pb(px(8.)).gap(px(8.));
|
||||
for job in jobs {
|
||||
list = list.child(self.render_sftp_job(job, cx));
|
||||
}
|
||||
@@ -1866,7 +1901,7 @@ impl Tty7App {
|
||||
v_flex()
|
||||
.flex_none()
|
||||
.border_t_1()
|
||||
.border_color(border)
|
||||
.border_color(divider)
|
||||
.child(head)
|
||||
.when(running > 0 && !expanded, |this| this.child(underline))
|
||||
.children(body)
|
||||
@@ -1876,11 +1911,10 @@ impl Tty7App {
|
||||
|
||||
fn render_sftp_job(&self, job: &SftpJobProgress, cx: &mut Context<Self>) -> Div {
|
||||
let foreground = cx.theme().foreground;
|
||||
let border = cx.theme().border;
|
||||
let track = cx.theme().muted;
|
||||
let danger = cx.theme().danger;
|
||||
let success = cx.theme().success;
|
||||
let muted = cx.theme().muted_foreground;
|
||||
let accent = cx.theme().accent;
|
||||
let arrow = match job.kind {
|
||||
SftpTransferKind::Upload => "↑",
|
||||
SftpTransferKind::Download => "↓",
|
||||
@@ -1912,10 +1946,11 @@ impl Tty7App {
|
||||
SftpJobState::Done => success,
|
||||
_ => muted,
|
||||
};
|
||||
// The tray's own bar: body ink on the faint track, red once it fails.
|
||||
let bar_color = if matches!(job.state, SftpJobState::Error) {
|
||||
danger
|
||||
} else {
|
||||
accent
|
||||
foreground
|
||||
};
|
||||
let job_id = job.job_id;
|
||||
let running = matches!(job.state, SftpJobState::Running);
|
||||
@@ -1934,7 +1969,7 @@ impl Tty7App {
|
||||
div()
|
||||
.flex_1()
|
||||
.min_w_0()
|
||||
.text_xs()
|
||||
.text_size(rems(META))
|
||||
.text_color(foreground)
|
||||
.truncate()
|
||||
.child(format!("{arrow} {name}")),
|
||||
@@ -1971,7 +2006,7 @@ impl Tty7App {
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div().h(px(3.)).w_full().rounded_full().bg(border).child(
|
||||
div().h(px(3.)).w_full().rounded_full().bg(track).child(
|
||||
div()
|
||||
.h_full()
|
||||
.w(gpui::relative((pct / 100.0) as f32))
|
||||
@@ -1979,7 +2014,13 @@ impl Tty7App {
|
||||
.bg(bar_color),
|
||||
),
|
||||
)
|
||||
.child(div().text_xs().text_color(status_color).child(status))
|
||||
.child(
|
||||
div()
|
||||
.text_size(rems(META))
|
||||
.font_features(crate::ui::theme::tabular_figures())
|
||||
.text_color(status_color)
|
||||
.child(status),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -947,6 +947,36 @@ pub(crate) fn floating_surface<T: Styled>(element: T, cx: &App) -> T {
|
||||
.shadow_xl()
|
||||
}
|
||||
|
||||
/// One device pixel: the v4 hairline, 0.5px on a Retina panel and a whole
|
||||
/// pixel on a 1x one. A literal `px(0.5)` would be antialiased down to half
|
||||
/// its ink on a 1x display, under the contrast floor `divider` is held to.
|
||||
pub(crate) fn hairline(window: &Window) -> Pixels {
|
||||
px(1. / window.scale_factor().max(1.))
|
||||
}
|
||||
|
||||
/// Tabular figures, so a column of counts, sizes and positions keeps its
|
||||
/// digits on one grid while the numbers change under it.
|
||||
pub(crate) fn tabular_figures() -> gpui::FontFeatures {
|
||||
gpui::FontFeatures(std::sync::Arc::new(vec![("tnum".to_string(), 1)]))
|
||||
}
|
||||
|
||||
/// The v4 primary button: an inverted neutral — body ink as the fill, the
|
||||
/// surface it sits on as the label — rather than the accent, which stays with
|
||||
/// focus and the things that are on. `surface` is the opaque fill under the
|
||||
/// button, so the label reads as cut out of it. Disabled, gpui-component
|
||||
/// drops the fill to 15% of itself, the faint well the rest of v4 uses.
|
||||
pub(crate) fn inverted_button(
|
||||
surface: Hsla,
|
||||
cx: &App,
|
||||
) -> gpui_component::button::ButtonCustomVariant {
|
||||
let ink = cx.theme().foreground;
|
||||
gpui_component::button::ButtonCustomVariant::new(cx)
|
||||
.color(ink)
|
||||
.foreground(surface)
|
||||
.hover(ink.blend(surface.opacity(0.14)))
|
||||
.active(ink.blend(surface.opacity(0.24)))
|
||||
}
|
||||
|
||||
/// On-state shares the accent role with sliders and primary actions.
|
||||
pub(crate) fn switch(id: impl Into<gpui::ElementId>, cx: &App) -> gpui_component::switch::Switch {
|
||||
let accent = cx.global::<presets::ActiveAccent>().0;
|
||||
|
||||
Reference in New Issue
Block a user