use std::time::Duration; use gpui::prelude::FluentBuilder as _; use gpui::{ App, AppContext as _, Bounds, Context, Entity, FontWeight, IntoElement, ParentElement as _, Pixels, Render, SharedString, Styled as _, WeakEntity, Window, WindowBounds, WindowOptions, div, px, size, }; use gpui_native_core::alert::{Alert, AlertSeverity, Alerts, AlertsView}; use gpui_native_core::avatar::Avatar; use gpui_native_core::badge::{Badge, BadgeVariant}; use gpui_native_core::button::{Button, ButtonVariant}; use gpui_native_core::checkbox::Checkbox; use gpui_native_core::common::{Sizable as _, Size, h_flex, v_flex}; use gpui_native_core::context_menu::ContextMenu; use gpui_native_core::dialog::Dialog; use gpui_native_core::empty_state::EmptyState; 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, ScrollAxes}; use gpui_native_core::segmented::{Segmented, SegmentedOption}; use gpui_native_core::select::{Select, SelectOption}; use gpui_native_core::slider::Slider; use gpui_native_core::switch::Switch; use gpui_native_core::toast::{Toast, ToastAction, Toasts, ToastsView}; use gpui_native_core::toggle::Toggle; use gpui_native_core::tooltip::Tooltip; use gpui_native_data::list::{List, ListItem}; use gpui_native_data::sidebar::{Sidebar, SidebarEntry}; use gpui_native_data::tabs::{Tab, Tabs, TabsVariant}; use gpui_native_data::tree::{Tree, TreeItem}; use gpui_native_icons::{IconAssets, IconRole, icon}; use gpui_native_layout::dock::{Dock, DockSidebar, DockState, PanelDescriptor, SplitDirection}; 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, }; use gpui_platform::application; /// The component catalog: (group, [(panel id, sidebar label)]). const CATALOG: &[(&str, &[(&str, &str)])] = &[ ( "Controls", &[ ("button", "Button"), ("input", "Input"), ("checkbox", "Checkbox"), ("radio", "Radio"), ("switch", "Switch"), ("toggle", "Toggle"), ("slider", "Slider"), ("segmented", "Segmented"), ("select", "Select"), ("link", "Link"), ], ), ( "Data", &[ ("badge", "Badge"), ("avatar", "Avatar"), ("progress", "Progress"), ("list", "List"), ("tree", "Tree"), ("tabs", "Tabs"), ], ), ( "Feedback", &[ ("alert", "Alert"), ("toast", "Toast"), ("dialog", "Dialog"), ("menu", "Menu"), ("context-menu", "Context menu"), ("popover", "Popover"), ("tooltip", "Tooltip"), ("empty-state", "Empty state"), ("scroll-area", "Scroll area"), ], ), ( "Layout", &[ ("welcome", "Welcome"), ("sidebar", "Sidebar"), ("resize-handle", "Resize handle"), ], ), ("Theme", &[("theme", "Theme")]), ]; /// The select demo's value. #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Strategy { Curated, Uncensored, } /// The segmented demo's value. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum ExploreFilter { #[default] All, Popular, Recent, } /// The theme demo's mode choice, flattening `Option`. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum ModeChoice { #[default] System, Light, Dark, } impl ModeChoice { fn of(variant: Option) -> Self { match variant { None => Self::System, Some(ThemeVariant::Light) => Self::Light, Some(ThemeVariant::Dark) => Self::Dark, } } fn variant(self) -> Option { match self { Self::System => None, Self::Light => Some(ThemeVariant::Light), Self::Dark => Some(ThemeVariant::Dark), } } } /// The theme demo's preset choice. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] enum PresetChoice { #[default] FollowSystem, TokyoNight, } impl PresetChoice { fn of(name: Option<&str>) -> Self { match name { Some("tokyo-night") => Self::TokyoNight, _ => Self::FollowSystem, } } fn name(self) -> Option<&'static str> { match self { Self::FollowSystem => None, Self::TokyoNight => Some("tokyo-night"), } } } /// The context menu demo's actions. const CONTEXT_ACTIONS: [&str; 3] = ["Rename", "Duplicate", "Archive"]; struct Gallery { layout_state: Entity, dock_state: Entity, alerts: Entity, toasts: Entity, dialog_open: bool, alert_open: bool, } impl Gallery { /// Every sidebar entry's panel id, in order. Headers carry `None`: the /// sidebar's selection index counts them like any entry. fn panel_ids() -> Vec> { CATALOG .iter() .flat_map(|(_, panels)| { let mut ids = vec![None]; ids.extend(panels.iter().map(|(id, _)| Some(*id))); ids }) .collect() } fn render_shell(&self, cx: &App) -> Shell { let panel_ids = Self::panel_ids(); let selected = self .dock_state .read(cx) .active_panel() .as_deref() .and_then(|active| { panel_ids .iter() .position(|id| id.as_deref() == Some(active)) }); let left_sidebar = Sidebar::new("gallery-sidebar") .selected(selected) .accessibility_label("Component catalog") .entries(|_, _| { CATALOG .iter() .flat_map(|(group, panels)| { let mut entries = vec![SidebarEntry::header(*group)]; entries.extend(panels.iter().map(|(_, label)| SidebarEntry::row(*label))); entries }) .collect() }) .on_change({ let dock_state = self.dock_state.clone(); move |index, window, cx| { if let Some(Some(id)) = index.and_then(|index| panel_ids.get(index)) { dock_state.update(cx, |dock, cx| dock.open_panel(id, window, cx)); } } }); let center = Dock::new("gallery-dock", self.dock_state.clone()) // The pill bar is the titlebar strip whenever no right sidebar // sits to its right. .window_controls(!self.layout_state.read(cx).is_right_sidebar_visible()) .traffic_light_pad(!self.layout_state.read(cx).is_left_sidebar_visible()); let right_sidebar = DockSidebar::new("gallery-dock-sidebar", self.dock_state.clone()); Shell::new("gallery-shell", self.layout_state.clone()) .left_sidebar(left_sidebar) .center(center) .right_sidebar(right_sidebar) } fn render_dialog(open: bool, gallery: WeakEntity) -> Dialog { let gallery_change = gallery.clone(); Dialog::new("gallery-dialog") .open(open) .title("Delete file?") .on_change(move |open, _, cx| { gallery_change .update(cx, |gallery, cx| { gallery.dialog_open = open; cx.notify(); }) .ok(); }) .content(|_, _| { div() .child("\"report-final.pdf\" will be removed permanently.") .into_any_element() }) .actions(move |_, _| { let gallery_cancel = gallery.clone(); let gallery_delete = gallery.clone(); vec![ Button::new("gallery-dialog-cancel") .label("Cancel") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { gallery_cancel .update(cx, |gallery, cx| { gallery.dialog_open = false; cx.notify(); }) .ok(); }) .into_any_element(), Button::new("gallery-dialog-delete") .label("Delete") .variant(ButtonVariant::Destructive) .on_click(move |_, _, cx| { gallery_delete .update(cx, |gallery, cx| { gallery.dialog_open = false; cx.notify(); }) .ok(); }) .into_any_element(), ] }) } fn render_alert_dialog(open: bool, gallery: WeakEntity) -> Dialog { let gallery_change = gallery.clone(); Dialog::new("gallery-alert") .open(open) .alert(true) .title("Sign out of the workspace?") .on_change(move |open, _, cx| { gallery_change .update(cx, |gallery, cx| { gallery.alert_open = open; cx.notify(); }) .ok(); }) .content(|_, _| { div() .child("Any running agents will keep working in the background.") .into_any_element() }) .actions(move |_, _| { let gallery_stay = gallery.clone(); let gallery_signout = gallery.clone(); vec![ Button::new("gallery-alert-stay") .label("Stay") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { gallery_stay .update(cx, |gallery, cx| { gallery.alert_open = false; cx.notify(); }) .ok(); }) .into_any_element(), Button::new("gallery-alert-signout") .label("Sign out") .variant(ButtonVariant::Primary) .on_click(move |_, _, cx| { gallery_signout .update(cx, |gallery, cx| { gallery.alert_open = false; cx.notify(); }) .ok(); }) .into_any_element(), ] }) } fn register_panels( dock: &mut DockState, layout_state: Entity, alerts: Entity, toasts: Entity, gallery: WeakEntity, cx: &mut Context, ) { let dock_state = cx.entity(); dock.register_panel( PanelDescriptor::new("welcome", "Welcome", { let layout_state = layout_state.clone(); let dock_state = dock_state.clone(); move |_, cx| { cx.new(|_| WelcomePanel { layout_state: layout_state.clone(), dock_state: dock_state.clone(), }) .into() } }) .right_sidebar(|_, cx| { cx.new(|_| RightDetails { title: "Welcome", body: "Panels can declare this view; activating one opens the sidebar.", }) .into() }), cx, ); dock.register_panel( PanelDescriptor::new("button", "Button", |_, cx| { cx.new(|_| ButtonDemo { clicks: 0 }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("input", "Input", |_, cx| cx.new(InputDemo::new).into()), cx, ); dock.register_panel( PanelDescriptor::new("checkbox", "Checkbox", |_, cx| { cx.new(|_| CheckboxDemo { checked: false }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("radio", "Radio", |_, cx| { cx.new(|_| RadioDemo { plan: SharedString::from("starter"), }) .into() }), cx, ); dock.register_panel( PanelDescriptor::new("switch", "Switch", |_, cx| { cx.new(|_| SwitchDemo { checked: true }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("toggle", "Toggle", |_, cx| { cx.new(|_| ToggleDemo { pressed: false }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("slider", "Slider", |_, cx| { cx.new(|_| SliderDemo { volume: 40.0 }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("segmented", "Segmented", |_, cx| { cx.new(|_| SegmentedDemo { filter: ExploreFilter::default(), }) .into() }), cx, ); dock.register_panel( PanelDescriptor::new("select", "Select", |_, cx| { cx.new(|_| SelectDemo { strategy: Some(Strategy::Curated), }) .into() }), cx, ); dock.register_panel( PanelDescriptor::new("link", "Link", |_, cx| { cx.new(|_| LinkDemo { clicks: 0 }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("badge", "Badge", |_, cx| cx.new(|_| BadgeDemo).into()), cx, ); dock.register_panel( PanelDescriptor::new("avatar", "Avatar", |_, cx| cx.new(|_| AvatarDemo).into()), cx, ); dock.register_panel( PanelDescriptor::new("progress", "Progress", |_, cx| { cx.new(|_| ProgressDemo).into() }), cx, ); dock.register_panel( PanelDescriptor::new("list", "List", |_, cx| { cx.new(|_| ListDemo { selected: None }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("tree", "Tree", |_, cx| { cx.new(|_| TreeDemo { nodes: TreeDemo::demo(), selected: None, }) .into() }), cx, ); dock.register_panel( PanelDescriptor::new("tabs", "Tabs", |_, cx| { cx.new(|_| TabsDemo { selected: 0, pill_tabs: vec![ SharedString::from("Explorer"), SharedString::from("Syncing"), SharedString::from("Blame"), ], pill_selected: 0, }) .into() }), cx, ); dock.register_panel( PanelDescriptor::new("alert", "Alert", { let alerts = alerts.clone(); move |_, cx| { cx.new(|_| AlertDemo { alerts: alerts.clone(), dismissed: false, }) .into() } }), cx, ); dock.register_panel( PanelDescriptor::new("toast", "Toast", { let toasts = toasts.clone(); move |_, cx| { cx.new(|_| ToastDemo { toasts: toasts.clone(), }) .into() } }), cx, ); dock.register_panel( PanelDescriptor::new("dialog", "Dialog", { move |_, cx| { cx.new(|_| DialogDemo { gallery: gallery.clone(), }) .into() } }), cx, ); dock.register_panel( PanelDescriptor::new("menu", "Menu", |_, cx| { cx.new(|_| MenuDemo { open: false }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("context-menu", "Context menu", |_, cx| { cx.new(|_| ContextMenuDemo { action: SharedString::from("Right-click the card"), }) .into() }), cx, ); dock.register_panel( PanelDescriptor::new("popover", "Popover", |_, cx| { cx.new(|_| PopoverDemo { open: false }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("tooltip", "Tooltip", |_, cx| cx.new(|_| TooltipDemo).into()), cx, ); dock.register_panel( PanelDescriptor::new("empty-state", "Empty state", |_, cx| { cx.new(|_| EmptyStateDemo).into() }), cx, ); dock.register_panel( PanelDescriptor::new("scroll-area", "Scroll area", |_, cx| { cx.new(|_| ScrollAreaDemo).into() }), cx, ); dock.register_panel( PanelDescriptor::new("sidebar", "Sidebar", |_, cx| { cx.new(|_| SidebarDemo { selected: None }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("resize-handle", "Resize handle", |_, cx| { cx.new(|_| ResizeDemo { width: px(200.) }).into() }), cx, ); dock.register_panel( PanelDescriptor::new("theme", "Theme", |_, cx| cx.new(|_| ThemeDemo).into()), cx, ); } } impl Render for Gallery { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = cx.theme(); let gallery = cx.entity().downgrade(); // Dialogs are window-modal, so they mount at the window root; the // Dialog demo panel only flips these flags. let dialog = Self::render_dialog(self.dialog_open, gallery.clone()); let alert_dialog = Self::render_alert_dialog(self.alert_open, gallery); div() .size_full() .font_family(".SystemUIFont") .bg(theme.colors.background) .text_color(theme.colors.foreground) .child(self.render_shell(cx)) .child(dialog) .child(alert_dialog) .child(AlertsView::new("gallery-alerts", self.alerts.clone())) .child(ToastsView::new("gallery-toasts", self.toasts.clone())) } } /// The dock introduction: how to drive the center dock, plus the shell's /// sidebar toggles. struct WelcomePanel { layout_state: Entity, dock_state: Entity, } impl Render for WelcomePanel { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors; let layout_left = self.layout_state.clone(); let layout_right = self.layout_state.clone(); let dock_state = self.dock_state.clone(); ScrollArea::new("welcome-demo") .size_full() .p_4() .child( v_flex() .gap_4() .max_w(px(560.)) .child( div() .text_lg() .font_weight(FontWeight::SEMIBOLD) .child("gpui-native-kit gallery"), ) .child( v_flex() .gap_1() .text_sm() .text_color(colors.muted_foreground) .child(div().child("Pick a component in the sidebar — it opens here as a tab.")) .child(div().child( "Drag a tab to another pane's edge to split, or onto a tab bar to move it.", )) .child(div().child( "Right-click the tab bar to close a tab, close others, or close the pane.", )) .child(div().child("Seams between panes resize; double-click a seam to reset.")), ) .child(demo_section( "layout state", colors, h_flex() .flex_wrap() .gap_2() .child( Button::new("welcome-toggle-left") .label("Toggle left sidebar") .variant(ButtonVariant::Secondary) .small() .on_click(move |_, _, cx| { layout_left.update(cx, |state, cx| { let visible = !state.is_left_sidebar_visible(); state.set_left_sidebar_visible(visible, cx); }); }), ) .child( Button::new("welcome-toggle-right") .label("Toggle right sidebar") .variant(ButtonVariant::Secondary) .small() .on_click(move |_, _, cx| { layout_right.update(cx, |state, cx| { let visible = !state.is_right_sidebar_visible(); state.set_right_sidebar_visible(visible, cx); }); }), ) .child( Button::new("welcome-split") .label("Split active right") .variant(ButtonVariant::Secondary) .small() .on_click(move |_, _, cx| { dock_state.update(cx, |dock, cx| { if let Some(active) = dock.active_panel() { dock.split_panel(&active, SplitDirection::Right, cx); } }); }), ), )), ) } } /// The right sidebar view a panel declares. struct RightDetails { title: &'static str, body: &'static str, } impl Render for RightDetails { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors; v_flex() .size_full() .gap_1() .p_2() .bg(colors.background) .child(div().font_weight(FontWeight::MEDIUM).child(self.title)) .child( div() .text_sm() .text_color(colors.muted_foreground) .child(self.body), ) } } struct ButtonDemo { clicks: usize, } impl Render for ButtonDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; let variants: [(&str, ButtonVariant); 4] = [ ("primary", ButtonVariant::Primary), ("secondary", ButtonVariant::Secondary), ("ghost", ButtonVariant::Ghost), ("destructive", ButtonVariant::Destructive), ]; ScrollArea::new("button-demo").size_full().p_4().child( v_flex() .gap_4() .child(demo_section( "variants", colors, h_flex() .flex_wrap() .gap_2() .children(variants.map(|(name, variant)| { let view = view.clone(); let mut button = Button::new(SharedString::from(format!("button-demo-{name}"))) .label(button_label(variant)) .variant(variant) .on_click(move |_, _, cx| { view.update(cx, |demo, cx| { demo.clicks += 1; cx.notify(); }); }); if variant == ButtonVariant::Destructive { button = button.tooltip(Tooltip::text("Removes the selected item")); } button })) .child( Button::new("button-demo-disabled") .label("Disabled") .disabled(true), ) .child( Button::new("button-demo-icon") .label("Open") .icon(IconRole::Folder) .tooltip(Tooltip::text("Opens the selected file")), ) .child( Button::new("button-demo-icon-only") .icon(IconRole::Folder) .variant(ButtonVariant::Ghost) .accessibility_label("Open the selected file") .tooltip(Tooltip::text("Opens the selected file")), ), )) .child(demo_section( "sizes", colors, h_flex() .items_center() .gap_2() .child(Button::new("button-demo-small").label("Small").small()) .child(Button::new("button-demo-medium").label("Medium")) .child(Button::new("button-demo-large").label("Large").large()), )) .child( div() .text_xs() .text_color(colors.muted_foreground) .child(format!("clicked {} times", self.clicks)), ), ) } } struct InputDemo { text: Entity, password: Entity, notes: Entity, } impl InputDemo { fn new(cx: &mut Context) -> Self { let text = InputState::new(cx); let password = InputState::new(cx); password.update(cx, |state, cx| state.set_password(true, cx)); let notes = InputState::new(cx); notes.update(cx, |state, cx| state.set_multiline(true, cx)); Self { text, password, notes, } } } impl Render for InputDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors; ScrollArea::new("input-demo").size_full().p_4().child( v_flex() .gap_3() .child(Input::new("input-demo-text", self.text.clone()).placeholder("Type here…")) .child( Input::new("input-demo-password", self.password.clone()) .placeholder("Password"), ) .child( Input::new("input-demo-notes", self.notes.clone()) .placeholder("Notes — Enter breaks the line, cmd-Enter submits") .rows(4), ) .child( div() .text_xs() .text_color(colors.muted_foreground) .child(format!("value: {}", self.text.read(cx).value())), ), ) } } struct CheckboxDemo { checked: bool, } impl Render for CheckboxDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("checkbox-demo") .size_full() .p_4() .child(demo_section( "states", colors, v_flex() .gap_2() .child( Checkbox::new("checkbox-demo-telemetry") .label("Enable telemetry") .checked(self.checked) .on_change(move |checked, _, cx| { view.update(cx, |demo, cx| { demo.checked = checked; cx.notify(); }); }), ) .child( Checkbox::new("checkbox-demo-disabled-off") .label("Disabled unchecked") .disabled(true), ) .child( Checkbox::new("checkbox-demo-disabled-on") .label("Disabled checked") .checked(true) .disabled(true), ), )) } } struct RadioDemo { plan: SharedString, } impl Render for RadioDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("radio-demo") .size_full() .p_4() .child(demo_section( "plans", colors, RadioGroup::new("radio-demo-group") .selected(Some(self.plan.clone())) .on_change(move |plan, _, cx| { view.update(cx, |demo, cx| { demo.plan = plan; cx.notify(); }); }) .child( Radio::new("radio-demo-starter", SharedString::from("starter")) .label("Starter"), ) .child(Radio::new("radio-demo-pro", SharedString::from("pro")).label("Pro")) .child( Radio::new("radio-demo-team", SharedString::from("team")) .label("Team") .disabled(true), ), )) } } struct SwitchDemo { checked: bool, } impl Render for SwitchDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("switch-demo") .size_full() .p_4() .child(demo_section( "states", colors, v_flex() .gap_2() .child( Switch::new("switch-demo-wifi") .label("Wi-Fi") .checked(self.checked) .on_change(move |checked, _, cx| { view.update(cx, |demo, cx| { demo.checked = checked; cx.notify(); }); }), ) .child( Switch::new("switch-demo-disabled-off") .label("Disabled off") .disabled(true), ) .child( Switch::new("switch-demo-disabled-on") .label("Disabled on") .checked(true) .disabled(true), ), )) } } struct ToggleDemo { pressed: bool, } impl Render for ToggleDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("toggle-demo") .size_full() .p_4() .child(demo_section( "states", colors, h_flex() .gap_2() .child( Toggle::new("toggle-demo-bold") .label("Bold") .pressed(self.pressed) .on_change(move |pressed, _, cx| { view.update(cx, |demo, cx| { demo.pressed = pressed; cx.notify(); }); }), ) .child( Toggle::new("toggle-demo-disabled") .label("Disabled") .pressed(true) .disabled(true), ), )) } } struct SliderDemo { volume: f32, } impl Render for SliderDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("slider-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "volume", colors, Slider::new("slider-demo-volume", self.volume) .min(0.0) .max(100.0) .step(5.0) .accessibility_label("Volume") .on_change(move |volume, _, cx| { view.update(cx, |demo, cx| { demo.volume = volume; cx.notify(); }); }), )) .child( div() .text_xs() .text_color(colors.muted_foreground) .child(format!("volume: {}", self.volume)), ), ) } } struct SegmentedDemo { filter: ExploreFilter, } impl Render for SegmentedDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let view_icons = view.clone(); let view_large = view.clone(); let colors = cx.theme().colors; ScrollArea::new("segmented-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "sizes", colors, v_flex() .gap_2() .child( Segmented::new("segmented-demo-small", self.filter) .options(vec![ (ExploreFilter::All, "All"), (ExploreFilter::Popular, "Popular"), (ExploreFilter::Recent, "Recent"), ]) .on_change(move |next, _, cx| { view.update(cx, |demo, cx| { demo.filter = next; cx.notify(); }); }), ) .child( Segmented::new("segmented-demo-medium", self.filter) .options(vec![ SegmentedOption::new(ExploreFilter::All, "All") .icon(IconRole::Check), SegmentedOption::new(ExploreFilter::Popular, "Popular") .icon(IconRole::Search), SegmentedOption::new(ExploreFilter::Recent, "Recent") .icon(IconRole::Info), ]) .with_size(Size::Medium) .on_change(move |next, _, cx| { view_icons.update(cx, |demo, cx| { demo.filter = next; cx.notify(); }); }), ) .child( Segmented::new("segmented-demo-large", self.filter) .options(vec![ (ExploreFilter::All, "All"), (ExploreFilter::Popular, "Popular"), (ExploreFilter::Recent, "Recent"), ]) .large() .on_change(move |next, _, cx| { view_large.update(cx, |demo, cx| { demo.filter = next; cx.notify(); }); }), ), )) .child({ let label = match self.filter { ExploreFilter::All => "showing everything", ExploreFilter::Popular => "showing popular", ExploreFilter::Recent => "showing recent", }; div() .text_xs() .text_color(colors.muted_foreground) .child(label) }), ) } } struct SelectDemo { strategy: Option, } impl Render for SelectDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("select-demo") .size_full() .p_4() .child(demo_section( "options", colors, v_flex() .gap_3() .child( Select::new("select-demo-strategy") .options(vec![ SelectOption::new(Strategy::Curated, "Curated") .icon(IconRole::Check), SelectOption::new(Strategy::Uncensored, "Uncensored") .icon(IconRole::Warning), ]) .selected(self.strategy) .placeholder("Fetching strategy") .on_change(move |next, _, cx| { view.update(cx, |demo, cx| { demo.strategy = next; cx.notify(); }); }), ) .child( Select::new("select-demo-size") .options(vec![ ("small", "Small"), ("medium", "Medium"), ("large", "Large"), ]) .selected("medium") .placeholder("Control size") .small(), ), )) } } struct LinkDemo { clicks: usize, } impl Render for LinkDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("link-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "hrefs", colors, h_flex() .gap_4() .child( Link::new("link-demo-github") .label("gpui on GitHub") .href("https://github.com/zed-industries/zed"), ) .child( Link::new("link-demo-override") .label("On-click override") .href("https://example.com/never-opened") .on_click(move |_, _, cx| { view.update(cx, |demo, cx| { demo.clicks += 1; cx.notify(); }); }), ) .child( Link::new("link-demo-disabled") .label("Disabled") .href("https://example.com/disabled") .disabled(true), ), )) .child( div() .text_xs() .text_color(colors.muted_foreground) .child(format!("override clicked {} times", self.clicks)), ), ) } } struct BadgeDemo; impl Render for BadgeDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors; ScrollArea::new("badge-demo") .size_full() .p_4() .child(demo_section( "variants", colors, h_flex() .flex_wrap() .gap_3() .child(Badge::new("3")) .child(Badge::new("new").variant(BadgeVariant::Accent)) .child(Badge::new("synced").variant(BadgeVariant::Success)) .child(Badge::new("drift").variant(BadgeVariant::Warning)) .child(Badge::new("12").variant(BadgeVariant::Danger)) .child(Badge::new("small").small()) .child(Badge::new("large").large()), )) } } struct AvatarDemo; impl Render for AvatarDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors; ScrollArea::new("avatar-demo") .size_full() .p_4() .child(demo_section( "pictures fall back to initials", colors, h_flex() .flex_wrap() .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()), )) } } struct ProgressDemo; impl Render for ProgressDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors; ScrollArea::new("progress-demo") .size_full() .p_4() .child(demo_section( "determinite and indeterminate", colors, v_flex() .gap_2() .child(Progress::new("progress-demo-25", 0.25).accessibility_label("Quarter")) .child(Progress::new("progress-demo-60", 0.6).accessibility_label("Most")) .child(Progress::new("progress-demo-90", 0.9).accessibility_label("Nearly")) .child( Progress::new("progress-demo-indeterminate", 0.0) .indeterminate(true) .accessibility_label("Working"), ), )) } } struct ListDemo { selected: Option, } impl Render for ListDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("list-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "rows", colors, List::new("list-demo-list") .selected(self.selected) .on_change(move |selected, _, cx| { view.update(cx, |demo, cx| { demo.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.selected)), ), ) } } /// A demo model for the tree: the view owns the real tree and its expansion /// set, and flattens the visible nodes for the component. #[derive(Clone)] struct GalleryNode { label: SharedString, icon: Option, expanded: bool, children: Vec, } struct TreeDemo { nodes: Vec, selected: Option, } impl TreeDemo { fn demo() -> Vec { vec![ GalleryNode { label: "Workspace".into(), icon: Some(IconRole::Folder), expanded: true, children: vec![GalleryNode { label: "gpui-native-kit".into(), icon: Some(IconRole::Folder), expanded: true, children: vec![ GalleryNode { label: "crates".into(), icon: Some(IconRole::Folder), expanded: true, children: vec![ GalleryNode { label: "theme".into(), icon: None, expanded: false, children: Vec::new(), }, GalleryNode { label: "core".into(), icon: None, expanded: false, children: Vec::new(), }, GalleryNode { label: "data".into(), icon: None, expanded: false, children: Vec::new(), }, ], }, GalleryNode { label: "docs".into(), icon: Some(IconRole::Folder), expanded: false, children: Vec::new(), }, GalleryNode { label: "Cargo.toml".into(), icon: None, expanded: false, children: Vec::new(), }, ], }], }, GalleryNode { label: "Archive.zip".into(), icon: None, expanded: false, children: Vec::new(), }, ] } fn flatten( nodes: &[GalleryNode], depth: usize, prefix: &[usize], items: &mut Vec, paths: &mut Vec>, ) { for (index, node) in nodes.iter().enumerate() { let mut path = prefix.to_vec(); path.push(index); let mut item = TreeItem::new(node.label.clone()) .depth(depth) .expandable(!node.children.is_empty()) .expanded(node.expanded); if let Some(role) = node.icon { item = item.icon(role); } items.push(item); paths.push(path.clone()); if node.expanded { Self::flatten(&node.children, depth + 1, &path, items, paths); } } } fn toggle(nodes: &mut [GalleryNode], path: &[usize]) { let Some(index) = path.first() else { return }; let Some(node) = nodes.get_mut(*index) else { return; }; if path.len() == 1 { node.expanded = !node.expanded; } else { Self::toggle(&mut node.children, &path[1..]); } } /// The visible-row paths of the current tree state, matching the flat /// index the tree component reports. fn paths(nodes: &[GalleryNode]) -> Vec> { let mut items = Vec::new(); let mut paths = Vec::new(); Self::flatten(nodes, 0, &[], &mut items, &mut paths); paths } } impl Render for TreeDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let view_disclosure = view.clone(); let colors = cx.theme().colors; let mut items = Vec::new(); let mut paths = Vec::new(); Self::flatten(&self.nodes, 0, &[], &mut items, &mut paths); ScrollArea::new("tree-demo") .size_full() .p_4() .child(demo_section( "outline", colors, Tree::new("tree-demo-tree") .selected(self.selected) .on_change(move |selected, _, cx| { view.update(cx, |demo, cx| { demo.selected = selected; cx.notify(); }); }) .on_disclosure(move |index, _, cx| { view_disclosure.update(cx, |demo, cx| { let paths = TreeDemo::paths(&demo.nodes); if let Some(path) = paths.get(index) { TreeDemo::toggle(&mut demo.nodes, path); } cx.notify(); }); }) .items(move |_, _| items.clone()), )) } } struct TabsDemo { selected: usize, pill_tabs: Vec, pill_selected: usize, } impl Render for TabsDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let view_pill = view.clone(); let view_close = view.clone(); let colors = cx.theme().colors; let pill_count = self.pill_tabs.len(); ScrollArea::new("tabs-demo") .size_full() .p_4() .child( v_flex().gap_3() .child(demo_section( "underline", colors, Tabs::new("tabs-demo-underline") .selected(self.selected) .on_change(move |selected, _, cx| { view.update(cx, |demo, cx| { demo.selected = selected; cx.notify(); }); }) .tabs(|_, _| { vec![ Tab::new("Overview"), Tab::new("Details"), Tab::new("Settings"), Tab::new("Locked").disabled(true), ] }), )) .child(match self.selected { 0 => div() .child( "The overview panel is the app's business — the strip only reports.", ) .into_any_element(), 1 => div() .child("Details: rows, lists and trees all live under their own tabs.") .into_any_element(), _ => div() .child("Settings: keyboard focus rides the strip; arrows switch tabs.") .into_any_element(), }) .child(demo_section( "pill", colors, Tabs::new("tabs-demo-pill") .variant(TabsVariant::Pill) .selected(self.pill_selected) .on_change(move |selected, _, cx| { view_pill.update(cx, |demo, cx| { demo.pill_selected = selected; cx.notify(); }); }) .on_close(move |index, _, cx| { view_close.update(cx, |demo, cx| { if index < demo.pill_tabs.len() { demo.pill_tabs.remove(index); demo.pill_selected = demo .pill_selected .min(demo.pill_tabs.len().saturating_sub(1)); cx.notify(); } }); }) .tabs({ let labels = self.pill_tabs.clone(); move |_, _| { labels .iter() .map(|label| { Tab::new(label.clone()) .closeable(true) .busy(label == "Syncing") }) .collect() } }), )) .child( div() .text_xs() .text_color(colors.muted_foreground) .child(format!( "{pill_count} tabs left; the ring on \"Syncing\" is the Spinner placeholder" )), ), ) } } struct AlertDemo { alerts: Entity, dismissed: bool, } impl Render for AlertDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let alerts_info = self.alerts.clone(); let alerts_warning = self.alerts.clone(); let alerts_clear = self.alerts.clone(); let colors = cx.theme().colors; ScrollArea::new("alert-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "inline callouts", colors, v_flex() .gap_2() .child( Alert::new("alert-demo-update") .title("Update available") .message("Version 2.4.0 is ready to install.") .severity(AlertSeverity::Info), ) .when(!self.dismissed, |section| { section.child( Alert::new("alert-demo-sync") .title("Sync failed") .message("Will retry when you're back online.") .severity(AlertSeverity::Danger) .on_dismiss(move |cx| { view.update(cx, |demo, cx| { demo.dismissed = true; cx.notify(); }); }), ) }), )) .child(demo_section( "pushed to the window stack", colors, h_flex() .flex_wrap() .gap_2() .child( Button::new("alert-demo-push-info") .label("Push info") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { alerts_info.update(cx, |alerts, cx| { alerts.push( Alert::new("alert-demo-pushed-info") .title("Backed up") .message("Everything is in sync.") .severity(AlertSeverity::Info), cx, ); }); }), ) .child( Button::new("alert-demo-push-warning") .label("Push warning") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { alerts_warning.update(cx, |alerts, cx| { alerts.push( Alert::new("alert-demo-pushed-warning") .title("Storage almost full") .message("1.2 GB remaining on this device.") .severity(AlertSeverity::Warning), cx, ); }); }), ) .child( Button::new("alert-demo-clear") .label("Clear all") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { alerts_clear.update(cx, |alerts, cx| alerts.clear(cx)); }), ), )), ) } } struct ToastDemo { toasts: Entity, } impl Render for ToastDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let toasts_undo = self.toasts.clone(); let toasts_saved = self.toasts.clone(); let toasts_connection = self.toasts.clone(); let colors = cx.theme().colors; ScrollArea::new("toast-demo") .size_full() .p_4() .child(demo_section( "temporary, one at a time", colors, h_flex() .flex_wrap() .gap_2() .child( Button::new("toast-demo-undo") .label("Deleted (with undo)") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { toasts_undo.update(cx, |toasts, cx| { toasts.push( Toast::new("Draft deleted").action(ToastAction::new( "Undo", |cx| { // A real app restores the draft here. let _ = cx; }, )), cx, ); }); }), ) .child( Button::new("toast-demo-saved") .label("Saved (with icon)") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { toasts_saved.update(cx, |toasts, cx| { toasts.push( Toast::new("Workspace saved").icon(IconRole::Check), cx, ); }); }), ) .child( Button::new("toast-demo-connection") .label("Connection lost (sticky)") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { toasts_connection.update(cx, |toasts, cx| { toasts.push( Toast::new("Connection lost") .icon(IconRole::Warning) .duration(Duration::ZERO), cx, ); }); }), ), )) } } struct DialogDemo { gallery: WeakEntity, } impl Render for DialogDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let gallery_dialog = self.gallery.clone(); let gallery_alert = self.gallery.clone(); let colors = cx.theme().colors; ScrollArea::new("dialog-demo") .size_full() .p_4() .child(demo_section( "window-modal, mounted at the window root", colors, h_flex() .gap_2() .child( Button::new("dialog-demo-open") .label("Delete file…") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { gallery_dialog .update(cx, |gallery, cx| { gallery.dialog_open = true; cx.notify(); }) .ok(); }), ) .child( Button::new("dialog-demo-alert") .label("Sign out…") .variant(ButtonVariant::Secondary) .on_click(move |_, _, cx| { gallery_alert .update(cx, |gallery, cx| { gallery.alert_open = true; cx.notify(); }) .ok(); }), ), )) } } struct MenuDemo { open: bool, } impl Render for MenuDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("menu-demo") .size_full() .p_4() .child(demo_section( "trigger and items", colors, Menu::new("menu-demo-menu") .open(self.open) .on_change(move |open, _, cx| { view.update(cx, |demo, cx| { demo.open = open; cx.notify(); }); }) .trigger(|_, _| { Button::new("menu-demo-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), ] }), )) } } struct ContextMenuDemo { action: SharedString, } impl Render for ContextMenuDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("context-menu-demo") .size_full() .p_4() .child( v_flex() .gap_3() .child(demo_section( "on any target", colors, ContextMenu::new("context-menu-demo-menu") .target(move |_, cx| { let theme = cx.theme(); div() .px_3() .py_2() .rounded(theme.radius.overlay) .border_1() .border_color(theme.colors.border) .bg(theme.colors.surface) .text_size(theme.sizes.medium.text_size) .child("Right-click this card") .into_any_element() }) .items(move |_, _| { CONTEXT_ACTIONS .iter() .map(|action| { let view = view.clone(); MenuItem::new(*action).handler(move |_, cx| { view.update(cx, |demo, cx| { demo.action = SharedString::from(*action); cx.notify(); }); }) }) .collect() }) .accessibility_label("Card actions"), )) .child( div() .text_sm() .text_color(colors.muted_foreground) .child(self.action.clone()), ), ) } } struct PopoverDemo { open: bool, } impl Render for PopoverDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let colors = cx.theme().colors; ScrollArea::new("popover-demo") .size_full() .p_4() .child(demo_section( "anchored to its trigger", colors, Popover::new("popover-demo-popover") .open(self.open) .on_change(move |open, _, cx| { view.update(cx, |demo, cx| { demo.open = open; cx.notify(); }); }) .trigger(|_, _| { Button::new("popover-demo-trigger") .label("Options") .variant(ButtonVariant::Secondary) .into_any_element() }) .content(|_, _| { v_flex() .gap_1() .w(px(240.)) .p_2() .child( Checkbox::new("popover-demo-telemetry").label("Enable telemetry"), ) .child(Checkbox::new("popover-demo-crash").label("Send crash reports")) .into_any_element() }), )) } } struct TooltipDemo; impl Render for TooltipDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let colors = cx.theme().colors; ScrollArea::new("tooltip-demo") .size_full() .p_4() .child(demo_section( "hover the buttons", colors, h_flex() .gap_2() .child( Button::new("tooltip-demo-destructive") .label("Delete") .variant(ButtonVariant::Destructive) .tooltip(Tooltip::text("Removes the selected item")), ) .child( Button::new("tooltip-demo-icon") .label("Open") .icon(IconRole::Folder) .tooltip(Tooltip::text("Opens the selected file")), ) .child( Button::new("tooltip-demo-icon-only") .icon(IconRole::Folder) .variant(ButtonVariant::Ghost) .accessibility_label("Open the selected file") .tooltip(Tooltip::text("Opens the selected file")), ), )) } } struct EmptyStateDemo; impl Render for EmptyStateDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = cx.theme(); let colors = theme.colors; let radius = theme.radius; ScrollArea::new("empty-state-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "with action", colors, div() .h(px(180.)) .rounded(radius.overlay) .border_1() .border_color(colors.border) .flex() .child( EmptyState::new("No repositories found") .icon(IconRole::Search) .hint("Try a different name, or publish one first.") .action( Button::new("empty-state-demo-clear") .label("Clear filters") .variant(ButtonVariant::Secondary), ), ), )) .child(demo_section( "bare", colors, div() .h(px(120.)) .rounded(radius.overlay) .border_1() .border_color(colors.border) .flex() .child(EmptyState::new("Nothing here yet")), )), ) } } struct ScrollAreaDemo; impl Render for ScrollAreaDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = cx.theme(); let colors = theme.colors; let radius = theme.radius; let rows = (0..24).map(|row| { div() .px_3() .py_1() .border_b_1() .border_color(colors.border) .child(format!("Row {row} — scroll down to reach it")) }); let wide_rows = (0..12).map(|row| { div() .flex_none() .w(px(760.)) .px_3() .py_1() .border_b_1() .border_color(colors.border) .child(format!("Wide row {row} — scroll right")) }); ScrollArea::new("scroll-area-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "vertical", colors, div() .h(px(200.)) .rounded(radius.control) .border_1() .border_color(colors.border) .overflow_hidden() .child( ScrollArea::new("scroll-area-demo-vertical") .size_full() .children(rows), ), )) .child(demo_section( "both axes", colors, div() .h(px(160.)) .rounded(radius.control) .border_1() .border_color(colors.border) .overflow_hidden() .child( ScrollArea::new("scroll-area-demo-both") .size_full() .axes(ScrollAxes::Both) .child(v_flex().children(wide_rows)), ), )), ) } } struct SidebarDemo { selected: Option, } impl Render for SidebarDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let theme = cx.theme(); let colors = theme.colors; let radius = theme.radius; ScrollArea::new("sidebar-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "standalone; the app's own left sidebar is this component", colors, div() .w(px(280.)) .h(px(360.)) .rounded(radius.control) .border_1() .border_color(colors.border) .overflow_hidden() .child( Sidebar::new("sidebar-demo-list") .selected(self.selected) .on_change(move |selected, _, cx| { view.update(cx, |demo, cx| { demo.selected = selected; cx.notify(); }); }) .entries(|_, _| { vec![ SidebarEntry::header("Favorites"), SidebarEntry::row("Documents").icon(IconRole::Folder), SidebarEntry::row("Pictures") .icon(IconRole::Folder) .detail("24"), SidebarEntry::row("Archive").disabled(true), SidebarEntry::header("Locations"), SidebarEntry::row("iCloud").icon(IconRole::Folder), SidebarEntry::row("Trash"), ] }), ), )) .child( div() .text_xs() .text_color(colors.muted_foreground) .child(format!("selected: {:?}", self.selected)), ), ) } } struct ResizeDemo { width: Pixels, } impl Render for ResizeDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let view = cx.entity(); let view_reset = view.clone(); let theme = cx.theme(); let colors = theme.colors; let radius = theme.radius; let width = self.width; ScrollArea::new("resize-demo").size_full().p_4().child( v_flex() .gap_3() .child(demo_section( "seam", colors, h_flex() .h(px(112.)) .rounded(radius.control) .border_1() .border_color(colors.border) .overflow_hidden() .child( div() .w(width) .h_full() .flex() .items_center() .justify_center() .bg(colors.muted) .child("Sidebar"), ) .child( ResizeHandle::new("resize-demo-handle") .on_resize(move |delta, _, cx| { view.update(cx, |demo, cx| { demo.width = (demo.width + delta).clamp(px(96.), px(320.)); cx.notify(); }); }) .on_reset(move |_, cx| { view_reset.update(cx, |demo, cx| { demo.width = px(200.); cx.notify(); }); }), ) .child( div() .flex_1() .h_full() .flex() .items_center() .justify_center() .bg(colors.input) .child("Editor"), ), )) .child( div() .text_xs() .text_color(colors.muted_foreground) .child(format!( "width: {} px — drag the seam, double-click resets", f32::from(width) )), ), ) } } struct ThemeDemo; impl Render for ThemeDemo { fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { let theme = cx.theme(); let colors = theme.colors; let radius = theme.radius; let mode = ModeChoice::of(mode_override(cx)); let preset = PresetChoice::of(active_preset(cx)); 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), ]; ScrollArea::new("theme-demo").size_full().p_4().child( v_flex() .gap_4() .child(demo_section( "mode", colors, Segmented::new("theme-demo-mode", mode) .options(vec![ (ModeChoice::System, "System"), (ModeChoice::Light, "Light"), (ModeChoice::Dark, "Dark"), ]) .on_change(|next, _, cx| set_mode_override(next.variant(), cx)), )) .child(demo_section( "preset", colors, Segmented::new("theme-demo-preset", preset) .options(vec![ (PresetChoice::FollowSystem, "Follow system"), (PresetChoice::TokyoNight, "Tokyo Night"), ]) .on_change(|next, _, cx| { set_preset(next.name(), cx).ok(); }), )) .child(demo_section( "accent shades (derived, OKLCH)", colors, div() .flex() .flex_wrap() .gap_2() .children(shades.map(|(name, color)| { h_flex() .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(demo_section( "color tokens", colors, div() .flex() .flex_wrap() .gap_2() .children(swatches.map(|(name, color)| { h_flex() .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(demo_section( "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)), ), )), ) } } 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(FontWeight::MEDIUM) .text_color(colors.muted_foreground) .child(text.to_string()) } fn demo_section( label: &str, colors: gpui_native_theme::ColorTokens, body: B, ) -> impl IntoElement + use where B: IntoElement, { v_flex() .gap_2() .child(section_label(label, colors)) .child(body) } fn main() { application().with_assets(IconAssets).run(|cx: &mut App| { gpui_native_core::init(cx); 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| { state.set_right_sidebar_visible(true, cx); }); }); }); let bounds = Bounds::centered(None, size(px(960.0), px(640.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()), // 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, ..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()); 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(), alerts: alerts.clone(), toasts: toasts.clone(), dialog_open: false, alert_open: false, } }); dock_state.update(cx, |dock, cx| { Gallery::register_panels( dock, layout_state.clone(), alerts.clone(), toasts.clone(), gallery.downgrade(), cx, ); dock.open_panel("welcome", window, cx); }); gallery }, ) { eprintln!("failed to open gallery window: {err}"); } cx.activate(true); }); }