story: Add TitleBar to examples. (#586)
This commit is contained in:
parent
71de5dd882
commit
649e765729
6 changed files with 400 additions and 333 deletions
|
|
@ -61,6 +61,8 @@ gpui = { git = "https://github.com/huacnlee/zed.git", branch = "export-platform-
|
||||||
ui = { git = "https://github.com/longbridge/gpui-component.git" }
|
ui = { git = "https://github.com/longbridge/gpui-component.git" }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
More usage can be found in [story](https://github.com/longbridge/gpui-component/tree/main/crates/story) directory.
|
||||||
|
|
||||||
## Demo
|
## Demo
|
||||||
|
|
||||||
If you want to see the demo, here is a some demo applications.
|
If you want to see the demo, here is a some demo applications.
|
||||||
|
|
|
||||||
|
|
@ -353,7 +353,6 @@ impl Render for InputStory {
|
||||||
.on_action(cx.listener(Self::tab))
|
.on_action(cx.listener(Self::tab))
|
||||||
.on_action(cx.listener(Self::tab_prev))
|
.on_action(cx.listener(Self::tab_prev))
|
||||||
.size_full()
|
.size_full()
|
||||||
.p_4()
|
|
||||||
.justify_start()
|
.justify_start()
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(
|
.child(
|
||||||
|
|
@ -429,14 +428,14 @@ impl Render for InputStory {
|
||||||
.gap_3()
|
.gap_3()
|
||||||
.child(
|
.child(
|
||||||
Button::new("btn-submit")
|
Button::new("btn-submit")
|
||||||
.w_full()
|
.flex_1()
|
||||||
.with_variant(ButtonVariant::Primary)
|
.with_variant(ButtonVariant::Primary)
|
||||||
.label("Submit")
|
.label("Submit")
|
||||||
.on_click(cx.listener(|_, _, cx| cx.dispatch_action(Box::new(Tab)))),
|
.on_click(cx.listener(|_, _, cx| cx.dispatch_action(Box::new(Tab)))),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
Button::new("btn-cancel")
|
Button::new("btn-cancel")
|
||||||
.w_full()
|
.flex_1()
|
||||||
.label("Cancel")
|
.label("Cancel")
|
||||||
.into_element(),
|
.into_element(),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ mod sidebar_story;
|
||||||
mod switch_story;
|
mod switch_story;
|
||||||
mod table_story;
|
mod table_story;
|
||||||
mod text_story;
|
mod text_story;
|
||||||
|
mod title_bar;
|
||||||
mod tooltip_story;
|
mod tooltip_story;
|
||||||
mod webview_story;
|
mod webview_story;
|
||||||
|
|
||||||
|
|
@ -29,10 +30,11 @@ pub use dropdown_story::DropdownStory;
|
||||||
pub use form_story::FormStory;
|
pub use form_story::FormStory;
|
||||||
|
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, div, prelude::FluentBuilder as _, px, size, AnyElement, AnyView, AppContext, Bounds,
|
actions, div, impl_internal_actions, prelude::FluentBuilder as _, px, size, AnyElement,
|
||||||
Context as _, Div, EventEmitter, FocusableView, Global, Hsla, InteractiveElement, IntoElement,
|
AnyView, AppContext, Bounds, Context as _, Div, EventEmitter, FocusableView, Global, Hsla,
|
||||||
Model, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled as _, View,
|
InteractiveElement, IntoElement, Model, ParentElement, Render, SharedString,
|
||||||
ViewContext, VisualContext, WindowBounds, WindowContext, WindowKind, WindowOptions,
|
StatefulInteractiveElement, Styled as _, View, ViewContext, VisualContext, WindowBounds,
|
||||||
|
WindowContext, WindowKind, WindowOptions,
|
||||||
};
|
};
|
||||||
pub use icon_story::IconStory;
|
pub use icon_story::IconStory;
|
||||||
pub use image_story::ImageStory;
|
pub use image_story::ImageStory;
|
||||||
|
|
@ -48,6 +50,7 @@ pub use sidebar_story::SidebarStory;
|
||||||
pub use switch_story::SwitchStory;
|
pub use switch_story::SwitchStory;
|
||||||
pub use table_story::TableStory;
|
pub use table_story::TableStory;
|
||||||
pub use text_story::TextStory;
|
pub use text_story::TextStory;
|
||||||
|
pub use title_bar::AppTitleBar;
|
||||||
pub use tooltip_story::TooltipStory;
|
pub use tooltip_story::TooltipStory;
|
||||||
pub use webview_story::WebViewStory;
|
pub use webview_story::WebViewStory;
|
||||||
|
|
||||||
|
|
@ -59,9 +62,23 @@ use ui::{
|
||||||
label::Label,
|
label::Label,
|
||||||
notification::Notification,
|
notification::Notification,
|
||||||
popup_menu::PopupMenu,
|
popup_menu::PopupMenu,
|
||||||
v_flex, ActiveTheme, ContextModal, IconName, Root,
|
scroll::ScrollbarShow,
|
||||||
|
v_flex, ActiveTheme, ContextModal, IconName, Root, TitleBar,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
pub struct SelectScrollbarShow(ScrollbarShow);
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
pub struct SelectLocale(SharedString);
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
pub struct SelectFont(usize);
|
||||||
|
|
||||||
|
impl_internal_actions!(story, [SelectLocale, SelectFont, SelectScrollbarShow,]);
|
||||||
|
|
||||||
|
actions!(story, [Quit, Open, CloseWindow]);
|
||||||
|
|
||||||
const PANEL_NAME: &str = "StoryContainer";
|
const PANEL_NAME: &str = "StoryContainer";
|
||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
|
|
@ -101,11 +118,7 @@ where
|
||||||
cx.spawn(|mut cx| async move {
|
cx.spawn(|mut cx| async move {
|
||||||
let options = WindowOptions {
|
let options = WindowOptions {
|
||||||
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
||||||
// titlebar: Some(TitlebarOptions {
|
titlebar: Some(TitleBar::title_bar_options()),
|
||||||
// title: Some(title.clone()),
|
|
||||||
// appears_transparent: true,
|
|
||||||
// traffic_light_position: Some(point(px(9.0), px(9.0))),
|
|
||||||
// }),
|
|
||||||
window_min_size: Some(gpui::Size {
|
window_min_size: Some(gpui::Size {
|
||||||
width: px(640.),
|
width: px(640.),
|
||||||
height: px(480.),
|
height: px(480.),
|
||||||
|
|
@ -121,7 +134,8 @@ where
|
||||||
let window = cx
|
let window = cx
|
||||||
.open_window(options, |cx| {
|
.open_window(options, |cx| {
|
||||||
let view = crate_view_fn(cx);
|
let view = crate_view_fn(cx);
|
||||||
cx.new_view(|cx| Root::new(view.into(), cx))
|
let root = cx.new_view(|cx| StoryRoot::new(title.clone(), view, cx));
|
||||||
|
cx.new_view(|cx| Root::new(root.into(), cx))
|
||||||
})
|
})
|
||||||
.expect("failed to open window");
|
.expect("failed to open window");
|
||||||
|
|
||||||
|
|
@ -137,6 +151,34 @@ where
|
||||||
.detach();
|
.detach();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct StoryRoot {
|
||||||
|
title_bar: View<AppTitleBar>,
|
||||||
|
view: AnyView,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StoryRoot {
|
||||||
|
pub fn new(
|
||||||
|
title: impl Into<SharedString>,
|
||||||
|
view: impl Into<AnyView>,
|
||||||
|
cx: &mut ViewContext<Self>,
|
||||||
|
) -> Self {
|
||||||
|
let title_bar = cx.new_view(|cx| AppTitleBar::new(title, cx));
|
||||||
|
Self {
|
||||||
|
title_bar,
|
||||||
|
view: view.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for StoryRoot {
|
||||||
|
fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
v_flex()
|
||||||
|
.size_full()
|
||||||
|
.child(self.title_bar.clone())
|
||||||
|
.child(self.view.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Global for AppState {}
|
impl Global for AppState {}
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
|
|
|
||||||
|
|
@ -1,58 +1,33 @@
|
||||||
use anyhow::{Context, Result};
|
use anyhow::{Context, Result};
|
||||||
use gpui::*;
|
use gpui::*;
|
||||||
use prelude::FluentBuilder as _;
|
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::{sync::Arc, time::Duration};
|
use std::{sync::Arc, time::Duration};
|
||||||
use story::{
|
use story::{
|
||||||
AccordionStory, AppState, Assets, ButtonStory, CalendarStory, DropdownStory, FormStory,
|
AccordionStory, AppState, AppTitleBar, Assets, ButtonStory, CalendarStory, DropdownStory,
|
||||||
IconStory, ImageStory, InputStory, ListStory, ModalStory, PopupStory, ProgressStory,
|
FormStory, IconStory, ImageStory, InputStory, ListStory, ModalStory, Open, PopupStory,
|
||||||
ResizableStory, ScrollableStory, SidebarStory, StoryContainer, SwitchStory, TableStory,
|
ProgressStory, Quit, ResizableStory, ScrollableStory, SidebarStory, StoryContainer,
|
||||||
TextStory, TooltipStory,
|
SwitchStory, TableStory, TextStory, TooltipStory,
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
badge::Badge,
|
|
||||||
button::{Button, ButtonVariants as _},
|
button::{Button, ButtonVariants as _},
|
||||||
color_picker::{ColorPicker, ColorPickerEvent},
|
|
||||||
dock::{DockArea, DockAreaState, DockEvent, DockItem, DockPlacement},
|
dock::{DockArea, DockAreaState, DockEvent, DockItem, DockPlacement},
|
||||||
popup_menu::PopupMenuExt,
|
popup_menu::PopupMenuExt,
|
||||||
scroll::ScrollbarShow,
|
IconName, Root, Sizable, Theme, TitleBar,
|
||||||
ActiveTheme, ContextModal, IconName, Root, Sizable, Theme, TitleBar,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
pub struct AddPanel(DockPlacement);
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||||
|
pub struct TogglePanelVisible(SharedString);
|
||||||
|
|
||||||
|
impl_internal_actions!(story, [AddPanel, TogglePanelVisible]);
|
||||||
|
|
||||||
const MAIN_DOCK_AREA: DockAreaTab = DockAreaTab {
|
const MAIN_DOCK_AREA: DockAreaTab = DockAreaTab {
|
||||||
id: "main-dock",
|
id: "main-dock",
|
||||||
version: 5,
|
version: 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
|
||||||
struct SelectScrollbarShow(ScrollbarShow);
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
|
||||||
struct SelectLocale(SharedString);
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
|
||||||
struct SelectFont(usize);
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
|
||||||
struct AddPanel(DockPlacement);
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
|
||||||
struct TogglePanelVisible(SharedString);
|
|
||||||
|
|
||||||
impl_internal_actions!(
|
|
||||||
story,
|
|
||||||
[
|
|
||||||
SelectLocale,
|
|
||||||
SelectFont,
|
|
||||||
AddPanel,
|
|
||||||
SelectScrollbarShow,
|
|
||||||
TogglePanelVisible
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
actions!(main_menu, [Quit]);
|
|
||||||
actions!(workspace, [Open, CloseWindow]);
|
|
||||||
|
|
||||||
pub fn init(cx: &mut AppContext) {
|
pub fn init(cx: &mut AppContext) {
|
||||||
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
|
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
|
||||||
|
|
||||||
|
|
@ -63,11 +38,8 @@ pub fn init(cx: &mut AppContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct StoryWorkspace {
|
pub struct StoryWorkspace {
|
||||||
theme_color: Option<Hsla>,
|
title_bar: View<AppTitleBar>,
|
||||||
dock_area: View<DockArea>,
|
dock_area: View<DockArea>,
|
||||||
locale_selector: View<LocaleSelector>,
|
|
||||||
font_size_selector: View<FontSizeSelector>,
|
|
||||||
theme_color_picker: View<ColorPicker>,
|
|
||||||
last_layout_state: Option<DockAreaState>,
|
last_layout_state: Option<DockAreaState>,
|
||||||
_save_layout_task: Option<Task<()>>,
|
_save_layout_task: Option<Task<()>>,
|
||||||
}
|
}
|
||||||
|
|
@ -118,57 +90,75 @@ impl StoryWorkspace {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
let locale_selector = cx.new_view(LocaleSelector::new);
|
let title_bar = cx.new_view(|cx| {
|
||||||
let font_size_selector = cx.new_view(FontSizeSelector::new);
|
AppTitleBar::new("Examples", cx).child({
|
||||||
|
move |cx| {
|
||||||
|
Button::new("add-panel")
|
||||||
|
.icon(IconName::LayoutDashboard)
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.popup_menu({
|
||||||
|
let invisible_panels = AppState::global(cx).invisible_panels.clone();
|
||||||
|
|
||||||
let theme_color_picker = cx.new_view(|cx| {
|
move |menu, cx| {
|
||||||
let mut picker = ColorPicker::new("theme-color-picker", cx)
|
menu.menu(
|
||||||
.xsmall()
|
"Add Panel to Center",
|
||||||
.anchor(Corner::TopRight)
|
Box::new(AddPanel(DockPlacement::Center)),
|
||||||
.label("Theme Color");
|
)
|
||||||
picker.set_value(cx.theme().primary, cx);
|
.separator()
|
||||||
picker
|
.menu("Add Panel to Left", Box::new(AddPanel(DockPlacement::Left)))
|
||||||
});
|
.menu(
|
||||||
cx.subscribe(
|
"Add Panel to Right",
|
||||||
&theme_color_picker,
|
Box::new(AddPanel(DockPlacement::Right)),
|
||||||
|this, _, ev: &ColorPickerEvent, cx| match ev {
|
)
|
||||||
ColorPickerEvent::Change(color) => {
|
.menu(
|
||||||
this.set_theme_color(*color, cx);
|
"Add Panel to Bottom",
|
||||||
|
Box::new(AddPanel(DockPlacement::Bottom)),
|
||||||
|
)
|
||||||
|
.separator()
|
||||||
|
.menu_with_check(
|
||||||
|
"Sidebar",
|
||||||
|
!invisible_panels
|
||||||
|
.read(cx)
|
||||||
|
.contains(&SharedString::from("Sidebar")),
|
||||||
|
Box::new(TogglePanelVisible(SharedString::from("Sidebar"))),
|
||||||
|
)
|
||||||
|
.menu_with_check(
|
||||||
|
"Modal",
|
||||||
|
!invisible_panels
|
||||||
|
.read(cx)
|
||||||
|
.contains(&SharedString::from("SidebModalar")),
|
||||||
|
Box::new(TogglePanelVisible(SharedString::from("Modal"))),
|
||||||
|
)
|
||||||
|
.menu_with_check(
|
||||||
|
"Accordion",
|
||||||
|
!invisible_panels
|
||||||
|
.read(cx)
|
||||||
|
.contains(&SharedString::from("Accordion")),
|
||||||
|
Box::new(TogglePanelVisible(SharedString::from("Accordion"))),
|
||||||
|
)
|
||||||
|
.menu_with_check(
|
||||||
|
"List",
|
||||||
|
!invisible_panels
|
||||||
|
.read(cx)
|
||||||
|
.contains(&SharedString::from("List")),
|
||||||
|
Box::new(TogglePanelVisible(SharedString::from("List"))),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.anchor(Corner::TopRight)
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
)
|
});
|
||||||
.detach();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
theme_color: None,
|
|
||||||
dock_area,
|
dock_area,
|
||||||
locale_selector,
|
title_bar,
|
||||||
font_size_selector,
|
|
||||||
theme_color_picker,
|
|
||||||
last_layout_state: None,
|
last_layout_state: None,
|
||||||
_save_layout_task: None,
|
_save_layout_task: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_theme_color(&mut self, color: Option<Hsla>, cx: &mut ViewContext<Self>) {
|
|
||||||
self.theme_color = color;
|
|
||||||
if let Some(color) = self.theme_color {
|
|
||||||
let theme = cx.global_mut::<Theme>();
|
|
||||||
theme.apply_color(color);
|
|
||||||
cx.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change_color_mode(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
|
||||||
let mode = match cx.theme().mode.is_dark() {
|
|
||||||
true => ui::ThemeMode::Light,
|
|
||||||
false => ui::ThemeMode::Dark,
|
|
||||||
};
|
|
||||||
|
|
||||||
Theme::change(mode, cx);
|
|
||||||
self.set_theme_color(self.theme_color, cx);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_layout(&mut self, dock_area: View<DockArea>, cx: &mut ViewContext<Self>) {
|
fn save_layout(&mut self, dock_area: View<DockArea>, cx: &mut ViewContext<Self>) {
|
||||||
self._save_layout_task = Some(cx.spawn(|this, mut cx| async move {
|
self._save_layout_task = Some(cx.spawn(|this, mut cx| async move {
|
||||||
Timer::after(Duration::from_secs(10)).await;
|
Timer::after(Duration::from_secs(10)).await;
|
||||||
|
|
@ -345,11 +335,7 @@ impl StoryWorkspace {
|
||||||
let options = WindowOptions {
|
let options = WindowOptions {
|
||||||
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
titlebar: Some(TitlebarOptions {
|
titlebar: Some(TitleBar::title_bar_options()),
|
||||||
title: None,
|
|
||||||
appears_transparent: true,
|
|
||||||
traffic_light_position: Some(point(px(9.0), px(9.0))),
|
|
||||||
}),
|
|
||||||
window_min_size: Some(gpui::Size {
|
window_min_size: Some(gpui::Size {
|
||||||
width: px(640.),
|
width: px(640.),
|
||||||
height: px(480.),
|
height: px(480.),
|
||||||
|
|
@ -450,8 +436,6 @@ impl Render for StoryWorkspace {
|
||||||
let drawer_layer = Root::render_drawer_layer(cx);
|
let drawer_layer = Root::render_drawer_layer(cx);
|
||||||
let modal_layer = Root::render_modal_layer(cx);
|
let modal_layer = Root::render_modal_layer(cx);
|
||||||
let notification_layer = Root::render_notification_layer(cx);
|
let notification_layer = Root::render_notification_layer(cx);
|
||||||
let notifications_count = cx.notifications().len();
|
|
||||||
let invisible_panels = AppState::global(cx).invisible_panels.clone();
|
|
||||||
|
|
||||||
div()
|
div()
|
||||||
.id("story-workspace")
|
.id("story-workspace")
|
||||||
|
|
@ -461,123 +445,7 @@ impl Render for StoryWorkspace {
|
||||||
.size_full()
|
.size_full()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_col()
|
.flex_col()
|
||||||
.child(
|
.child(self.title_bar.clone())
|
||||||
TitleBar::new()
|
|
||||||
// left side
|
|
||||||
.child(div().flex().items_center().child("GPUI App"))
|
|
||||||
.child(
|
|
||||||
div()
|
|
||||||
.flex()
|
|
||||||
.items_center()
|
|
||||||
.justify_end()
|
|
||||||
.px_2()
|
|
||||||
.gap_2()
|
|
||||||
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
|
||||||
.child(self.theme_color_picker.clone())
|
|
||||||
.child(
|
|
||||||
Button::new("add-panel")
|
|
||||||
.icon(IconName::LayoutDashboard)
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.popup_menu(move |menu, cx| {
|
|
||||||
menu.menu(
|
|
||||||
"Add Panel to Center",
|
|
||||||
Box::new(AddPanel(DockPlacement::Center)),
|
|
||||||
)
|
|
||||||
.separator()
|
|
||||||
.menu(
|
|
||||||
"Add Panel to Left",
|
|
||||||
Box::new(AddPanel(DockPlacement::Left)),
|
|
||||||
)
|
|
||||||
.menu(
|
|
||||||
"Add Panel to Right",
|
|
||||||
Box::new(AddPanel(DockPlacement::Right)),
|
|
||||||
)
|
|
||||||
.menu(
|
|
||||||
"Add Panel to Bottom",
|
|
||||||
Box::new(AddPanel(DockPlacement::Bottom)),
|
|
||||||
)
|
|
||||||
.separator()
|
|
||||||
.menu_with_check(
|
|
||||||
"Sidebar",
|
|
||||||
!invisible_panels
|
|
||||||
.read(cx)
|
|
||||||
.contains(&SharedString::from("Sidebar")),
|
|
||||||
Box::new(TogglePanelVisible(SharedString::from(
|
|
||||||
"Sidebar",
|
|
||||||
))),
|
|
||||||
)
|
|
||||||
.menu_with_check(
|
|
||||||
"Modal",
|
|
||||||
!invisible_panels
|
|
||||||
.read(cx)
|
|
||||||
.contains(&SharedString::from("SidebModalar")),
|
|
||||||
Box::new(TogglePanelVisible(SharedString::from(
|
|
||||||
"Modal",
|
|
||||||
))),
|
|
||||||
)
|
|
||||||
.menu_with_check(
|
|
||||||
"Accordion",
|
|
||||||
!invisible_panels
|
|
||||||
.read(cx)
|
|
||||||
.contains(&SharedString::from("Accordion")),
|
|
||||||
Box::new(TogglePanelVisible(SharedString::from(
|
|
||||||
"Accordion",
|
|
||||||
))),
|
|
||||||
)
|
|
||||||
.menu_with_check(
|
|
||||||
"List",
|
|
||||||
!invisible_panels
|
|
||||||
.read(cx)
|
|
||||||
.contains(&SharedString::from("List")),
|
|
||||||
Box::new(TogglePanelVisible(SharedString::from(
|
|
||||||
"List",
|
|
||||||
))),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.anchor(Corner::TopRight),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
Button::new("theme-mode")
|
|
||||||
.map(|this| {
|
|
||||||
if cx.theme().mode.is_dark() {
|
|
||||||
this.icon(IconName::Sun)
|
|
||||||
} else {
|
|
||||||
this.icon(IconName::Moon)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.on_click(cx.listener(Self::change_color_mode)),
|
|
||||||
)
|
|
||||||
.child(self.locale_selector.clone())
|
|
||||||
.child(self.font_size_selector.clone())
|
|
||||||
.child(
|
|
||||||
Badge::new().dot().count(1).child(
|
|
||||||
Button::new("github")
|
|
||||||
.icon(IconName::GitHub)
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.on_click(|_, cx| {
|
|
||||||
cx.open_url(
|
|
||||||
"https://github.com/longbridge/gpui-component",
|
|
||||||
)
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
|
||||||
div().relative().child(
|
|
||||||
Badge::new().count(notifications_count).max(99).child(
|
|
||||||
Button::new("bell")
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.compact()
|
|
||||||
.icon(IconName::Bell),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(self.dock_area.clone())
|
.child(self.dock_area.clone())
|
||||||
.children(drawer_layer)
|
.children(drawer_layer)
|
||||||
.children(modal_layer)
|
.children(modal_layer)
|
||||||
|
|
@ -585,117 +453,6 @@ impl Render for StoryWorkspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct LocaleSelector {
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LocaleSelector {
|
|
||||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_select_locale(&mut self, locale: &SelectLocale, cx: &mut ViewContext<Self>) {
|
|
||||||
ui::set_locale(&locale.0);
|
|
||||||
cx.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for LocaleSelector {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
let focus_handle = self.focus_handle.clone();
|
|
||||||
let locale = ui::locale().to_string();
|
|
||||||
|
|
||||||
div()
|
|
||||||
.id("locale-selector")
|
|
||||||
.track_focus(&focus_handle)
|
|
||||||
.on_action(cx.listener(Self::on_select_locale))
|
|
||||||
.child(
|
|
||||||
Button::new("btn")
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.icon(IconName::Globe)
|
|
||||||
.popup_menu(move |this, _| {
|
|
||||||
this.menu_with_check(
|
|
||||||
"English",
|
|
||||||
locale == "en",
|
|
||||||
Box::new(SelectLocale("en".into())),
|
|
||||||
)
|
|
||||||
.menu_with_check(
|
|
||||||
"简体中文",
|
|
||||||
locale == "zh-CN",
|
|
||||||
Box::new(SelectLocale("zh-CN".into())),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.anchor(Corner::TopRight),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct FontSizeSelector {
|
|
||||||
focus_handle: FocusHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FontSizeSelector {
|
|
||||||
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
focus_handle: cx.focus_handle(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_select_font(&mut self, font_size: &SelectFont, cx: &mut ViewContext<Self>) {
|
|
||||||
Theme::global_mut(cx).font_size = font_size.0 as f32;
|
|
||||||
cx.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn on_select_scrollbar_show(&mut self, show: &SelectScrollbarShow, cx: &mut ViewContext<Self>) {
|
|
||||||
Theme::global_mut(cx).scrollbar_show = show.0;
|
|
||||||
cx.refresh();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Render for FontSizeSelector {
|
|
||||||
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
|
||||||
let focus_handle = self.focus_handle.clone();
|
|
||||||
let font_size = cx.theme().font_size as i32;
|
|
||||||
let scroll_show = cx.theme().scrollbar_show;
|
|
||||||
|
|
||||||
div()
|
|
||||||
.id("font-size-selector")
|
|
||||||
.track_focus(&focus_handle)
|
|
||||||
.on_action(cx.listener(Self::on_select_font))
|
|
||||||
.on_action(cx.listener(Self::on_select_scrollbar_show))
|
|
||||||
.child(
|
|
||||||
Button::new("btn")
|
|
||||||
.small()
|
|
||||||
.ghost()
|
|
||||||
.icon(IconName::Settings2)
|
|
||||||
.popup_menu(move |this, _| {
|
|
||||||
this.menu_with_check(
|
|
||||||
"Font Large",
|
|
||||||
font_size == 18,
|
|
||||||
Box::new(SelectFont(18)),
|
|
||||||
)
|
|
||||||
.menu_with_check("Font Default", font_size == 16, Box::new(SelectFont(16)))
|
|
||||||
.menu_with_check("Font Small", font_size == 14, Box::new(SelectFont(14)))
|
|
||||||
.separator()
|
|
||||||
.menu_with_check(
|
|
||||||
"Scrolling to show Scrollbar",
|
|
||||||
scroll_show == ScrollbarShow::Scrolling,
|
|
||||||
Box::new(SelectScrollbarShow(ScrollbarShow::Scrolling)),
|
|
||||||
)
|
|
||||||
.menu_with_check(
|
|
||||||
"Hover to show Scrollbar",
|
|
||||||
scroll_show == ScrollbarShow::Hover,
|
|
||||||
Box::new(SelectScrollbarShow(ScrollbarShow::Hover)),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.anchor(Corner::TopRight),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
use ui::input::{Copy, Cut, Paste, Redo, Undo};
|
use ui::input::{Copy, Cut, Paste, Redo, Undo};
|
||||||
|
|
||||||
|
|
|
||||||
258
crates/story/src/title_bar.rs
Normal file
258
crates/story/src/title_bar.rs
Normal file
|
|
@ -0,0 +1,258 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use gpui::{
|
||||||
|
div, AnyElement, ClickEvent, Corner, FocusHandle, Hsla, InteractiveElement as _, IntoElement,
|
||||||
|
MouseButton, ParentElement as _, Render, SharedString, Styled as _, View, ViewContext,
|
||||||
|
VisualContext as _, WindowContext,
|
||||||
|
};
|
||||||
|
use ui::{
|
||||||
|
badge::Badge,
|
||||||
|
button::{Button, ButtonVariants as _},
|
||||||
|
color_picker::{ColorPicker, ColorPickerEvent},
|
||||||
|
popup_menu::PopupMenuExt as _,
|
||||||
|
prelude::FluentBuilder as _,
|
||||||
|
scroll::ScrollbarShow,
|
||||||
|
ActiveTheme as _, ContextModal as _, IconName, Sizable as _, Theme, TitleBar,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{SelectFont, SelectLocale, SelectScrollbarShow};
|
||||||
|
|
||||||
|
pub struct AppTitleBar {
|
||||||
|
title: SharedString,
|
||||||
|
theme_color: Option<Hsla>,
|
||||||
|
locale_selector: View<LocaleSelector>,
|
||||||
|
font_size_selector: View<FontSizeSelector>,
|
||||||
|
theme_color_picker: View<ColorPicker>,
|
||||||
|
child: Rc<dyn Fn(&mut WindowContext) -> AnyElement>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppTitleBar {
|
||||||
|
pub fn new(title: impl Into<SharedString>, cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
let locale_selector = cx.new_view(LocaleSelector::new);
|
||||||
|
let font_size_selector = cx.new_view(FontSizeSelector::new);
|
||||||
|
|
||||||
|
let theme_color_picker = cx.new_view(|cx| {
|
||||||
|
let mut picker = ColorPicker::new("theme-color-picker", cx)
|
||||||
|
.xsmall()
|
||||||
|
.anchor(Corner::TopRight)
|
||||||
|
.label("Theme Color");
|
||||||
|
picker.set_value(cx.theme().primary, cx);
|
||||||
|
picker
|
||||||
|
});
|
||||||
|
cx.subscribe(
|
||||||
|
&theme_color_picker,
|
||||||
|
|this, _, ev: &ColorPickerEvent, cx| match ev {
|
||||||
|
ColorPickerEvent::Change(color) => {
|
||||||
|
this.set_theme_color(*color, cx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.detach();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
title: title.into(),
|
||||||
|
theme_color: None,
|
||||||
|
locale_selector,
|
||||||
|
font_size_selector,
|
||||||
|
theme_color_picker,
|
||||||
|
child: Rc::new(|_| div().into_any_element()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn child<F, E>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
E: IntoElement,
|
||||||
|
F: Fn(&mut WindowContext) -> E + 'static,
|
||||||
|
{
|
||||||
|
self.child = Rc::new(move |cx| f(cx).into_any_element());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_theme_color(&mut self, color: Option<Hsla>, cx: &mut ViewContext<Self>) {
|
||||||
|
self.theme_color = color;
|
||||||
|
if let Some(color) = self.theme_color {
|
||||||
|
let theme = cx.global_mut::<Theme>();
|
||||||
|
theme.apply_color(color);
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn change_color_mode(&mut self, _: &ClickEvent, cx: &mut ViewContext<Self>) {
|
||||||
|
let mode = match cx.theme().mode.is_dark() {
|
||||||
|
true => ui::ThemeMode::Light,
|
||||||
|
false => ui::ThemeMode::Dark,
|
||||||
|
};
|
||||||
|
|
||||||
|
Theme::change(mode, cx);
|
||||||
|
self.set_theme_color(self.theme_color, cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for AppTitleBar {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let notifications_count = cx.notifications().len();
|
||||||
|
|
||||||
|
TitleBar::new()
|
||||||
|
// left side
|
||||||
|
.child(div().flex().items_center().child(self.title.clone()))
|
||||||
|
.child(
|
||||||
|
div()
|
||||||
|
.flex()
|
||||||
|
.items_center()
|
||||||
|
.justify_end()
|
||||||
|
.px_2()
|
||||||
|
.gap_2()
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, cx| cx.stop_propagation())
|
||||||
|
.child(self.theme_color_picker.clone())
|
||||||
|
.child((self.child.clone())(cx))
|
||||||
|
.child(
|
||||||
|
Button::new("theme-mode")
|
||||||
|
.map(|this| {
|
||||||
|
if cx.theme().mode.is_dark() {
|
||||||
|
this.icon(IconName::Sun)
|
||||||
|
} else {
|
||||||
|
this.icon(IconName::Moon)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.on_click(cx.listener(Self::change_color_mode)),
|
||||||
|
)
|
||||||
|
.child(self.locale_selector.clone())
|
||||||
|
.child(self.font_size_selector.clone())
|
||||||
|
.child(
|
||||||
|
Badge::new().dot().count(1).child(
|
||||||
|
Button::new("github")
|
||||||
|
.icon(IconName::GitHub)
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.on_click(|_, cx| {
|
||||||
|
cx.open_url("https://github.com/longbridge/gpui-component")
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
div().relative().child(
|
||||||
|
Badge::new().count(notifications_count).max(99).child(
|
||||||
|
Button::new("bell")
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.compact()
|
||||||
|
.icon(IconName::Bell),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct LocaleSelector {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocaleSelector {
|
||||||
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_select_locale(&mut self, locale: &SelectLocale, cx: &mut ViewContext<Self>) {
|
||||||
|
ui::set_locale(&locale.0);
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for LocaleSelector {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
let locale = ui::locale().to_string();
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id("locale-selector")
|
||||||
|
.track_focus(&focus_handle)
|
||||||
|
.on_action(cx.listener(Self::on_select_locale))
|
||||||
|
.child(
|
||||||
|
Button::new("btn")
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.icon(IconName::Globe)
|
||||||
|
.popup_menu(move |this, _| {
|
||||||
|
this.menu_with_check(
|
||||||
|
"English",
|
||||||
|
locale == "en",
|
||||||
|
Box::new(SelectLocale("en".into())),
|
||||||
|
)
|
||||||
|
.menu_with_check(
|
||||||
|
"简体中文",
|
||||||
|
locale == "zh-CN",
|
||||||
|
Box::new(SelectLocale("zh-CN".into())),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.anchor(Corner::TopRight),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FontSizeSelector {
|
||||||
|
focus_handle: FocusHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FontSizeSelector {
|
||||||
|
pub fn new(cx: &mut ViewContext<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
focus_handle: cx.focus_handle(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_select_font(&mut self, font_size: &SelectFont, cx: &mut ViewContext<Self>) {
|
||||||
|
Theme::global_mut(cx).font_size = font_size.0 as f32;
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_select_scrollbar_show(&mut self, show: &SelectScrollbarShow, cx: &mut ViewContext<Self>) {
|
||||||
|
Theme::global_mut(cx).scrollbar_show = show.0;
|
||||||
|
cx.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Render for FontSizeSelector {
|
||||||
|
fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
|
let focus_handle = self.focus_handle.clone();
|
||||||
|
let font_size = cx.theme().font_size as i32;
|
||||||
|
let scroll_show = cx.theme().scrollbar_show;
|
||||||
|
|
||||||
|
div()
|
||||||
|
.id("font-size-selector")
|
||||||
|
.track_focus(&focus_handle)
|
||||||
|
.on_action(cx.listener(Self::on_select_font))
|
||||||
|
.on_action(cx.listener(Self::on_select_scrollbar_show))
|
||||||
|
.child(
|
||||||
|
Button::new("btn")
|
||||||
|
.small()
|
||||||
|
.ghost()
|
||||||
|
.icon(IconName::Settings2)
|
||||||
|
.popup_menu(move |this, _| {
|
||||||
|
this.menu_with_check(
|
||||||
|
"Font Large",
|
||||||
|
font_size == 18,
|
||||||
|
Box::new(SelectFont(18)),
|
||||||
|
)
|
||||||
|
.menu_with_check("Font Default", font_size == 16, Box::new(SelectFont(16)))
|
||||||
|
.menu_with_check("Font Small", font_size == 14, Box::new(SelectFont(14)))
|
||||||
|
.separator()
|
||||||
|
.menu_with_check(
|
||||||
|
"Scrolling to show Scrollbar",
|
||||||
|
scroll_show == ScrollbarShow::Scrolling,
|
||||||
|
Box::new(SelectScrollbarShow(ScrollbarShow::Scrolling)),
|
||||||
|
)
|
||||||
|
.menu_with_check(
|
||||||
|
"Hover to show Scrollbar",
|
||||||
|
scroll_show == ScrollbarShow::Hover,
|
||||||
|
Box::new(SelectScrollbarShow(ScrollbarShow::Hover)),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.anchor(Corner::TopRight),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::{h_flex, ActiveTheme, Icon, IconName, InteractiveElementExt as _, Siz
|
||||||
use gpui::{
|
use gpui::{
|
||||||
div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Div, Element, Hsla,
|
div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Div, Element, Hsla,
|
||||||
InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce, Stateful,
|
InteractiveElement as _, IntoElement, MouseButton, ParentElement, Pixels, RenderOnce, Stateful,
|
||||||
StatefulInteractiveElement as _, Style, Styled, WindowContext,
|
StatefulInteractiveElement as _, Style, Styled, TitlebarOptions, WindowContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const TITLE_BAR_HEIGHT: Pixels = px(35.);
|
pub const TITLE_BAR_HEIGHT: Pixels = px(35.);
|
||||||
|
|
@ -32,6 +32,15 @@ impl TitleBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the default title bar options for compatible with the [`crate::TitleBar`].
|
||||||
|
pub fn title_bar_options() -> TitlebarOptions {
|
||||||
|
TitlebarOptions {
|
||||||
|
title: None,
|
||||||
|
appears_transparent: true,
|
||||||
|
traffic_light_position: Some(gpui::point(px(9.0), px(9.0))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Add custom for close window event, default is None, then click X button will call `cx.remove_window()`.
|
/// Add custom for close window event, default is None, then click X button will call `cx.remove_window()`.
|
||||||
/// Linux only, this will do nothing on other platforms.
|
/// Linux only, this will do nothing on other platforms.
|
||||||
pub fn on_close_window(
|
pub fn on_close_window(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue