feat add command palette component
This commit is contained in:
@@ -2,9 +2,9 @@ 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,
|
||||
App, AppContext as _, Bounds, Context, Entity, FontWeight, InteractiveElement as _,
|
||||
IntoElement, KeyBinding, ParentElement as _, Pixels, Render, SharedString, Styled as _,
|
||||
WeakEntity, Window, WindowBounds, WindowOptions, actions, div, px, size,
|
||||
};
|
||||
use gpui_native_core::alert::{Alert, AlertSeverity, Alerts, AlertsView};
|
||||
use gpui_native_core::avatar::Avatar;
|
||||
@@ -12,6 +12,7 @@ use gpui_native_core::badge::{Badge, BadgeVariant};
|
||||
use gpui_native_core::button::{Button, ButtonVariant};
|
||||
use gpui_native_core::checkbox::Checkbox;
|
||||
use gpui_native_core::combobox::{Combobox, ComboboxOption, ComboboxState};
|
||||
use gpui_native_core::command_palette::{Command, CommandPalette, CommandPaletteState};
|
||||
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;
|
||||
@@ -85,6 +86,7 @@ const CATALOG: &[(&str, &[(&str, &str)])] = &[
|
||||
("alert", "Alert"),
|
||||
("toast", "Toast"),
|
||||
("dialog", "Dialog"),
|
||||
("command-palette", "Command palette"),
|
||||
("menu", "Menu"),
|
||||
("context-menu", "Context menu"),
|
||||
("popover", "Popover"),
|
||||
@@ -174,11 +176,26 @@ impl PresetChoice {
|
||||
/// The context menu demo's actions.
|
||||
const CONTEXT_ACTIONS: [&str; 3] = ["Rename", "Duplicate", "Archive"];
|
||||
|
||||
actions!(gallery, [OpenCommandPalette]);
|
||||
|
||||
/// The command palette demo's actions.
|
||||
fn gallery_commands() -> Vec<Command> {
|
||||
vec![
|
||||
Command::new("open-settings", "Open Settings").hint("Cmd+,"),
|
||||
Command::new("toggle-theme", "Toggle Theme").hint("Shift+Cmd+T"),
|
||||
Command::new("save-all", "Save All").hint("Cmd+Alt+S"),
|
||||
Command::new("close-tab", "Close Tab").hint("Cmd+W"),
|
||||
Command::new("reload-window", "Reload Window"),
|
||||
]
|
||||
}
|
||||
|
||||
struct Gallery {
|
||||
layout_state: Entity<LayoutState>,
|
||||
dock_state: Entity<DockState>,
|
||||
alerts: Entity<Alerts>,
|
||||
toasts: Entity<Toasts>,
|
||||
palette: Option<Entity<CommandPaletteState>>,
|
||||
palette_runs: Vec<SharedString>,
|
||||
dialog_open: bool,
|
||||
alert_open: bool,
|
||||
}
|
||||
@@ -352,6 +369,7 @@ impl Gallery {
|
||||
layout_state: Entity<LayoutState>,
|
||||
alerts: Entity<Alerts>,
|
||||
toasts: Entity<Toasts>,
|
||||
palette: Entity<CommandPaletteState>,
|
||||
gallery: WeakEntity<Self>,
|
||||
cx: &mut Context<DockState>,
|
||||
) {
|
||||
@@ -539,6 +557,7 @@ impl Gallery {
|
||||
);
|
||||
dock.register_panel(
|
||||
PanelDescriptor::new("dialog", "Dialog", {
|
||||
let gallery = gallery.clone();
|
||||
move |_, cx| {
|
||||
cx.new(|_| DialogDemo {
|
||||
gallery: gallery.clone(),
|
||||
@@ -548,6 +567,16 @@ impl Gallery {
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
dock.register_panel(
|
||||
PanelDescriptor::new("command-palette", "Command palette", {
|
||||
let palette = palette.clone();
|
||||
move |_, cx| {
|
||||
cx.new(|cx| CommandPaletteDemo::new(gallery.clone(), palette.clone(), cx))
|
||||
.into()
|
||||
}
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
dock.register_panel(
|
||||
PanelDescriptor::new("menu", "Menu", |_, cx| {
|
||||
cx.new(|_| MenuDemo { open: false }).into()
|
||||
@@ -608,6 +637,7 @@ impl Render for Gallery {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let theme = cx.theme();
|
||||
let gallery = cx.entity().downgrade();
|
||||
let palette_for_action = self.palette.clone();
|
||||
|
||||
// Dialogs are window-modal, so they mount at the window root; the
|
||||
// Dialog demo panel only flips these flags.
|
||||
@@ -617,11 +647,17 @@ impl Render for Gallery {
|
||||
div()
|
||||
.size_full()
|
||||
.font_family(".SystemUIFont")
|
||||
.on_action(move |_: &OpenCommandPalette, window, cx| {
|
||||
if let Some(palette) = &palette_for_action {
|
||||
palette.update(cx, |palette, cx| palette.toggle(window, cx));
|
||||
}
|
||||
})
|
||||
.bg(theme.colors.background)
|
||||
.text_color(theme.colors.foreground)
|
||||
.child(self.render_shell(cx))
|
||||
.child(dialog)
|
||||
.child(alert_dialog)
|
||||
.when_some(self.palette.clone(), |root, palette| root.child(palette))
|
||||
.child(AlertsView::new("gallery-alerts", self.alerts.clone()))
|
||||
.child(ToastsView::new("gallery-toasts", self.toasts.clone()))
|
||||
}
|
||||
@@ -2194,6 +2230,92 @@ impl Render for DialogDemo {
|
||||
}
|
||||
}
|
||||
|
||||
struct CommandPaletteDemo {
|
||||
gallery: WeakEntity<Gallery>,
|
||||
palette: Entity<CommandPaletteState>,
|
||||
_subscription: gpui::Subscription,
|
||||
}
|
||||
|
||||
impl CommandPaletteDemo {
|
||||
fn new(
|
||||
gallery: WeakEntity<Gallery>,
|
||||
palette: Entity<CommandPaletteState>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Self {
|
||||
// The panel is a cached view in the dock: the readouts re-render only
|
||||
// when the palette state they read is notified.
|
||||
let subscription = cx.observe(&palette, |_, _, cx| cx.notify());
|
||||
Self {
|
||||
gallery,
|
||||
palette,
|
||||
_subscription: subscription,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for CommandPaletteDemo {
|
||||
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let colors = cx.theme().colors;
|
||||
let open = self.palette.read(cx).is_open();
|
||||
let runs = self
|
||||
.gallery
|
||||
.upgrade()
|
||||
.map(|gallery| gallery.read(cx).palette_runs.clone())
|
||||
.unwrap_or_default();
|
||||
let runs = if runs.is_empty() {
|
||||
"none yet".to_string()
|
||||
} else {
|
||||
runs.iter()
|
||||
.map(|id| id.to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
};
|
||||
let palette_toggle = self.palette.clone();
|
||||
|
||||
ScrollArea::new("command-palette-demo")
|
||||
.size_full()
|
||||
.p_4()
|
||||
.child(demo_section(
|
||||
"window-modal, mounted at the window root",
|
||||
colors,
|
||||
v_flex()
|
||||
.gap_2()
|
||||
.child(
|
||||
Button::new("command-palette-demo-open")
|
||||
.label("Open command palette")
|
||||
.variant(ButtonVariant::Secondary)
|
||||
.on_click(move |_, window, cx| {
|
||||
palette_toggle.update(cx, |palette, cx| palette.toggle(window, cx));
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.muted_foreground)
|
||||
.child("Cmd+K (Ctrl+K elsewhere) opens it anywhere in the window."),
|
||||
),
|
||||
))
|
||||
.child(demo_section(
|
||||
"runs",
|
||||
colors,
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.muted_foreground)
|
||||
.child(if open { "open" } else { "closed" }),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.text_xs()
|
||||
.text_color(colors.muted_foreground)
|
||||
.child(format!("executed: {runs}")),
|
||||
),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
struct MenuDemo {
|
||||
open: bool,
|
||||
}
|
||||
@@ -2791,6 +2913,10 @@ fn main() {
|
||||
.with_http_client(std::sync::Arc::new(reqwest_client::ReqwestClient::new()))
|
||||
.run(|cx: &mut App| {
|
||||
gpui_native_core::init(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
|
||||
@@ -2842,16 +2968,47 @@ fn main() {
|
||||
dock_state: dock_state.clone(),
|
||||
alerts: alerts.clone(),
|
||||
toasts: toasts.clone(),
|
||||
palette: None,
|
||||
palette_runs: Vec::new(),
|
||||
dialog_open: false,
|
||||
alert_open: false,
|
||||
}
|
||||
});
|
||||
let palette = cx.new(|cx| {
|
||||
let matcher = |query: &str, commands: &[Command]| {
|
||||
let needle = query.to_lowercase();
|
||||
commands
|
||||
.iter()
|
||||
.filter(|command| command.label.to_lowercase().contains(&needle))
|
||||
.cloned()
|
||||
.collect()
|
||||
};
|
||||
let gallery_for_execute = gallery.downgrade();
|
||||
CommandPaletteState::new(
|
||||
CommandPalette::new("gallery-palette", gallery_commands(), matcher)
|
||||
.on_execute(move |command: &Command, _, cx| {
|
||||
gallery_for_execute
|
||||
.update(cx, |gallery, cx| {
|
||||
gallery.palette_runs.push(command.id.clone());
|
||||
cx.notify();
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
gallery.update(cx, |gallery, cx| {
|
||||
gallery.palette = Some(palette.clone());
|
||||
cx.notify();
|
||||
});
|
||||
dock_state.update(cx, |dock, cx| {
|
||||
Gallery::register_panels(
|
||||
dock,
|
||||
layout_state.clone(),
|
||||
alerts.clone(),
|
||||
toasts.clone(),
|
||||
palette.clone(),
|
||||
gallery.downgrade(),
|
||||
cx,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user