chore: audit components

This commit is contained in:
2026-11-10 15:06:16 +07:00
parent af2177c6c8
commit 03369f501e
92 changed files with 2744 additions and 1947 deletions
+240 -190
View File
@@ -5,7 +5,7 @@ use gpui::{
App, AppContext as _, Axis, Bounds, Context, Entity, FontWeight, InteractiveElement as _,
IntoElement, KeyBinding, ListAlignment, ListState, ParentElement as _, Pixels, Render,
SharedString, Styled as _, UniformListScrollHandle, WeakEntity, Window, WindowBounds,
WindowOptions, actions, div, list, px, size, uniform_list,
WindowOptions, actions, div, list, px, rems, size, uniform_list,
};
use gpui_native_core::alert::{Alert, AlertSeverity, Alerts, AlertsView};
use gpui_native_core::avatar::Avatar;
@@ -53,9 +53,10 @@ use gpui_native_layout::resizable::{Resizable, ResizableState};
use gpui_native_layout::resize_handle::ResizeHandle;
use gpui_native_layout::shell::{LayoutState, Shell};
use gpui_native_theme::{
ActiveTheme as _, Theme, ThemeVariant, active_preset, mode_override, set_mode_override,
set_preset,
ActiveTheme as _, CODE_OVERSCROLL_MAX, Theme, ThemeVariant, active_preset, mode_override,
set_mode_override, set_preset,
};
use gpui_native_utils::ResultLogErr as _;
use gpui_platform::application;
/// The component catalog: (group, [(panel id, sidebar label)]).
@@ -225,6 +226,7 @@ struct Gallery {
alert_open: bool,
/// Whether the app sidebar paints the window's native material.
sidebar_transparent: bool,
_subscriptions: Vec<gpui::Subscription>,
}
impl Gallery {
@@ -263,7 +265,11 @@ impl Gallery {
.iter()
.flat_map(|(group, panels)| {
let mut entries = vec![SidebarEntry::header(*group)];
entries.extend(panels.iter().map(|(_, label)| SidebarEntry::row(*label)));
entries.extend(
panels
.iter()
.map(|(id, label)| SidebarEntry::row(*id, *label)),
);
entries
})
.collect()
@@ -302,7 +308,7 @@ impl Gallery {
gallery.dialog_open = open;
cx.notify();
})
.ok();
.log_err();
})
.content(|_, _| {
div()
@@ -322,7 +328,7 @@ impl Gallery {
gallery.dialog_open = false;
cx.notify();
})
.ok();
.log_err();
})
.into_any_element(),
Button::new("gallery-dialog-delete")
@@ -334,7 +340,7 @@ impl Gallery {
gallery.dialog_open = false;
cx.notify();
})
.ok();
.log_err();
})
.into_any_element(),
]
@@ -353,7 +359,7 @@ impl Gallery {
gallery.alert_open = open;
cx.notify();
})
.ok();
.log_err();
})
.content(|_, _| {
div()
@@ -373,7 +379,7 @@ impl Gallery {
gallery.alert_open = false;
cx.notify();
})
.ok();
.log_err();
})
.into_any_element(),
Button::new("gallery-alert-signout")
@@ -385,7 +391,7 @@ impl Gallery {
gallery.alert_open = false;
cx.notify();
})
.ok();
.log_err();
})
.into_any_element(),
]
@@ -612,7 +618,12 @@ impl Gallery {
dock.register_panel(PanelDescriptor::new(
"resize-handle",
"Resize handle",
|_, cx| cx.new(|_| ResizeDemo { width: px(200.) }).into(),
|window, cx| {
cx.new(|_| ResizeDemo {
width: rems(ResizeDemo::WIDTH_DEFAULT_REMS).to_pixels(window.rem_size()),
})
.into()
},
));
dock.register_panel(PanelDescriptor::new("resizable", "Resizable", |_, cx| {
cx.new(ResizableDemo::new).into()
@@ -677,7 +688,7 @@ impl Render for WelcomePanel {
.child(
v_flex()
.gap_4()
.max_w(px(560.))
.max_w(rems(35.))
.child(
div()
.text_lg()
@@ -740,7 +751,7 @@ impl Render for WelcomePanel {
!gallery.sidebar_transparent;
cx.notify();
})
.ok();
.log_err();
}),
)
.child(
@@ -789,6 +800,17 @@ struct ButtonDemo {
clicks: usize,
}
impl ButtonDemo {
fn button_label(variant: ButtonVariant) -> &'static str {
match variant {
ButtonVariant::Primary => "Primary",
ButtonVariant::Secondary => "Secondary",
ButtonVariant::Ghost => "Ghost",
ButtonVariant::Destructive => "Delete",
}
}
}
impl Render for ButtonDemo {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
@@ -813,7 +835,7 @@ impl Render for ButtonDemo {
let view = view.clone();
let mut button =
Button::new(SharedString::from(format!("button-demo-{name}")))
.label(button_label(variant))
.label(Self::button_label(variant))
.variant(variant)
.on_click(move |_, _, cx| {
view.update(cx, |demo, cx| {
@@ -839,7 +861,7 @@ impl Render for ButtonDemo {
let view = view.clone();
let mut button =
Button::new(SharedString::from(format!("button-demo-icon-{name}")))
.label(button_label(variant))
.label(Self::button_label(variant))
.icon(IconRole::Folder)
.variant(variant)
.on_click(move |_, _, cx| {
@@ -1174,10 +1196,6 @@ impl Render for StatusDotDemo {
}
}
fn combo_option(label: &'static str) -> ComboboxOption<SharedString> {
(SharedString::from(label), SharedString::from(label))
}
struct ComboboxDemo {
branch: Entity<ComboboxState<SharedString>>,
label: Entity<ComboboxState<SharedString>>,
@@ -1187,6 +1205,10 @@ struct ComboboxDemo {
}
impl ComboboxDemo {
fn combo_option(label: &'static str) -> ComboboxOption<SharedString> {
(SharedString::from(label), SharedString::from(label))
}
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let view = cx.entity();
let branch = cx.new(|cx| {
@@ -1195,10 +1217,10 @@ impl ComboboxDemo {
"combo-demo-branch",
"Branch",
vec![
combo_option("main"),
combo_option("next"),
combo_option("release/1.0"),
combo_option("release/2.0"),
Self::combo_option("main"),
Self::combo_option("next"),
Self::combo_option("release/1.0"),
Self::combo_option("release/2.0"),
],
)
.selected(SharedString::from("main"))
@@ -1206,7 +1228,7 @@ impl ComboboxDemo {
.keep_unmatched_text()
// Widths are style refinements on the builder; the popup
// always matches whatever the combobox ends up being.
.w(px(280.)),
.w(rems(17.5)),
window,
cx,
)
@@ -1215,9 +1237,9 @@ impl ComboboxDemo {
"combo-demo-label",
"Label",
vec![
combo_option("bug"),
combo_option("docs"),
combo_option("enhancement"),
Self::combo_option("bug"),
Self::combo_option("docs"),
Self::combo_option("enhancement"),
],
)
.placeholder("Pick or type a label…")
@@ -1245,10 +1267,10 @@ impl ComboboxDemo {
"combo-demo-fuzzy",
"Module",
vec![
combo_option("combobox"),
combo_option("dock"),
combo_option("editor"),
combo_option("markdown"),
Self::combo_option("combobox"),
Self::combo_option("dock"),
Self::combo_option("editor"),
Self::combo_option("markdown"),
],
)
.placeholder("Filter modules…")
@@ -1817,7 +1839,7 @@ impl ResizableDemo {
}
impl Render for ResizableDemo {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let colors = cx.theme().colors;
let pane = |text: &'static str| {
div().size_full().bg(colors.muted).p_2().child(
@@ -1836,8 +1858,8 @@ impl Render for ResizableDemo {
colors,
Resizable::new("resizable-demo-vertical", self.vertical.clone())
.size_full()
.h(px(320.))
.pane_with_min(pane("Tree"), px(96.))
.h(rems(20.))
.pane_with_min(pane("Tree"), rems(6.).to_pixels(window.rem_size()))
.pane(pane("Editor"))
.pane(pane("Terminal")),
))
@@ -1847,7 +1869,7 @@ impl Render for ResizableDemo {
Resizable::new("resizable-demo-horizontal", self.horizontal.clone())
.axis(Axis::Horizontal)
.size_full()
.h(px(120.))
.h(rems(7.5))
.pane(pane("Primary"))
.pane(pane("Inspector")),
))
@@ -2022,13 +2044,13 @@ impl Render for ListDemo {
})
.items(|_, _| {
vec![
ListItem::new("Documents").icon(IconRole::Folder),
ListItem::new("Pictures")
ListItem::new("Documents", "Documents").icon(IconRole::Folder),
ListItem::new("Pictures", "Pictures")
.icon(IconRole::Folder)
.detail("24 items"),
ListItem::new("Music").icon(IconRole::Folder),
ListItem::new("Archive").disabled(true),
ListItem::new("Trash").detail("emptied weekly"),
ListItem::new("Music", "Music").icon(IconRole::Folder),
ListItem::new("Archive", "Archive").disabled(true),
ListItem::new("Trash", "Trash").detail("emptied weekly"),
]
}),
))
@@ -2122,14 +2144,20 @@ impl TreeDemo {
nodes: &[GalleryNode],
depth: usize,
prefix: &[usize],
key_prefix: &str,
items: &mut Vec<TreeItem>,
paths: &mut Vec<Vec<usize>>,
) {
for (index, node) in nodes.iter().enumerate() {
let mut path = prefix.to_vec();
path.push(index);
// Slash-joined label paths key rows by domain identity, so element
// ids survive expansion changing the flat row order.
let key: SharedString = if key_prefix.is_empty() {
node.label.clone()
} else {
format!("{key_prefix}/{}", node.label).into()
};
let mut item = TreeItem::new(node.label.clone())
let mut item = TreeItem::new(key.clone(), node.label.clone())
.depth(depth)
.expandable(!node.children.is_empty())
.expanded(node.expanded);
@@ -2137,10 +2165,13 @@ impl TreeDemo {
item = item.icon(role);
}
items.push(item);
let mut path = prefix.to_vec();
path.push(index);
paths.push(path.clone());
if node.expanded {
Self::flatten(&node.children, depth + 1, &path, items, paths);
Self::flatten(&node.children, depth + 1, &path, &key, items, paths);
}
}
}
@@ -2162,7 +2193,7 @@ impl TreeDemo {
fn paths(nodes: &[GalleryNode]) -> Vec<Vec<usize>> {
let mut items = Vec::new();
let mut paths = Vec::new();
Self::flatten(nodes, 0, &[], &mut items, &mut paths);
Self::flatten(nodes, 0, &[], "", &mut items, &mut paths);
paths
}
}
@@ -2174,7 +2205,7 @@ impl Render for TreeDemo {
let colors = cx.theme().colors;
let mut items = Vec::new();
let mut paths = Vec::new();
Self::flatten(&self.nodes, 0, &[], &mut items, &mut paths);
Self::flatten(&self.nodes, 0, &[], "", &mut items, &mut paths);
ScrollArea::new("tree-demo")
.size_full()
@@ -2519,7 +2550,7 @@ impl Render for DialogDemo {
gallery.dialog_open = true;
cx.notify();
})
.ok();
.log_err();
}),
)
.child(
@@ -2532,7 +2563,7 @@ impl Render for DialogDemo {
gallery.alert_open = true;
cx.notify();
})
.ok();
.log_err();
}),
),
))
@@ -2694,7 +2725,10 @@ impl DropdownMenuDemo {
cx.notify();
}
fn set_status(&mut self, message: impl Into<SharedString>, cx: &mut Context<Self>) {
fn set_status<S>(&mut self, message: S, cx: &mut Context<Self>)
where
S: Into<SharedString>,
{
self.status = Some(message.into());
cx.notify();
}
@@ -2940,7 +2974,7 @@ impl Render for PopoverDemo {
.content(|_, _| {
v_flex()
.gap_1()
.w(px(240.))
.w(rems(15.))
.p_2()
.child(
Checkbox::new("popover-demo-telemetry").label("Enable telemetry"),
@@ -3013,7 +3047,7 @@ impl Render for EmptyStateDemo {
"with action",
colors,
div()
.h(px(180.))
.h(rems(11.25))
.rounded(radius.overlay)
.border_1()
.border_color(colors.border)
@@ -3033,7 +3067,7 @@ impl Render for EmptyStateDemo {
"bare",
colors,
div()
.h(px(120.))
.h(rems(7.5))
.rounded(radius.overlay)
.border_1()
.border_color(colors.border)
@@ -3062,7 +3096,7 @@ impl Render for ScrollAreaDemo {
let wide_rows = (0..12).map(|row| {
div()
.flex_none()
.w(px(760.))
.w(rems(47.5))
.px_3()
.py_1()
.border_b_1()
@@ -3077,7 +3111,7 @@ impl Render for ScrollAreaDemo {
"vertical",
colors,
div()
.h(px(200.))
.h(rems(12.5))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3092,7 +3126,7 @@ impl Render for ScrollAreaDemo {
"both axes",
colors,
div()
.h(px(160.))
.h(rems(10.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3117,10 +3151,12 @@ struct ScrollbarDemo {
}
impl ScrollbarDemo {
const ROW_HEIGHT_REMS: [f32; 3] = [1.75, 2.5, 3.25];
fn new() -> Self {
Self {
uniform: UniformListScrollHandle::new(),
list: ListState::new(64, ListAlignment::Top, px(400.)),
list: ListState::new(64, ListAlignment::Top, CODE_OVERSCROLL_MAX),
visibility: ScrollbarVisibility::Platform,
}
}
@@ -3159,7 +3195,7 @@ impl Render for ScrollbarDemo {
colors,
div()
.relative()
.h(px(240.))
.h(rems(15.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3169,7 +3205,7 @@ impl Render for ScrollbarDemo {
range
.map(|row| {
div()
.h(px(24.))
.h(rems(1.5))
.px_3()
.flex()
.items_center()
@@ -3190,7 +3226,7 @@ impl Render for ScrollbarDemo {
colors,
div()
.relative()
.h(px(240.))
.h(rems(15.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3203,7 +3239,7 @@ impl Render for ScrollbarDemo {
.border_b_1()
.border_color(colors.border)
.child(format!("Item {row} — {} lines tall", 1 + row % 3))
.h(px(28. + (row % 3) as f32 * 12.))
.h(rems(Self::ROW_HEIGHT_REMS[row % 3]))
.into_any_element()
})
.h_full()
@@ -3284,34 +3320,6 @@ enum CodeSample {
/// The line count of the large-file sample.
const LARGE_SAMPLE_LINES: usize = 5000;
/// Generates a deterministic pseudo-Rust file of roughly `lines` lines, so
/// scrolling the code view can be exercised on realistic content.
fn large_sample(lines: usize) -> String {
let mut source = String::with_capacity(lines * 48);
let mut index = 0usize;
while source.lines().count() < lines {
let i = index;
source.push_str(&format!(
"// Section {i}: generated for the scroll performance test.\n"
));
source.push_str(&format!("struct Section{i} {{\n"));
source.push_str(" id: u64,\n");
source.push_str(" label: String,\n");
source.push_str(" scores: Vec<f64>,\n");
source.push_str("}\n\n");
source.push_str(&format!("impl Section{i} {{\n"));
source.push_str(" /// A one-line summary of this section.\n");
source.push_str(" fn describe(&self) -> String {\n");
source.push_str(&format!(
" format!(\"section {i}: {{}} at {{:?}}\", self.label, self.scores)\n"
));
source.push_str(" }\n");
source.push_str("}\n\n");
index += 1;
}
source
}
struct CodeViewDemo {
code: Entity<CodeViewState>,
sample: CodeSample,
@@ -3329,10 +3337,38 @@ impl CodeViewDemo {
Self {
code,
sample: CodeSample::Rust,
large_source: large_sample(LARGE_SAMPLE_LINES).into(),
large_source: Self::large_sample(LARGE_SAMPLE_LINES).into(),
_subscription: subscription,
}
}
/// Generates a deterministic pseudo-Rust file of roughly `lines` lines, so
/// scrolling the code view can be exercised on realistic content.
fn large_sample(lines: usize) -> String {
let mut source = String::with_capacity(lines * 48);
let mut index = 0usize;
while source.lines().count() < lines {
let i = index;
source.push_str(&format!(
"// Section {i}: generated for the scroll performance test.\n"
));
source.push_str(&format!("struct Section{i} {{\n"));
source.push_str(" id: u64,\n");
source.push_str(" label: String,\n");
source.push_str(" scores: Vec<f64>,\n");
source.push_str("}\n\n");
source.push_str(&format!("impl Section{i} {{\n"));
source.push_str(" /// A one-line summary of this section.\n");
source.push_str(" fn describe(&self) -> String {\n");
source.push_str(&format!(
" format!(\"section {i}: {{}} at {{:?}}\", self.label, self.scores)\n"
));
source.push_str(" }\n");
source.push_str("}\n\n");
index += 1;
}
source
}
}
impl Render for CodeViewDemo {
@@ -3375,7 +3411,7 @@ impl Render for CodeViewDemo {
"source",
colors,
div()
.h(px(480.))
.h(rems(30.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3457,36 +3493,6 @@ enum DiffScenario {
/// The line count of each version in the large-diff sample.
const LARGE_DIFF_LINES: usize = 5000;
/// Generates a matched old/new pair of roughly `lines` lines each, with a
/// change every few lines so the diff has spread-out hunks.
fn large_diff_pair(lines: usize) -> (String, String) {
let mut old = String::with_capacity(lines * 48);
let mut new = String::with_capacity(lines * 48);
let mut index = 0usize;
while old.lines().count() < lines {
let i = index;
let comment = format!("// Handler {i} in the generated dispatch table.\n");
old.push_str(&comment);
new.push_str(&comment);
old.push_str(&format!("fn handle_{i}(payload: &str) -> bool {{\n"));
new.push_str(&format!("fn handle_{i}(payload: &Payload) -> bool {{\n"));
old.push_str(&format!(" payload.len() < {i} + 16\n"));
new.push_str(&format!(
" payload.size() < {i} + 16 && !payload.is_empty()\n"
));
if i.is_multiple_of(5) {
old.push_str(&format!(" log::debug(\"handler {i} ran\");\n"));
}
if i.is_multiple_of(7) {
new.push_str(" metrics::count(\"handle\");\n");
}
old.push_str("}\n\n");
new.push_str("}\n\n");
index += 1;
}
(old, new)
}
impl DiffViewDemo {
fn new(cx: &mut Context<Self>) -> Self {
let diff =
@@ -3494,7 +3500,7 @@ impl DiffViewDemo {
// The panel is a cached view in the dock: it re-renders only when
// the diff state it renders is notified.
let subscription = cx.observe(&diff, |_, _, cx| cx.notify());
let (large_old, large_new) = large_diff_pair(LARGE_DIFF_LINES);
let (large_old, large_new) = Self::large_diff_pair(LARGE_DIFF_LINES);
Self {
diff,
scenario: DiffScenario::Small,
@@ -3503,6 +3509,36 @@ impl DiffViewDemo {
_subscription: subscription,
}
}
/// Generates a matched old/new pair of roughly `lines` lines each, with a
/// change every few lines so the diff has spread-out hunks.
fn large_diff_pair(lines: usize) -> (String, String) {
let mut old = String::with_capacity(lines * 48);
let mut new = String::with_capacity(lines * 48);
let mut index = 0usize;
while old.lines().count() < lines {
let i = index;
let comment = format!("// Handler {i} in the generated dispatch table.\n");
old.push_str(&comment);
new.push_str(&comment);
old.push_str(&format!("fn handle_{i}(payload: &str) -> bool {{\n"));
new.push_str(&format!("fn handle_{i}(payload: &Payload) -> bool {{\n"));
old.push_str(&format!(" payload.len() < {i} + 16\n"));
new.push_str(&format!(
" payload.size() < {i} + 16 && !payload.is_empty()\n"
));
if i.is_multiple_of(5) {
old.push_str(&format!(" log::debug(\"handler {i} ran\");\n"));
}
if i.is_multiple_of(7) {
new.push_str(" metrics::count(\"handle\");\n");
}
old.push_str("}\n\n");
new.push_str("}\n\n");
index += 1;
}
(old, new)
}
}
impl Render for DiffViewDemo {
@@ -3548,7 +3584,7 @@ impl Render for DiffViewDemo {
"unified, highlighted, virtualized",
colors,
div()
.h(px(480.))
.h(rems(30.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3617,23 +3653,6 @@ That is the news.
/// The section count of the large markdown sample.
const LARGE_MARKDOWN_SECTIONS: usize = 1000;
/// Generates a long markdown document, so scrolling the view can be
/// exercised on realistic content.
fn large_markdown(sections: usize) -> String {
let mut source = String::new();
for section in 0..sections {
source.push_str(&format!("## Section {section}\n\n"));
source.push_str(&format!(
"Handler {section} retries stale sockets and logs every outcome.\n\n"
));
source.push_str(&format!(
"- [ ] Audit handler {section}\n- See [issue {section}](https://example.com/issues/{section})\n\n"
));
source.push_str("```rust\nfn handle() -> bool { true }\n```\n\n");
}
source
}
struct MarkdownViewDemo {
view: Entity<MarkdownViewState>,
sample: MarkdownSample,
@@ -3660,10 +3679,27 @@ impl MarkdownViewDemo {
Self {
view,
sample: MarkdownSample::Guide,
large_source: large_markdown(LARGE_MARKDOWN_SECTIONS).into(),
large_source: Self::large_markdown(LARGE_MARKDOWN_SECTIONS).into(),
_subscription: subscription,
}
}
/// Generates a long markdown document, so scrolling the view can be
/// exercised on realistic content.
fn large_markdown(sections: usize) -> String {
let mut source = String::new();
for section in 0..sections {
source.push_str(&format!("## Section {section}\n\n"));
source.push_str(&format!(
"Handler {section} retries stale sockets and logs every outcome.\n\n"
));
source.push_str(&format!(
"- [ ] Audit handler {section}\n- See [issue {section}](https://example.com/issues/{section})\n\n"
));
source.push_str("```rust\nfn handle() -> bool { true }\n```\n\n");
}
source
}
}
impl Render for MarkdownViewDemo {
@@ -3704,7 +3740,7 @@ impl Render for MarkdownViewDemo {
"rendered, virtualized, links clickable",
colors,
div()
.h(px(480.))
.h(rems(30.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3741,8 +3777,8 @@ impl Render for SidebarDemo {
"standalone; the app's own left sidebar is this component",
colors,
div()
.w(px(280.))
.h(px(360.))
.w(rems(17.5))
.h(rems(22.5))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3764,22 +3800,25 @@ impl Render for SidebarDemo {
};
vec![
SidebarEntry::header("Favorites"),
SidebarEntry::row("Documents").prefix(row_icon()),
SidebarEntry::row("Pictures")
SidebarEntry::row("Documents", "Documents")
.prefix(row_icon()),
SidebarEntry::row("Pictures", "Pictures")
.prefix(row_icon())
.detail("24")
.suffix(
Badge::new("sidebar-demo-new-badge", "new")
.variant(BadgeVariant::Accent),
),
SidebarEntry::row("Archive").disabled(true),
SidebarEntry::row("Archive", "Archive").disabled(true),
SidebarEntry::header("Locations"),
SidebarEntry::row("iCloud").prefix(row_icon()).suffix(
StatusDot::new("sidebar-demo-icloud-status")
.small()
.variant(StatusDotVariant::Success),
),
SidebarEntry::row("Trash"),
SidebarEntry::row("iCloud", "iCloud")
.prefix(row_icon())
.suffix(
StatusDot::new("sidebar-demo-icloud-status")
.small()
.variant(StatusDotVariant::Success),
),
SidebarEntry::row("Trash", "Trash"),
]
}),
),
@@ -3798,6 +3837,12 @@ struct ResizeDemo {
width: Pixels,
}
impl ResizeDemo {
const WIDTH_DEFAULT_REMS: f32 = 12.5;
const WIDTH_MIN_REMS: f32 = 6.;
const WIDTH_MAX_REMS: f32 = 20.;
}
impl Render for ResizeDemo {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let view = cx.entity();
@@ -3814,7 +3859,7 @@ impl Render for ResizeDemo {
"seam",
colors,
h_flex()
.h(px(112.))
.h(rems(7.))
.rounded(radius.control)
.border_1()
.border_color(colors.border)
@@ -3831,15 +3876,21 @@ impl Render for ResizeDemo {
)
.child(
ResizeHandle::new("resize-demo-handle")
.on_resize(move |delta, _, cx| {
.on_resize(move |delta, window, cx| {
view.update(cx, |demo, cx| {
demo.width = (demo.width + delta).clamp(px(96.), px(320.));
demo.width = (demo.width + delta).clamp(
rems(ResizeDemo::WIDTH_MIN_REMS)
.to_pixels(window.rem_size()),
rems(ResizeDemo::WIDTH_MAX_REMS)
.to_pixels(window.rem_size()),
);
cx.notify();
});
})
.on_reset(move |_, cx| {
.on_reset(move |window, cx| {
view_reset.update(cx, |demo, cx| {
demo.width = px(200.);
demo.width = rems(ResizeDemo::WIDTH_DEFAULT_REMS)
.to_pixels(window.rem_size());
cx.notify();
});
}),
@@ -3871,7 +3922,7 @@ impl Render for ResizeDemo {
struct ThemeDemo;
impl Render for ThemeDemo {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let theme = cx.theme();
let colors = theme.colors;
let radius = theme.radius;
@@ -3928,7 +3979,7 @@ impl Render for ThemeDemo {
(PresetChoice::TokyoNight, "Tokyo Night"),
])
.on_change(|next, _, cx| {
set_preset(next.name(), cx).ok();
set_preset(next.name(), cx).log_err();
}),
))
.child(demo_section(
@@ -3987,24 +4038,17 @@ impl Render for ThemeDemo {
"icons (Lucide fallback; platform resolvers land later)",
colors,
div().flex().flex_wrap().items_center().gap_4().children(
IconRole::all()
.iter()
.map(|role| icon(*role).size(px(20.)).color(colors.foreground)),
IconRole::all().iter().map(|role| {
icon(*role)
.size(rems(1.25).to_pixels(window.rem_size()))
.color(colors.foreground)
}),
),
)),
)
}
}
fn button_label(variant: ButtonVariant) -> &'static str {
match variant {
ButtonVariant::Primary => "Primary",
ButtonVariant::Secondary => "Secondary",
ButtonVariant::Ghost => "Ghost",
ButtonVariant::Destructive => "Delete",
}
}
fn section_label(text: &str, colors: gpui_native_theme::ColorTokens) -> impl IntoElement + use<> {
div()
.text_sm()
@@ -4028,23 +4072,22 @@ where
}
fn main() {
// Without a client gpui installs a NullHttpClient and remote images —
// the avatar pictures — silently never load.
application()
.with_assets(IconAssets)
.with_http_client(std::sync::Arc::new(reqwest_client::ReqwestClient::new()))
.run(|cx: &mut App| {
gpui_native_core::init(cx);
gpui_native_editor::code_view::bind_code_view_keys(cx);
cx.bind_keys([
KeyBinding::new("cmd-k", OpenCommandPalette, None),
KeyBinding::new("ctrl-k", OpenCommandPalette, None),
]);
let layout_state = LayoutState::new(cx);
let dock_state = DockState::new(cx);
// A panel that declares a right sidebar view asks for it to open on
// activation; visibility itself stays on the shell's LayoutState.
let layout_for_dock = layout_state.clone();
dock_state.update(cx, |dock, _| {
dock.on_open_right_sidebar(move |cx| {
layout_for_dock.update(cx, |state, cx| {
@@ -4052,29 +4095,27 @@ fn main() {
});
});
});
let bounds = Bounds::centered(None, size(px(960.0), px(640.0)), cx);
let theme = cx.theme();
if let Err(err) = cx.open_window(
WindowOptions {
window_bounds: Some(WindowBounds::Windowed(bounds)),
titlebar: Some(gpui::TitlebarOptions {
title: Some("gpui-native-kit gallery".into()),
// The shell's strips are the titlebar; the native one only
// hosts the traffic lights on macOS.
appears_transparent: true,
..Default::default()
}),
// The strips move the window themselves, so a tab dragged inside
// the titlebar band never drags the window with it.
app_owns_titlebar_drag: true,
// The platform's native backdrop; the theme's material paints
// resolve over it and go opaque under reduced transparency.
window_background: cx.theme().window_background,
window_background: theme.window_background,
..Default::default()
},
|window, cx| {
Theme::sync_from_window(window, cx);
Theme::sync_window_rem_size(window, cx);
// Detached on purpose: the subscription lives as long as the window.
window
.observe_window_appearance(|window, cx| {
Theme::sync_from_window(window, cx);
@@ -4082,13 +4123,11 @@ fn main() {
cx.refresh_windows();
})
.detach();
let toasts = cx.new(|_| Toasts::default());
let alerts = cx.new(|_| Alerts::default());
let gallery = cx.new(|cx| {
// Toast pushes and dismissals repaint the stack.
cx.observe(&toasts, |_, _, cx| cx.notify()).detach();
// Alert pushes and dismissals repaint it.
cx.observe(&alerts, |_, _, cx| cx.notify()).detach();
Gallery {
layout_state: layout_state.clone(),
dock_state: dock_state.clone(),
@@ -4099,8 +4138,15 @@ fn main() {
dialog_open: false,
alert_open: false,
sidebar_transparent: true,
_subscriptions: vec![
// Toast pushes and dismissals repaint the stack.
cx.observe(&toasts, |_, _, cx| cx.notify()),
// Alert pushes and dismissals repaint it.
cx.observe(&alerts, |_, _, cx| cx.notify()),
],
}
});
let palette = cx.new(|cx| {
let matcher = |query: &str, commands: &[Command]| {
let needle = query.to_lowercase();
@@ -4111,6 +4157,7 @@ fn main() {
.collect()
};
let gallery_for_execute = gallery.downgrade();
CommandPaletteState::new(
CommandPalette::new("gallery-palette", gallery_commands(), matcher)
.on_execute(move |command: &Command, _, cx| {
@@ -4119,16 +4166,18 @@ fn main() {
gallery.palette_runs.push(command.id.clone());
cx.notify();
})
.ok();
.log_err();
}),
window,
cx,
)
});
gallery.update(cx, |gallery, cx| {
gallery.palette = Some(palette.clone());
cx.notify();
});
dock_state.update(cx, |dock, cx| {
Gallery::register_panels(
dock,
@@ -4139,16 +4188,17 @@ fn main() {
gallery.downgrade(),
cx,
);
// TEMPORARY perf probe: open the field panel under the
// autoplay env var so the probe types into a real window.
let first = if std::env::var("GPUI_FIELD_AUTOPLAY").is_ok() {
eprintln!("[field-probe] autoplay: opening field panel");
"field"
} else {
"welcome"
};
dock.open_panel(first, window, cx);
});
gallery
},
) {