1008 lines
43 KiB
Rust
1008 lines
43 KiB
Rust
use std::time::Duration;
|
|
|
|
use gpui::prelude::FluentBuilder as _;
|
|
use gpui::{
|
|
App, AppContext as _, Bounds, ClickEvent, Context, Entity, InteractiveElement as _,
|
|
IntoElement, ParentElement as _, Render, SharedString, StatefulInteractiveElement as _,
|
|
Styled as _, Window, WindowBounds, WindowOptions, div, px, size,
|
|
};
|
|
use gpui_native_core::avatar::Avatar;
|
|
use gpui_native_core::button::{Button, ButtonVariant};
|
|
use gpui_native_core::checkbox::Checkbox;
|
|
use gpui_native_core::common::{Sizable as _, h_flex, v_flex};
|
|
use gpui_native_core::dialog::Dialog;
|
|
use gpui_native_core::input::{Input, InputState};
|
|
use gpui_native_core::link::Link;
|
|
use gpui_native_core::menu::{Menu, MenuItem};
|
|
use gpui_native_core::popover::Popover;
|
|
use gpui_native_core::progress::Progress;
|
|
use gpui_native_core::radio::{Radio, RadioGroup};
|
|
use gpui_native_core::scroll_area::ScrollArea;
|
|
use gpui_native_core::slider::Slider;
|
|
use gpui_native_core::switch::Switch;
|
|
use gpui_native_core::toast::{Toast, ToastSeverity, Toasts, ToastsView};
|
|
use gpui_native_core::toggle::Toggle;
|
|
use gpui_native_core::tooltip::Tooltip;
|
|
use gpui_native_data::list::{List, ListItem};
|
|
use gpui_native_icons::{IconAssets, IconRole, icon};
|
|
use gpui_native_theme::{ActiveTheme as _, Theme};
|
|
use gpui_platform::application;
|
|
|
|
struct Gallery {
|
|
button_clicks: usize,
|
|
checkbox_checked: bool,
|
|
switch_checked: bool,
|
|
plan: SharedString,
|
|
toggle_pressed: bool,
|
|
volume: f32,
|
|
input_state: Entity<InputState>,
|
|
popover_open: bool,
|
|
menu_open: bool,
|
|
dialog_open: bool,
|
|
alert_open: bool,
|
|
toasts: Entity<Toasts>,
|
|
scroll: gpui::ScrollHandle,
|
|
list_selected: Option<usize>,
|
|
}
|
|
|
|
impl Render for Gallery {
|
|
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;
|
|
let variant = theme.variant;
|
|
let source = gpui_native_theme::detected_source(cx);
|
|
let mode_override = gpui_native_theme::mode_override(cx);
|
|
let active_preset = gpui_native_theme::active_preset(cx);
|
|
let gallery = cx.entity();
|
|
|
|
let swatches: [(&str, gpui::Hsla); 18] = [
|
|
("background", colors.background),
|
|
("foreground", colors.foreground),
|
|
("surface", colors.surface),
|
|
("surface_foreground", colors.surface_foreground),
|
|
("primary", colors.primary),
|
|
("primary_foreground", colors.primary_foreground),
|
|
("secondary", colors.secondary),
|
|
("secondary_foreground", colors.secondary_foreground),
|
|
("muted", colors.muted),
|
|
("muted_foreground", colors.muted_foreground),
|
|
("accent", colors.accent),
|
|
("accent_foreground", colors.accent_foreground),
|
|
("destructive", colors.destructive),
|
|
("destructive_foreground", colors.destructive_foreground),
|
|
("border", colors.border),
|
|
("input", colors.input),
|
|
("ring", colors.ring),
|
|
("selection", colors.selection),
|
|
];
|
|
let shades = [
|
|
("base", theme.ramps.primary.base),
|
|
("hover", theme.ramps.primary.hover),
|
|
("pressed", theme.ramps.primary.pressed),
|
|
("subtle", theme.ramps.primary.subtle),
|
|
];
|
|
|
|
let modes: [(SharedString, Option<gpui_native_theme::ThemeVariant>, bool); 3] = [
|
|
("System".into(), None, mode_override.is_none()),
|
|
(
|
|
"Light".into(),
|
|
Some(gpui_native_theme::ThemeVariant::Light),
|
|
mode_override == Some(gpui_native_theme::ThemeVariant::Light),
|
|
),
|
|
(
|
|
"Dark".into(),
|
|
Some(gpui_native_theme::ThemeVariant::Dark),
|
|
mode_override == Some(gpui_native_theme::ThemeVariant::Dark),
|
|
),
|
|
];
|
|
let presets: [(SharedString, Option<&'static str>, bool); 2] = [
|
|
("Follow system".into(), None, active_preset.is_none()),
|
|
(
|
|
"Tokyo Night".into(),
|
|
Some("tokyo-night"),
|
|
active_preset == Some("tokyo-night"),
|
|
),
|
|
];
|
|
|
|
let button_variants = [
|
|
("primary", ButtonVariant::Primary),
|
|
("secondary", ButtonVariant::Secondary),
|
|
("ghost", ButtonVariant::Ghost),
|
|
("destructive", ButtonVariant::Destructive),
|
|
];
|
|
|
|
let content = div()
|
|
.font_family(".SystemUIFont")
|
|
.bg(colors.background)
|
|
.text_color(colors.foreground)
|
|
.p_6()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_5()
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_1()
|
|
.child(
|
|
div()
|
|
.text_xl()
|
|
.font_weight(gpui::FontWeight::SEMIBOLD)
|
|
.child("gpui-native-kit"),
|
|
)
|
|
.child(
|
|
div()
|
|
.text_sm()
|
|
.text_color(colors.muted_foreground)
|
|
.child(format!(
|
|
"phase C — button · source: {source} · variant: {variant:?} · control radius {}px",
|
|
f32::from(theme.radius.control)
|
|
)),
|
|
),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_2()
|
|
.child(section_label("mode", colors))
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_2()
|
|
.children(modes.map(|(label, value, active)| {
|
|
chip(
|
|
format!("mode-{label}"),
|
|
label,
|
|
active,
|
|
colors,
|
|
radius,
|
|
move |_, _, cx| gpui_native_theme::set_mode_override(value, cx),
|
|
)
|
|
})),
|
|
),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_2()
|
|
.child(section_label("preset", colors))
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.gap_2()
|
|
.children(presets.map(|(label, value, active)| {
|
|
chip(
|
|
format!("preset-{label}"),
|
|
label,
|
|
active,
|
|
colors,
|
|
radius,
|
|
move |_, _, cx| {
|
|
let _ = gpui_native_theme::set_preset(value, cx);
|
|
},
|
|
)
|
|
})),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"button · native refs — NSButton, WinUI Button, Adwaita, Breeze",
|
|
colors,
|
|
))
|
|
.child(
|
|
h_flex()
|
|
.flex_wrap()
|
|
.gap_2()
|
|
.children(button_variants.map(|(name, variant)| {
|
|
let gallery = gallery.clone();
|
|
let mut button = Button::new(SharedString::from(format!(
|
|
"gallery-button-{name}"
|
|
)))
|
|
.label(button_label(variant))
|
|
.variant(variant)
|
|
.on_click(move |_, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.button_clicks += 1;
|
|
cx.notify();
|
|
});
|
|
});
|
|
if variant == ButtonVariant::Destructive {
|
|
button =
|
|
button.tooltip(Tooltip::text("Removes the selected item"));
|
|
}
|
|
button
|
|
}))
|
|
.child(
|
|
Button::new("gallery-button-disabled")
|
|
.label("Disabled")
|
|
.disabled(true),
|
|
)
|
|
.child(
|
|
Button::new("gallery-button-icon")
|
|
.label("Open")
|
|
.icon(IconRole::Folder)
|
|
.tooltip(Tooltip::text("Opens the selected file")),
|
|
),
|
|
)
|
|
.child(
|
|
h_flex()
|
|
.items_center()
|
|
.gap_2()
|
|
.child(Button::new("gallery-button-sm").label("Small").small())
|
|
.child(Button::new("gallery-button-md").label("Medium"))
|
|
.child(Button::new("gallery-button-lg").label("Large").large()),
|
|
)
|
|
.child(
|
|
div()
|
|
.text_xs()
|
|
.text_color(colors.muted_foreground)
|
|
.child(format!("clicked {} times", self.button_clicks)),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"input · native refs — NSTextField, WinUI TextBox, Adwaita Entry, Breeze LineEdit",
|
|
colors,
|
|
))
|
|
.child(
|
|
Input::new("gallery-input", self.input_state.clone())
|
|
.placeholder("Type here…"),
|
|
)
|
|
.child(
|
|
div()
|
|
.text_xs()
|
|
.text_color(colors.muted_foreground)
|
|
.child(format!(
|
|
"value: {}",
|
|
self.input_state.read(cx).value()
|
|
)),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"checkbox · native refs — NSButton (checkbox), WinUI CheckBox, Adwaita CheckButton, Breeze",
|
|
colors,
|
|
))
|
|
.child(
|
|
v_flex().gap_2().child({
|
|
let gallery = gallery.clone();
|
|
Checkbox::new("gallery-checkbox")
|
|
.label("Enable telemetry")
|
|
.checked(self.checkbox_checked)
|
|
.on_change(move |checked, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.checkbox_checked = checked;
|
|
cx.notify();
|
|
});
|
|
})
|
|
})
|
|
.child(
|
|
Checkbox::new("gallery-checkbox-disabled-off")
|
|
.label("Disabled unchecked")
|
|
.disabled(true),
|
|
)
|
|
.child(
|
|
Checkbox::new("gallery-checkbox-disabled-on")
|
|
.label("Disabled checked")
|
|
.checked(true)
|
|
.disabled(true),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"menu · native ref — NSMenu, WinUI MenuFlyout, Adwaita popover menu",
|
|
colors,
|
|
))
|
|
.child({
|
|
let gallery = gallery.clone();
|
|
Menu::new("gallery-menu")
|
|
.open(self.menu_open)
|
|
.on_change(move |open, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.menu_open = open;
|
|
cx.notify();
|
|
});
|
|
})
|
|
.trigger(|_, _| {
|
|
Button::new("gallery-menu-trigger")
|
|
.label("Actions")
|
|
.variant(ButtonVariant::Secondary)
|
|
.into_any_element()
|
|
})
|
|
.items(|_, _| {
|
|
vec![
|
|
MenuItem::new("Cut")
|
|
.shortcut("cmd-x")
|
|
.handler(|_, cx| {
|
|
eprintln!("menu: cut");
|
|
let _ = cx;
|
|
}),
|
|
MenuItem::new("Copy").shortcut("cmd-c"),
|
|
MenuItem::new("Paste").shortcut("cmd-v"),
|
|
MenuItem::separator(),
|
|
MenuItem::new("Use quiet mode").checked(true),
|
|
MenuItem::new("Show hidden files"),
|
|
MenuItem::separator(),
|
|
MenuItem::new("Unavailable").disabled(true),
|
|
MenuItem::new("Delete").destructive(true),
|
|
]
|
|
})
|
|
}),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"list · native refs — NSTableView (list), WinUI ListView, Adwaita ListBox, Breeze",
|
|
colors,
|
|
))
|
|
.child({
|
|
let gallery = gallery.clone();
|
|
List::new("gallery-list")
|
|
.selected(self.list_selected)
|
|
.on_change(move |selected, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.list_selected = selected;
|
|
cx.notify();
|
|
});
|
|
})
|
|
.items(|_, _| {
|
|
vec![
|
|
ListItem::new("Documents").icon(IconRole::Folder),
|
|
ListItem::new("Pictures")
|
|
.icon(IconRole::Folder)
|
|
.detail("24 items"),
|
|
ListItem::new("Music").icon(IconRole::Folder),
|
|
ListItem::new("Archive").disabled(true),
|
|
ListItem::new("Trash").detail("emptied weekly"),
|
|
]
|
|
})
|
|
})
|
|
.child(
|
|
div()
|
|
.text_xs()
|
|
.text_color(colors.muted_foreground)
|
|
.child(format!("selected: {:?}", self.list_selected)),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"toast · native ref — NSUserNotification stack, WinUI InfoBar, Adwaita in-app notifications",
|
|
colors,
|
|
))
|
|
.child(
|
|
div().flex().gap_2().children([
|
|
{
|
|
let toasts = self.toasts.clone();
|
|
Button::new("gallery-toast-success")
|
|
.label("Success")
|
|
.variant(ButtonVariant::Secondary)
|
|
.on_click(move |_, _, cx| {
|
|
toasts.update(cx, |toasts, cx| {
|
|
toasts.push(
|
|
Toast::new("Workspace saved")
|
|
.message("All changes written to disk.")
|
|
.severity(ToastSeverity::Success)
|
|
.no_icon(),
|
|
cx,
|
|
);
|
|
});
|
|
})
|
|
},
|
|
{
|
|
let toasts = self.toasts.clone();
|
|
Button::new("gallery-toast-warning")
|
|
.label("Warning")
|
|
.variant(ButtonVariant::Secondary)
|
|
.on_click(move |_, _, cx| {
|
|
toasts.update(cx, |toasts, cx| {
|
|
toasts.push(
|
|
Toast::new("Disk almost full")
|
|
.message("1.2 GB remaining.")
|
|
.severity(ToastSeverity::Warning),
|
|
cx,
|
|
);
|
|
});
|
|
})
|
|
},
|
|
{
|
|
let toasts = self.toasts.clone();
|
|
Button::new("gallery-toast-danger")
|
|
.label("Danger (sticky)")
|
|
.variant(ButtonVariant::Destructive)
|
|
.on_click(move |_, _, cx| {
|
|
toasts.update(cx, |toasts, cx| {
|
|
toasts.push(
|
|
Toast::new("Sync failed")
|
|
.message("Will retry when online.")
|
|
.severity(ToastSeverity::Danger)
|
|
.duration(Duration::ZERO),
|
|
cx,
|
|
);
|
|
});
|
|
})
|
|
},
|
|
]),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"dialog · alert · native refs — NSAlert, WinUI ContentDialog, Adwaita MessageDialog",
|
|
colors,
|
|
))
|
|
.child(
|
|
div().flex().gap_2().children([
|
|
{
|
|
let gallery = gallery.clone();
|
|
Button::new("gallery-dialog-open")
|
|
.label("Delete file…")
|
|
.variant(ButtonVariant::Secondary)
|
|
.on_click(move |_, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.dialog_open = true;
|
|
cx.notify();
|
|
});
|
|
})
|
|
},
|
|
{
|
|
let gallery = gallery.clone();
|
|
Button::new("gallery-alert-open")
|
|
.label("Sign out…")
|
|
.variant(ButtonVariant::Secondary)
|
|
.on_click(move |_, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.alert_open = true;
|
|
cx.notify();
|
|
});
|
|
})
|
|
},
|
|
]),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"popover · native ref — NSPopover, WinUI Flyout, Adwaita Popover",
|
|
colors,
|
|
))
|
|
.child({
|
|
let gallery = gallery.clone();
|
|
Popover::new("gallery-popover")
|
|
.open(self.popover_open)
|
|
.on_change(move |open, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.popover_open = open;
|
|
cx.notify();
|
|
});
|
|
})
|
|
.trigger(|_, _| {
|
|
Button::new("gallery-popover-trigger")
|
|
.label("Options")
|
|
.variant(ButtonVariant::Secondary)
|
|
.into_any_element()
|
|
})
|
|
.content(move |_, _| {
|
|
v_flex()
|
|
.gap_1()
|
|
.w(px(240.))
|
|
.p_2()
|
|
.child(section_label("appearance", colors))
|
|
.child(
|
|
Checkbox::new("gallery-popover-telemetry")
|
|
.label("Enable telemetry"),
|
|
)
|
|
.child(
|
|
Checkbox::new("gallery-popover-crash")
|
|
.label("Send crash reports"),
|
|
)
|
|
.into_any_element()
|
|
})
|
|
}),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"switch · native refs — NSSwitch, WinUI ToggleSwitch, Adwaita Switch, Breeze Switch",
|
|
colors,
|
|
))
|
|
.child(
|
|
v_flex().gap_2().child({
|
|
let gallery = gallery.clone();
|
|
Switch::new("gallery-switch")
|
|
.label("Wi-Fi")
|
|
.checked(self.switch_checked)
|
|
.on_change(move |checked, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.switch_checked = checked;
|
|
cx.notify();
|
|
});
|
|
})
|
|
})
|
|
.child(
|
|
Switch::new("gallery-switch-disabled-off")
|
|
.label("Disabled off")
|
|
.disabled(true),
|
|
)
|
|
.child(
|
|
Switch::new("gallery-switch-disabled-on")
|
|
.label("Disabled on")
|
|
.checked(true)
|
|
.disabled(true),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"radio · native refs — NSButton (radio), WinUI RadioButton, Adwaita CheckButton (radio), Breeze",
|
|
colors,
|
|
))
|
|
.child({
|
|
let gallery = gallery.clone();
|
|
RadioGroup::new("gallery-radio-group")
|
|
.selected(Some(self.plan.clone()))
|
|
.on_change(move |plan, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.plan = plan;
|
|
cx.notify();
|
|
});
|
|
})
|
|
.child(Radio::new("radio-starter", SharedString::from("starter")).label("Starter"))
|
|
.child(Radio::new("radio-pro", SharedString::from("pro")).label("Pro"))
|
|
.child(
|
|
Radio::new("radio-team", SharedString::from("team"))
|
|
.label("Team")
|
|
.disabled(true),
|
|
)
|
|
}),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"toggle · native refs — NSButton (toggle), WinUI ToggleButton, Adwaita ToggleButton, Breeze",
|
|
colors,
|
|
))
|
|
.child(
|
|
h_flex().gap_2().child({
|
|
let gallery = gallery.clone();
|
|
Toggle::new("gallery-toggle")
|
|
.label("Bold")
|
|
.pressed(self.toggle_pressed)
|
|
.on_change(move |pressed, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.toggle_pressed = pressed;
|
|
cx.notify();
|
|
});
|
|
})
|
|
})
|
|
.child(
|
|
Toggle::new("gallery-toggle-disabled")
|
|
.label("Disabled")
|
|
.pressed(true)
|
|
.disabled(true),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"link · native refs — NSTextView links, WinUI HyperlinkButton, Adwaita LinkButton, Breeze",
|
|
colors,
|
|
))
|
|
.child(
|
|
h_flex()
|
|
.gap_4()
|
|
.child(
|
|
Link::new("gallery-link-github")
|
|
.label("gpui on GitHub")
|
|
.href("https://github.com/zed-industries/zed"),
|
|
)
|
|
.child({
|
|
let gallery = gallery.clone();
|
|
Link::new("gallery-link-override")
|
|
.label("On-click override")
|
|
.href("https://example.com/never-opened")
|
|
.on_click(move |_, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.button_clicks += 1;
|
|
cx.notify();
|
|
});
|
|
})
|
|
})
|
|
.child(
|
|
Link::new("gallery-link-disabled")
|
|
.label("Disabled")
|
|
.href("https://example.com/disabled")
|
|
.disabled(true),
|
|
),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"slider · native refs — NSSlider, WinUI Slider, Adwaita Scale, Breeze Slider",
|
|
colors,
|
|
))
|
|
.child({
|
|
let gallery = gallery.clone();
|
|
Slider::new("gallery-slider", self.volume)
|
|
.min(0.0)
|
|
.max(100.0)
|
|
.step(5.0)
|
|
.accessibility_label("Volume")
|
|
.on_change(move |volume, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.volume = volume;
|
|
cx.notify();
|
|
});
|
|
})
|
|
})
|
|
.child(
|
|
div()
|
|
.text_xs()
|
|
.text_color(colors.muted_foreground)
|
|
.child(format!("volume: {}", self.volume)),
|
|
),
|
|
)
|
|
.child(
|
|
v_flex()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"progress · native refs — NSProgressIndicator, WinUI ProgressBar, Adwaita ProgressBar, Breeze",
|
|
colors,
|
|
))
|
|
.child(Progress::new("progress-25", 0.25).accessibility_label("Quarter"))
|
|
.child(Progress::new("progress-60", 0.6).accessibility_label("Most"))
|
|
.child(Progress::new("progress-90", 0.9).accessibility_label("Nearly"))
|
|
.child(
|
|
Progress::new("progress-indeterminate", 0.0)
|
|
.indeterminate(true)
|
|
.accessibility_label("Working"),
|
|
),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"avatar · native refs — NSImage (contact), WinUI PersonPicture, Adwaita Avatar, Breeze",
|
|
colors,
|
|
))
|
|
.child(
|
|
h_flex()
|
|
.gap_3()
|
|
.child(
|
|
Avatar::new("reya@reya.info")
|
|
.name("Reya Adele")
|
|
.picture(Some("https://github.com/reya.png")),
|
|
)
|
|
.child(
|
|
Avatar::new("ada@reya.info")
|
|
.name("Ada Lovelace")
|
|
.picture(Some("https://example.com/missing.png")),
|
|
)
|
|
.child(Avatar::new("grace@reya.info").name("Grace Hopper").small())
|
|
.child(Avatar::new("desk@reya.info").name("Help Desk").large()),
|
|
),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_2()
|
|
.child(section_label("accent shades (derived, OKLCH)", colors))
|
|
.child(div().flex().gap_2().children(shades.map(|(name, color)| {
|
|
div()
|
|
.flex()
|
|
.items_center()
|
|
.gap_2()
|
|
.child(
|
|
div()
|
|
.size_6()
|
|
.rounded(radius.full)
|
|
.border_1()
|
|
.border_color(colors.border)
|
|
.bg(color),
|
|
)
|
|
.child(
|
|
div()
|
|
.text_xs()
|
|
.text_color(colors.muted_foreground)
|
|
.child(name),
|
|
)
|
|
}))),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_2()
|
|
.child(section_label("color tokens", colors))
|
|
.child(div().flex().flex_wrap().gap_2().children(swatches.map(
|
|
|(name, color)| {
|
|
div()
|
|
.flex()
|
|
.items_center()
|
|
.gap_2()
|
|
.child(
|
|
div()
|
|
.size_6()
|
|
.rounded(radius.full)
|
|
.border_1()
|
|
.border_color(colors.border)
|
|
.bg(color),
|
|
)
|
|
.child(
|
|
div()
|
|
.text_xs()
|
|
.text_color(colors.muted_foreground)
|
|
.child(name),
|
|
)
|
|
},
|
|
))),
|
|
)
|
|
.child(
|
|
div()
|
|
.flex()
|
|
.flex_col()
|
|
.gap_2()
|
|
.child(section_label(
|
|
"icons (Lucide fallback; platform resolvers land later)",
|
|
colors,
|
|
))
|
|
.child(
|
|
div().flex().items_center().gap_4().children(
|
|
IconRole::all()
|
|
.iter()
|
|
.map(|role| icon(*role).size(px(20.)).color(colors.foreground)),
|
|
),
|
|
),
|
|
);
|
|
|
|
// The whole gallery scrolls, demonstrating the scroll area at the
|
|
// page level: the thumb floats over the window's content. The
|
|
// dialogs render outside it, as the last children of the window, so
|
|
// their overlay layer paints over everything.
|
|
let dialog_gallery = gallery.clone();
|
|
let dialog = Dialog::new("gallery-dialog")
|
|
.open(self.dialog_open)
|
|
.title("Delete file?")
|
|
.on_change({
|
|
let gallery = dialog_gallery.clone();
|
|
move |open, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.dialog_open = open;
|
|
cx.notify();
|
|
});
|
|
}
|
|
})
|
|
.content(|_, _| {
|
|
div()
|
|
.child("\"report-final.pdf\" will be removed permanently.")
|
|
.into_any_element()
|
|
})
|
|
.actions(move |_, _| {
|
|
let gallery = dialog_gallery.clone();
|
|
vec![
|
|
Button::new("gallery-dialog-cancel")
|
|
.label("Cancel")
|
|
.variant(ButtonVariant::Secondary)
|
|
.on_click({
|
|
let gallery = gallery.clone();
|
|
move |_, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.dialog_open = false;
|
|
cx.notify();
|
|
});
|
|
}
|
|
})
|
|
.into_any_element(),
|
|
Button::new("gallery-dialog-delete")
|
|
.label("Delete")
|
|
.variant(ButtonVariant::Destructive)
|
|
.on_click({
|
|
let gallery = gallery.clone();
|
|
move |_, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.dialog_open = false;
|
|
cx.notify();
|
|
});
|
|
}
|
|
})
|
|
.into_any_element(),
|
|
]
|
|
});
|
|
|
|
let alert_gallery = gallery.clone();
|
|
let alert = Dialog::new("gallery-alert")
|
|
.open(self.alert_open)
|
|
.alert(true)
|
|
.title("Sign out of the workspace?")
|
|
.on_change({
|
|
let gallery = alert_gallery.clone();
|
|
move |open, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.alert_open = open;
|
|
cx.notify();
|
|
});
|
|
}
|
|
})
|
|
.content(|_, _| {
|
|
div()
|
|
.child("Any running agents will keep working in the background.")
|
|
.into_any_element()
|
|
})
|
|
.actions(move |_, _| {
|
|
let gallery = alert_gallery.clone();
|
|
vec![
|
|
Button::new("gallery-alert-stay")
|
|
.label("Stay")
|
|
.variant(ButtonVariant::Secondary)
|
|
.on_click({
|
|
let gallery = gallery.clone();
|
|
move |_, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.alert_open = false;
|
|
cx.notify();
|
|
});
|
|
}
|
|
})
|
|
.into_any_element(),
|
|
Button::new("gallery-alert-signout")
|
|
.label("Sign out")
|
|
.variant(ButtonVariant::Primary)
|
|
.on_click({
|
|
let gallery = gallery.clone();
|
|
move |_, _, cx| {
|
|
gallery.update(cx, |gallery, cx| {
|
|
gallery.alert_open = false;
|
|
cx.notify();
|
|
});
|
|
}
|
|
})
|
|
.into_any_element(),
|
|
]
|
|
});
|
|
|
|
div()
|
|
.size_full()
|
|
.child(
|
|
ScrollArea::new("gallery-scroll")
|
|
.handle(self.scroll.clone())
|
|
.size_full()
|
|
.child(content),
|
|
)
|
|
.child(dialog)
|
|
.child(alert)
|
|
.child(ToastsView::new("gallery-toasts", self.toasts.clone()))
|
|
}
|
|
}
|
|
|
|
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()
|
|
.font_weight(gpui::FontWeight::MEDIUM)
|
|
.text_color(colors.muted_foreground)
|
|
.child(text.to_string())
|
|
}
|
|
|
|
fn chip<F>(
|
|
id: String,
|
|
label: SharedString,
|
|
active: bool,
|
|
colors: gpui_native_theme::ColorTokens,
|
|
radius: gpui_native_theme::Radius,
|
|
on_click: F,
|
|
) -> impl IntoElement + use<F>
|
|
where
|
|
F: Fn(&ClickEvent, &mut Window, &mut App) + 'static,
|
|
{
|
|
div()
|
|
.id(SharedString::from(id))
|
|
.px_3()
|
|
.py_1()
|
|
.rounded(radius.full)
|
|
.border_1()
|
|
.text_xs()
|
|
.cursor_pointer()
|
|
.when(active, |el| {
|
|
el.bg(colors.primary)
|
|
.border_color(colors.primary)
|
|
.text_color(colors.primary_foreground)
|
|
})
|
|
.when(!active, |el| {
|
|
el.bg(colors.secondary)
|
|
.border_color(colors.border)
|
|
.text_color(colors.secondary_foreground)
|
|
.hover(|el| el.bg(colors.muted))
|
|
})
|
|
.on_click(move |event, window, cx| on_click(event, window, cx))
|
|
.child(label)
|
|
}
|
|
|
|
fn main() {
|
|
application().with_assets(IconAssets).run(|cx: &mut App| {
|
|
gpui_native_core::init(cx);
|
|
let input_state = InputState::new(cx);
|
|
let bounds = Bounds::centered(None, size(px(720.0), px(560.0)), cx);
|
|
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()),
|
|
..Default::default()
|
|
}),
|
|
..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);
|
|
Theme::sync_window_rem_size(window, cx);
|
|
cx.refresh_windows();
|
|
})
|
|
.detach();
|
|
let toasts = cx.new(|_| Toasts::default());
|
|
cx.new(|cx| {
|
|
// Toast pushes and dismissals repaint the stack.
|
|
cx.observe(&toasts, |_, _, cx| cx.notify()).detach();
|
|
Gallery {
|
|
button_clicks: 0,
|
|
checkbox_checked: false,
|
|
switch_checked: true,
|
|
plan: SharedString::from("starter"),
|
|
toggle_pressed: false,
|
|
volume: 40.0,
|
|
input_state,
|
|
popover_open: false,
|
|
menu_open: false,
|
|
dialog_open: false,
|
|
alert_open: false,
|
|
toasts,
|
|
scroll: gpui::ScrollHandle::new(),
|
|
list_selected: None,
|
|
}
|
|
})
|
|
},
|
|
) {
|
|
eprintln!("failed to open gallery window: {err}");
|
|
}
|
|
cx.activate(true);
|
|
});
|
|
}
|