dock: Add PanelControl for pinning the toolbar button (#537)
<img width="387" alt="image" src="https://github.com/user-attachments/assets/b3d3f2a8-dc4e-49e6-8e82-e7f551d70bc7" />
This commit is contained in:
parent
de42cc46a7
commit
ceba66c22d
7 changed files with 81 additions and 43 deletions
|
|
@ -3,6 +3,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonVariant, ButtonVariants},
|
button::{Button, ButtonVariant, ButtonVariants},
|
||||||
|
dock::PanelControl,
|
||||||
h_flex,
|
h_flex,
|
||||||
theme::ActiveTheme as _,
|
theme::ActiveTheme as _,
|
||||||
v_flex, Icon, IconName,
|
v_flex, Icon, IconName,
|
||||||
|
|
@ -33,8 +34,8 @@ impl super::Story for IconStory {
|
||||||
Self::view(cx)
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoomable() -> bool {
|
fn zoomable() -> Option<PanelControl> {
|
||||||
false
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use gpui::{px, ParentElement as _, Render, Styled, View, VisualContext as _, WindowContext};
|
use gpui::{px, ParentElement as _, Render, Styled, View, VisualContext as _, WindowContext};
|
||||||
use ui::{h_flex, v_flex, SvgImg};
|
use ui::{dock::PanelControl, h_flex, v_flex, SvgImg};
|
||||||
|
|
||||||
const GOOGLE_LOGO: &str = include_str!("./fixtures/google.svg");
|
const GOOGLE_LOGO: &str = include_str!("./fixtures/google.svg");
|
||||||
const PIE_JSON: &str = include_str!("./fixtures/pie.json");
|
const PIE_JSON: &str = include_str!("./fixtures/pie.json");
|
||||||
|
|
@ -19,6 +19,10 @@ impl super::Story for ImageStory {
|
||||||
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx)
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn zoomable() -> Option<PanelControl> {
|
||||||
|
Some(PanelControl::Toolbar)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ImageStory {
|
impl ImageStory {
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ use gpui::{
|
||||||
use ui::{
|
use ui::{
|
||||||
button::Button,
|
button::Button,
|
||||||
divider::Divider,
|
divider::Divider,
|
||||||
dock::{register_panel, Panel, PanelEvent, PanelInfo, PanelState, TitleStyle},
|
dock::{register_panel, Panel, PanelControl, PanelEvent, PanelInfo, PanelState, TitleStyle},
|
||||||
h_flex,
|
h_flex,
|
||||||
label::Label,
|
label::Label,
|
||||||
notification::Notification,
|
notification::Notification,
|
||||||
|
|
@ -147,7 +147,7 @@ pub struct StoryContainer {
|
||||||
story: Option<AnyView>,
|
story: Option<AnyView>,
|
||||||
story_klass: Option<SharedString>,
|
story_klass: Option<SharedString>,
|
||||||
closable: bool,
|
closable: bool,
|
||||||
zoomable: bool,
|
zoomable: Option<PanelControl>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
@ -167,8 +167,8 @@ pub trait Story: FocusableView {
|
||||||
fn closable() -> bool {
|
fn closable() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
fn zoomable() -> bool {
|
fn zoomable() -> Option<PanelControl> {
|
||||||
true
|
Some(PanelControl::default())
|
||||||
}
|
}
|
||||||
fn title_bg() -> Option<Hsla> {
|
fn title_bg() -> Option<Hsla> {
|
||||||
None
|
None
|
||||||
|
|
@ -192,7 +192,7 @@ impl StoryContainer {
|
||||||
story: None,
|
story: None,
|
||||||
story_klass: None,
|
story_klass: None,
|
||||||
closable: true,
|
closable: true,
|
||||||
zoomable: true,
|
zoomable: Some(PanelControl::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -260,7 +260,13 @@ impl StoryState {
|
||||||
fn to_story(
|
fn to_story(
|
||||||
&self,
|
&self,
|
||||||
cx: &mut WindowContext,
|
cx: &mut WindowContext,
|
||||||
) -> (&'static str, &'static str, bool, bool, AnyView) {
|
) -> (
|
||||||
|
&'static str,
|
||||||
|
&'static str,
|
||||||
|
bool,
|
||||||
|
Option<PanelControl>,
|
||||||
|
AnyView,
|
||||||
|
) {
|
||||||
macro_rules! story {
|
macro_rules! story {
|
||||||
($klass:tt) => {
|
($klass:tt) => {
|
||||||
(
|
(
|
||||||
|
|
@ -324,7 +330,7 @@ impl Panel for StoryContainer {
|
||||||
self.closable
|
self.closable
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoomable(&self, _cx: &AppContext) -> bool {
|
fn zoomable(&self, _cx: &AppContext) -> Option<PanelControl> {
|
||||||
self.zoomable
|
self.zoomable
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -172,10 +172,6 @@ impl super::Story for SidebarStory {
|
||||||
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
fn new_view(cx: &mut WindowContext) -> View<impl gpui::FocusableView> {
|
||||||
Self::view(cx)
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoomable() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
impl gpui::FocusableView for SidebarStory {
|
impl gpui::FocusableView for SidebarStory {
|
||||||
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
fn focus_handle(&self, _: &gpui::AppContext) -> gpui::FocusHandle {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ use gpui::{
|
||||||
use ui::{
|
use ui::{
|
||||||
button::{Button, ButtonVariant, ButtonVariants},
|
button::{Button, ButtonVariant, ButtonVariants},
|
||||||
checkbox::Checkbox,
|
checkbox::Checkbox,
|
||||||
|
dock::PanelControl,
|
||||||
h_flex,
|
h_flex,
|
||||||
label::Label,
|
label::Label,
|
||||||
tooltip::Tooltip,
|
tooltip::Tooltip,
|
||||||
|
|
@ -37,8 +38,8 @@ impl super::Story for TooltipStory {
|
||||||
Self::view(cx)
|
Self::view(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoomable() -> bool {
|
fn zoomable() -> Option<PanelControl> {
|
||||||
false
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl gpui::FocusableView for TooltipStory {
|
impl gpui::FocusableView for TooltipStory {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,26 @@ pub struct TitleStyle {
|
||||||
pub foreground: Hsla,
|
pub foreground: Hsla,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Default)]
|
||||||
|
pub enum PanelControl {
|
||||||
|
Both,
|
||||||
|
#[default]
|
||||||
|
Menu,
|
||||||
|
Toolbar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PanelControl {
|
||||||
|
#[inline]
|
||||||
|
pub fn toolbar_visible(&self) -> bool {
|
||||||
|
matches!(self, PanelControl::Both | PanelControl::Toolbar)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn menu_visible(&self) -> bool {
|
||||||
|
matches!(self, PanelControl::Both | PanelControl::Menu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The Panel trait used to define the panel.
|
/// The Panel trait used to define the panel.
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
||||||
|
|
@ -56,11 +76,11 @@ pub trait Panel: EventEmitter<PanelEvent> + FocusableView {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return true if the panel is zoomable, default is `false`.
|
/// Return `PanelControl` if the panel is zoomable, default is `None`.
|
||||||
///
|
///
|
||||||
/// This method called in Panel render, we should make sure it is fast.
|
/// This method called in Panel render, we should make sure it is fast.
|
||||||
fn zoomable(&self, cx: &AppContext) -> bool {
|
fn zoomable(&self, cx: &AppContext) -> Option<PanelControl> {
|
||||||
true
|
Some(PanelControl::Menu)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return false to hide panel, true to show panel, default is `true`.
|
/// Return false to hide panel, true to show panel, default is `true`.
|
||||||
|
|
@ -108,7 +128,7 @@ pub trait PanelView: 'static + Send + Sync {
|
||||||
fn title(&self, cx: &WindowContext) -> AnyElement;
|
fn title(&self, cx: &WindowContext) -> AnyElement;
|
||||||
fn title_style(&self, cx: &AppContext) -> Option<TitleStyle>;
|
fn title_style(&self, cx: &AppContext) -> Option<TitleStyle>;
|
||||||
fn closable(&self, cx: &AppContext) -> bool;
|
fn closable(&self, cx: &AppContext) -> bool;
|
||||||
fn zoomable(&self, cx: &AppContext) -> bool;
|
fn zoomable(&self, cx: &AppContext) -> Option<PanelControl>;
|
||||||
fn visible(&self, cx: &AppContext) -> bool;
|
fn visible(&self, cx: &AppContext) -> bool;
|
||||||
fn set_active(&self, active: bool, cx: &mut WindowContext);
|
fn set_active(&self, active: bool, cx: &mut WindowContext);
|
||||||
fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
|
fn set_zoomed(&self, zoomed: bool, cx: &mut WindowContext);
|
||||||
|
|
@ -136,7 +156,7 @@ impl<T: Panel> PanelView for View<T> {
|
||||||
self.read(cx).closable(cx)
|
self.read(cx).closable(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoomable(&self, cx: &AppContext) -> bool {
|
fn zoomable(&self, cx: &AppContext) -> Option<PanelControl> {
|
||||||
self.read(cx).zoomable(cx)
|
self.read(cx).zoomable(cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -20,14 +20,14 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ClosePanel, DockArea, DockPlacement, Panel, PanelEvent, PanelState, PanelStyle, PanelView,
|
ClosePanel, DockArea, DockPlacement, Panel, PanelControl, PanelEvent, PanelState, PanelStyle,
|
||||||
StackPanel, ToggleZoom,
|
PanelView, StackPanel, ToggleZoom,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct TabState {
|
struct TabState {
|
||||||
closable: bool,
|
closable: bool,
|
||||||
zoomable: bool,
|
zoomable: Option<PanelControl>,
|
||||||
draggable: bool,
|
draggable: bool,
|
||||||
droppable: bool,
|
droppable: bool,
|
||||||
active_panel: Option<Arc<dyn PanelView>>,
|
active_panel: Option<Arc<dyn PanelView>>,
|
||||||
|
|
@ -104,10 +104,8 @@ impl Panel for TabPanel {
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn zoomable(&self, cx: &AppContext) -> bool {
|
fn zoomable(&self, cx: &AppContext) -> Option<PanelControl> {
|
||||||
self.active_panel(cx)
|
self.active_panel(cx).and_then(|panel| panel.zoomable(cx))
|
||||||
.map(|panel| panel.zoomable(cx))
|
|
||||||
.unwrap_or(false)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visible(&self, cx: &AppContext) -> bool {
|
fn visible(&self, cx: &AppContext) -> bool {
|
||||||
|
|
@ -377,12 +375,12 @@ impl TabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_toolbar(&self, state: &TabState, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
fn render_toolbar(&self, state: &TabState, cx: &mut ViewContext<Self>) -> impl IntoElement {
|
||||||
let is_zoomed = self.is_zoomed && state.zoomable;
|
let is_zoomed = self.is_zoomed;
|
||||||
let view = cx.view().clone();
|
let view = cx.view().clone();
|
||||||
let build_popup_menu = move |this, cx: &WindowContext| view.read(cx).popup_menu(this, cx);
|
let build_popup_menu = move |this, cx: &WindowContext| view.read(cx).popup_menu(this, cx);
|
||||||
|
let zoomable_toolbar_visible = state.zoomable.map_or(false, |v| v.toolbar_visible());
|
||||||
|
|
||||||
// TODO: Do not show MenuButton if there is no menu items
|
// TODO: Do not show MenuButton if there is no menu items
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.occlude()
|
.occlude()
|
||||||
|
|
@ -390,17 +388,29 @@ impl TabPanel {
|
||||||
.when_some(self.toolbar_buttons(cx), |this, buttons| {
|
.when_some(self.toolbar_buttons(cx), |this, buttons| {
|
||||||
this.children(buttons.into_iter().map(|btn| btn.xsmall().ghost()))
|
this.children(buttons.into_iter().map(|btn| btn.xsmall().ghost()))
|
||||||
})
|
})
|
||||||
.when(self.is_zoomed, |this| {
|
.map(|this| {
|
||||||
this.child(
|
let value = if is_zoomed {
|
||||||
Button::new("zoom")
|
Some(("zoom-out", IconName::Minimize, t!("Dock.Zoom Out")))
|
||||||
.icon(IconName::Minimize)
|
} else if zoomable_toolbar_visible {
|
||||||
.xsmall()
|
Some(("zoom-in", IconName::Maximize, t!("Dock.Zoom In")))
|
||||||
.ghost()
|
} else {
|
||||||
.tooltip(t!("Dock.Zoom Out"))
|
None
|
||||||
.on_click(
|
};
|
||||||
cx.listener(|view, _, cx| view.on_action_toggle_zoom(&ToggleZoom, cx)),
|
|
||||||
),
|
if let Some((id, icon, tooltip)) = value {
|
||||||
)
|
this.child(
|
||||||
|
Button::new(id)
|
||||||
|
.icon(icon)
|
||||||
|
.xsmall()
|
||||||
|
.ghost()
|
||||||
|
.tooltip(tooltip)
|
||||||
|
.on_click(cx.listener(|view, _, cx| {
|
||||||
|
view.on_action_toggle_zoom(&ToggleZoom, cx)
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
Button::new("menu")
|
Button::new("menu")
|
||||||
|
|
@ -408,7 +418,7 @@ impl TabPanel {
|
||||||
.xsmall()
|
.xsmall()
|
||||||
.ghost()
|
.ghost()
|
||||||
.popup_menu({
|
.popup_menu({
|
||||||
let zoomable = state.zoomable;
|
let zoomable = state.zoomable.map_or(false, |v| v.menu_visible());
|
||||||
let closable = state.closable;
|
let closable = state.closable;
|
||||||
|
|
||||||
move |this, cx| {
|
move |this, cx| {
|
||||||
|
|
@ -932,7 +942,7 @@ impl TabPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_action_toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
|
fn on_action_toggle_zoom(&mut self, _: &ToggleZoom, cx: &mut ViewContext<Self>) {
|
||||||
if !self.zoomable(cx) {
|
if self.zoomable(cx).is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue