gpui-component/crates/story/src/popup_story.rs
Floyd Wang abb3de013e
Rename package from ui to gpui-component (#606)
## 🚨Break Change

Update dependencies from `ui` to `gpui-component`.
2025-02-06 17:21:46 +08:00

384 lines
16 KiB
Rust

use gpui::{
actions, div, impl_internal_actions, px, App, AppContext, Context, Corner, DismissEvent,
Element, Entity, EventEmitter, FocusHandle, Focusable, InteractiveElement, IntoElement,
KeyBinding, MouseButton, ParentElement as _, Render, SharedString, Styled as _, Window,
};
use gpui_component::{
button::{Button, ButtonVariants as _},
context_menu::ContextMenuExt,
divider::Divider,
h_flex,
input::TextInput,
popover::{Popover, PopoverContent},
popup_menu::PopupMenuExt,
switch::Switch,
v_flex, ActiveTheme as _, ContextModal, IconName, Sizable,
};
use serde::Deserialize;
#[derive(Clone, PartialEq, Deserialize)]
struct Info(usize);
actions!(
popover_story,
[Copy, Paste, Cut, SearchAll, ToggleWindowMode]
);
impl_internal_actions!(popover_story, [Info]);
pub fn init(cx: &mut App) {
cx.bind_keys([
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-c", Copy, None),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-c", Copy, None),
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-v", Paste, None),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-v", Paste, None),
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-x", Cut, None),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-x", Cut, None),
#[cfg(target_os = "macos")]
KeyBinding::new("cmd-shift-f", SearchAll, None),
#[cfg(not(target_os = "macos"))]
KeyBinding::new("ctrl-shift-f", SearchAll, None),
])
}
struct Form {
input1: Entity<TextInput>,
}
impl Form {
fn new(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self {
input1: cx.new(|cx| TextInput::new(window, cx)),
})
}
}
impl Focusable for Form {
fn focus_handle(&self, cx: &App) -> FocusHandle {
self.input1.focus_handle(cx)
}
}
impl EventEmitter<DismissEvent> for Form {}
impl Render for Form {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_4()
.p_4()
.size_full()
.child("This is a form container.")
.child(self.input1.clone())
.child(
Button::new("submit")
.label("Submit")
.primary()
.on_click(cx.listener(|_, _, _, cx| cx.emit(DismissEvent))),
)
}
}
pub struct PopupStory {
focus_handle: FocusHandle,
form: Entity<Form>,
message: String,
window_mode: bool,
}
impl super::Story for PopupStory {
fn title() -> &'static str {
"Popup"
}
fn description() -> &'static str {
"A popup displays content on top of the main page."
}
fn new_view(window: &mut Window, cx: &mut App) -> Entity<impl Render + Focusable> {
Self::view(window, cx)
}
}
impl PopupStory {
pub fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self::new(window, cx))
}
fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let form = Form::new(window, cx);
Self {
form,
focus_handle: cx.focus_handle(),
message: "".to_string(),
window_mode: false,
}
}
fn on_copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
self.message = "You have clicked copy".to_string();
cx.notify()
}
fn on_cut(&mut self, _: &Cut, _: &mut Window, cx: &mut Context<Self>) {
self.message = "You have clicked cut".to_string();
cx.notify()
}
fn on_paste(&mut self, _: &Paste, _: &mut Window, cx: &mut Context<Self>) {
self.message = "You have clicked paste".to_string();
cx.notify()
}
fn on_search_all(&mut self, _: &SearchAll, _: &mut Window, cx: &mut Context<Self>) {
self.message = "You have clicked search all".to_string();
cx.notify()
}
fn on_toggle_window_mode(
&mut self,
_: &ToggleWindowMode,
_: &mut Window,
cx: &mut Context<Self>,
) {
self.window_mode = !self.window_mode;
cx.notify()
}
fn on_action_info(&mut self, info: &Info, _: &mut Window, cx: &mut Context<Self>) {
self.message = format!("You have clicked info: {}", info.0);
cx.notify()
}
}
impl Focusable for PopupStory {
fn focus_handle(&self, _cx: &App) -> FocusHandle {
self.focus_handle.clone()
}
}
impl Render for PopupStory {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let form = self.form.clone();
let window_mode = self.window_mode;
v_flex()
.track_focus(&self.focus_handle)
.on_action(cx.listener(Self::on_copy))
.on_action(cx.listener(Self::on_cut))
.on_action(cx.listener(Self::on_paste))
.on_action(cx.listener(Self::on_search_all))
.on_action(cx.listener(Self::on_toggle_window_mode))
.on_action(cx.listener(Self::on_action_info))
.p_4()
.mb_5()
.size_full()
.min_h(px(400.))
.context_menu({
move |this, window, cx| {
this.separator()
.menu("Cut", Box::new(Cut))
.menu("Copy", Box::new(Copy))
.menu("Paste", Box::new(Paste))
.separator()
.separator()
.submenu("Settings", window, cx, move |menu, _, _| {
menu.menu_with_check(
"Toggle Window Mode",
window_mode,
Box::new(ToggleWindowMode),
)
.separator()
.menu("Info 0", Box::new(Info(0)))
.menu("Item 1", Box::new(Info(1)))
.menu("Item 2", Box::new(Info(2)))
})
.separator()
.menu("Search All", Box::new(SearchAll))
.separator()
}
})
.gap_6()
.child(
Switch::new("switch-window-mode")
.checked(window_mode)
.label("Use Window Popover")
.on_click(cx.listener(|this, checked, _, _| {
this.window_mode = *checked;
})),
)
.child(
h_flex()
.items_center()
.justify_between()
.child(
v_flex().gap_4().child(
Popover::new("info-top-left")
.trigger(Button::new("info-top-left").label("Top Left"))
.content(|window, cx| {
cx.new(|cx| {
PopoverContent::new(window, cx, |_, _| {
v_flex()
.gap_4()
.child("Hello, this is a Popover.")
.w(px(400.))
.child(Divider::horizontal())
.child(
Button::new("info1")
.label("Yes")
.w(px(80.))
.small(),
)
.into_any()
})
.max_w(px(600.))
})
}),
),
)
.child(
Popover::new("info-top-right")
.anchor(Corner::TopRight)
.trigger(Button::new("info-top-right").label("Top Right"))
.content(|window, cx| {
cx.new(|cx| {
PopoverContent::new(window, cx, |_, _| {
v_flex()
.gap_4()
.w_96()
.child("Hello, this is a Popover on the Top Right.")
.child(Divider::horizontal())
.child(
Button::new("info1")
.label("Yes")
.w(px(80.))
.small(),
)
.into_any()
})
})
}),
),
)
.child(
h_flex()
.gap_3()
.child(
Button::new("popup-menu-1")
.icon(IconName::Ellipsis)
.popup_menu(move |this, window, cx| {
this.menu("Copy", Box::new(Copy))
.menu("Cut", Box::new(Cut))
.menu("Paste", Box::new(Paste))
.separator()
.menu_with_icon("Search", IconName::Search, Box::new(SearchAll))
.separator()
.menu_with_check(
"Window Mode",
window_mode,
Box::new(ToggleWindowMode),
)
.separator()
.menu_with_element(
|_, cx| {
v_flex().gap_1().child("Custom Element").child(
div()
.text_sm()
.text_color(cx.theme().muted_foreground)
.child("THis is sub-title"),
)
},
Box::new(Info(0)),
)
.separator()
.submenu("Links", window, cx, |menu, _, _| {
menu.link_with_icon(
"GitHub Repository",
IconName::GitHub,
"https://github.com/longbridge/gpui-component",
)
.separator()
.link("GPUI", "https://gpui.rs")
.link("Zed", "https://zed.dev")
})
}),
)
.child(
Button::new("popup-menu-11112")
.label("Scrollable Menu")
.popup_menu_with_anchor(Corner::TopRight, move |this, _, _| {
let mut this = this.scrollable().max_h(px(300.));
for i in 0..100 {
this = this.menu(
SharedString::from(format!("Item {}", i)),
Box::new(Info(i)),
)
}
this
}),
)
.child(self.message.clone()),
)
.child("Right click to open ContextMenu")
.child(
div().absolute().bottom_4().left_0().w_full().h_10().child(
h_flex()
.items_center()
.justify_between()
.child(
Popover::new("info-bottom-left")
.anchor(Corner::BottomLeft)
.trigger(Button::new("pop").label("Popup with Form").w(px(300.)))
.content(move |_, _| form.clone()),
)
.child(
Popover::new("info-bottom-right")
.anchor(Corner::BottomRight)
.mouse_button(MouseButton::Right)
.trigger(Button::new("pop").label("Mouse Right Click").w(px(300.)))
.content(|window, cx| {
cx.new(|cx| {
PopoverContent::new(window, cx, |_, cx| {
v_flex()
.gap_2()
.child(
"Hello, this is a Popover on the Bottom Right.",
)
.child(Divider::horizontal())
.child(
h_flex()
.gap_2()
.child(
Button::new("info1")
.label("Ok")
.w(px(80.))
.small()
.on_click(cx.listener(
|_, _, window, cx| {
window.push_notification(
"You have clicked Ok.",
cx,
);
cx.emit(DismissEvent);
},
)),
)
.child(
Button::new("close")
.label("Cancel")
.small()
.on_click(cx.listener(
|_, _, _, cx| {
cx.emit(DismissEvent);
},
)),
),
)
.into_any()
})
})
}),
),
),
)
}
}