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" }
|
||||
```
|
||||
|
||||
More usage can be found in [story](https://github.com/longbridge/gpui-component/tree/main/crates/story) directory.
|
||||
|
||||
## Demo
|
||||
|
||||
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_prev))
|
||||
.size_full()
|
||||
.p_4()
|
||||
.justify_start()
|
||||
.gap_3()
|
||||
.child(
|
||||
|
|
@ -429,14 +428,14 @@ impl Render for InputStory {
|
|||
.gap_3()
|
||||
.child(
|
||||
Button::new("btn-submit")
|
||||
.w_full()
|
||||
.flex_1()
|
||||
.with_variant(ButtonVariant::Primary)
|
||||
.label("Submit")
|
||||
.on_click(cx.listener(|_, _, cx| cx.dispatch_action(Box::new(Tab)))),
|
||||
)
|
||||
.child(
|
||||
Button::new("btn-cancel")
|
||||
.w_full()
|
||||
.flex_1()
|
||||
.label("Cancel")
|
||||
.into_element(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ mod sidebar_story;
|
|||
mod switch_story;
|
||||
mod table_story;
|
||||
mod text_story;
|
||||
mod title_bar;
|
||||
mod tooltip_story;
|
||||
mod webview_story;
|
||||
|
||||
|
|
@ -29,10 +30,11 @@ pub use dropdown_story::DropdownStory;
|
|||
pub use form_story::FormStory;
|
||||
|
||||
use gpui::{
|
||||
actions, div, prelude::FluentBuilder as _, px, size, AnyElement, AnyView, AppContext, Bounds,
|
||||
Context as _, Div, EventEmitter, FocusableView, Global, Hsla, InteractiveElement, IntoElement,
|
||||
Model, ParentElement, Render, SharedString, StatefulInteractiveElement, Styled as _, View,
|
||||
ViewContext, VisualContext, WindowBounds, WindowContext, WindowKind, WindowOptions,
|
||||
actions, div, impl_internal_actions, prelude::FluentBuilder as _, px, size, AnyElement,
|
||||
AnyView, AppContext, Bounds, Context as _, Div, EventEmitter, FocusableView, Global, Hsla,
|
||||
InteractiveElement, IntoElement, Model, ParentElement, Render, SharedString,
|
||||
StatefulInteractiveElement, Styled as _, View, ViewContext, VisualContext, WindowBounds,
|
||||
WindowContext, WindowKind, WindowOptions,
|
||||
};
|
||||
pub use icon_story::IconStory;
|
||||
pub use image_story::ImageStory;
|
||||
|
|
@ -48,6 +50,7 @@ pub use sidebar_story::SidebarStory;
|
|||
pub use switch_story::SwitchStory;
|
||||
pub use table_story::TableStory;
|
||||
pub use text_story::TextStory;
|
||||
pub use title_bar::AppTitleBar;
|
||||
pub use tooltip_story::TooltipStory;
|
||||
pub use webview_story::WebViewStory;
|
||||
|
||||
|
|
@ -59,9 +62,23 @@ use ui::{
|
|||
label::Label,
|
||||
notification::Notification,
|
||||
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";
|
||||
|
||||
pub struct AppState {
|
||||
|
|
@ -101,11 +118,7 @@ where
|
|||
cx.spawn(|mut cx| async move {
|
||||
let options = WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
||||
// titlebar: Some(TitlebarOptions {
|
||||
// title: Some(title.clone()),
|
||||
// appears_transparent: true,
|
||||
// traffic_light_position: Some(point(px(9.0), px(9.0))),
|
||||
// }),
|
||||
titlebar: Some(TitleBar::title_bar_options()),
|
||||
window_min_size: Some(gpui::Size {
|
||||
width: px(640.),
|
||||
height: px(480.),
|
||||
|
|
@ -121,7 +134,8 @@ where
|
|||
let window = cx
|
||||
.open_window(options, |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");
|
||||
|
||||
|
|
@ -137,6 +151,34 @@ where
|
|||
.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 {}
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
|
|
|
|||
|
|
@ -1,58 +1,33 @@
|
|||
use anyhow::{Context, Result};
|
||||
use gpui::*;
|
||||
use prelude::FluentBuilder as _;
|
||||
use serde::Deserialize;
|
||||
use std::{sync::Arc, time::Duration};
|
||||
use story::{
|
||||
AccordionStory, AppState, Assets, ButtonStory, CalendarStory, DropdownStory, FormStory,
|
||||
IconStory, ImageStory, InputStory, ListStory, ModalStory, PopupStory, ProgressStory,
|
||||
ResizableStory, ScrollableStory, SidebarStory, StoryContainer, SwitchStory, TableStory,
|
||||
TextStory, TooltipStory,
|
||||
AccordionStory, AppState, AppTitleBar, Assets, ButtonStory, CalendarStory, DropdownStory,
|
||||
FormStory, IconStory, ImageStory, InputStory, ListStory, ModalStory, Open, PopupStory,
|
||||
ProgressStory, Quit, ResizableStory, ScrollableStory, SidebarStory, StoryContainer,
|
||||
SwitchStory, TableStory, TextStory, TooltipStory,
|
||||
};
|
||||
use ui::{
|
||||
badge::Badge,
|
||||
button::{Button, ButtonVariants as _},
|
||||
color_picker::{ColorPicker, ColorPickerEvent},
|
||||
dock::{DockArea, DockAreaState, DockEvent, DockItem, DockPlacement},
|
||||
popup_menu::PopupMenuExt,
|
||||
scroll::ScrollbarShow,
|
||||
ActiveTheme, ContextModal, IconName, Root, Sizable, Theme, TitleBar,
|
||||
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 {
|
||||
id: "main-dock",
|
||||
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) {
|
||||
cx.bind_keys([KeyBinding::new("cmd-q", Quit, None)]);
|
||||
|
||||
|
|
@ -63,11 +38,8 @@ pub fn init(cx: &mut AppContext) {
|
|||
}
|
||||
|
||||
pub struct StoryWorkspace {
|
||||
theme_color: Option<Hsla>,
|
||||
title_bar: View<AppTitleBar>,
|
||||
dock_area: View<DockArea>,
|
||||
locale_selector: View<LocaleSelector>,
|
||||
font_size_selector: View<FontSizeSelector>,
|
||||
theme_color_picker: View<ColorPicker>,
|
||||
last_layout_state: Option<DockAreaState>,
|
||||
_save_layout_task: Option<Task<()>>,
|
||||
}
|
||||
|
|
@ -118,57 +90,75 @@ impl StoryWorkspace {
|
|||
})
|
||||
.detach();
|
||||
|
||||
let locale_selector = cx.new_view(LocaleSelector::new);
|
||||
let font_size_selector = cx.new_view(FontSizeSelector::new);
|
||||
let title_bar = cx.new_view(|cx| {
|
||||
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| {
|
||||
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);
|
||||
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)
|
||||
}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
})
|
||||
});
|
||||
|
||||
Self {
|
||||
theme_color: None,
|
||||
dock_area,
|
||||
locale_selector,
|
||||
font_size_selector,
|
||||
theme_color_picker,
|
||||
title_bar,
|
||||
last_layout_state: 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>) {
|
||||
self._save_layout_task = Some(cx.spawn(|this, mut cx| async move {
|
||||
Timer::after(Duration::from_secs(10)).await;
|
||||
|
|
@ -345,11 +335,7 @@ impl StoryWorkspace {
|
|||
let options = WindowOptions {
|
||||
window_bounds: Some(WindowBounds::Windowed(window_bounds)),
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
titlebar: Some(TitlebarOptions {
|
||||
title: None,
|
||||
appears_transparent: true,
|
||||
traffic_light_position: Some(point(px(9.0), px(9.0))),
|
||||
}),
|
||||
titlebar: Some(TitleBar::title_bar_options()),
|
||||
window_min_size: Some(gpui::Size {
|
||||
width: px(640.),
|
||||
height: px(480.),
|
||||
|
|
@ -450,8 +436,6 @@ impl Render for StoryWorkspace {
|
|||
let drawer_layer = Root::render_drawer_layer(cx);
|
||||
let modal_layer = Root::render_modal_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()
|
||||
.id("story-workspace")
|
||||
|
|
@ -461,123 +445,7 @@ impl Render for StoryWorkspace {
|
|||
.size_full()
|
||||
.flex()
|
||||
.flex_col()
|
||||
.child(
|
||||
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.title_bar.clone())
|
||||
.child(self.dock_area.clone())
|
||||
.children(drawer_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() {
|
||||
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::{
|
||||
div, prelude::FluentBuilder as _, px, relative, AnyElement, ClickEvent, Div, Element, Hsla,
|
||||
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.);
|
||||
|
|
@ -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()`.
|
||||
/// Linux only, this will do nothing on other platforms.
|
||||
pub fn on_close_window(
|
||||
|
|
|
|||
Loading…
Reference in a new issue