title_bar: Fix TitleBar drag area on macOS, and only handle show window menu when it supports. (#1710)
Closes #1528 - Fix the TitleBar drag area on macOS. - Update Window Control Icon to use theme color.
This commit is contained in:
parent
4e0f9af789
commit
0f655314c9
2 changed files with 83 additions and 128 deletions
|
|
@ -1,14 +1,15 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
|
Selectable, Sizable,
|
||||||
actions::{Cancel, SelectLeft, SelectRight},
|
actions::{Cancel, SelectLeft, SelectRight},
|
||||||
button::{Button, ButtonVariants},
|
button::{Button, ButtonVariants},
|
||||||
h_flex,
|
h_flex,
|
||||||
menu::PopupMenu,
|
menu::PopupMenu,
|
||||||
Selectable, Sizable,
|
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
anchored, deferred, div, prelude::FluentBuilder, px, App, AppContext as _, ClickEvent, Context,
|
App, AppContext as _, ClickEvent, Context, DismissEvent, Entity, Focusable,
|
||||||
DismissEvent, Entity, Focusable, InteractiveElement as _, IntoElement, KeyBinding, OwnedMenu,
|
InteractiveElement as _, IntoElement, KeyBinding, MouseButton, OwnedMenu, ParentElement,
|
||||||
ParentElement, Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window,
|
Render, SharedString, StatefulInteractiveElement, Styled, Subscription, Window, anchored,
|
||||||
|
deferred, div, prelude::FluentBuilder, px,
|
||||||
};
|
};
|
||||||
|
|
||||||
const CONTEXT: &str = "AppMenuBar";
|
const CONTEXT: &str = "AppMenuBar";
|
||||||
|
|
@ -224,6 +225,11 @@ impl Render for AppMenu {
|
||||||
.ghost()
|
.ghost()
|
||||||
.label(self.name.clone())
|
.label(self.name.clone())
|
||||||
.selected(is_selected)
|
.selected(is_selected)
|
||||||
|
.on_mouse_down(MouseButton::Left, |_, window, cx| {
|
||||||
|
// Stop propagation to avoid dragging the window.
|
||||||
|
window.prevent_default();
|
||||||
|
cx.stop_propagation();
|
||||||
|
})
|
||||||
.on_click(cx.listener(Self::handle_trigger_click)),
|
.on_click(cx.listener(Self::handle_trigger_click)),
|
||||||
)
|
)
|
||||||
.on_hover(cx.listener(Self::handle_hover))
|
.on_hover(cx.listener(Self::handle_hover))
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,10 @@ use crate::{
|
||||||
ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _, StyledExt, h_flex,
|
ActiveTheme, Icon, IconName, InteractiveElementExt as _, Sizable as _, StyledExt, h_flex,
|
||||||
};
|
};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
AnyElement, App, ClickEvent, Element, Hsla, InteractiveElement, IntoElement, MouseButton,
|
AnyElement, App, ClickEvent, Context, Decorations, Hsla, InteractiveElement, IntoElement,
|
||||||
ParentElement, Pixels, RenderOnce, StatefulInteractiveElement as _, Style, StyleRefinement,
|
MouseButton, ParentElement, Pixels, Render, RenderOnce, StatefulInteractiveElement as _,
|
||||||
Styled, TitlebarOptions, Window, WindowControlArea, div, prelude::FluentBuilder as _, px,
|
StyleRefinement, Styled, TitlebarOptions, Window, WindowControlArea, div,
|
||||||
relative,
|
prelude::FluentBuilder as _, px,
|
||||||
};
|
};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
|
|
@ -120,33 +120,30 @@ impl ControlIcon {
|
||||||
matches!(self, Self::Close { .. })
|
matches!(self, Self::Close { .. })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fg(&self, cx: &App) -> Hsla {
|
#[inline]
|
||||||
if cx.theme().mode.is_dark() {
|
|
||||||
crate::white()
|
|
||||||
} else {
|
|
||||||
crate::black()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hover_fg(&self, cx: &App) -> Hsla {
|
fn hover_fg(&self, cx: &App) -> Hsla {
|
||||||
if self.is_close() || cx.theme().mode.is_dark() {
|
if self.is_close() {
|
||||||
crate::white()
|
cx.theme().danger_foreground
|
||||||
} else {
|
} else {
|
||||||
crate::black()
|
cx.theme().secondary_foreground
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
fn hover_bg(&self, cx: &App) -> Hsla {
|
fn hover_bg(&self, cx: &App) -> Hsla {
|
||||||
if self.is_close() {
|
if self.is_close() {
|
||||||
if cx.theme().mode.is_dark() {
|
cx.theme().danger_hover
|
||||||
crate::red_800()
|
|
||||||
} else {
|
|
||||||
crate::red_600()
|
|
||||||
}
|
|
||||||
} else if cx.theme().mode.is_dark() {
|
|
||||||
crate::stone_700()
|
|
||||||
} else {
|
} else {
|
||||||
crate::stone_200()
|
cx.theme().secondary_hover
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn active_bg(&self, cx: &mut App) -> Hsla {
|
||||||
|
if self.is_close() {
|
||||||
|
cx.theme().danger_active
|
||||||
|
} else {
|
||||||
|
cx.theme().secondary_active
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -155,9 +152,10 @@ impl RenderOnce for ControlIcon {
|
||||||
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
let is_linux = cfg!(target_os = "linux");
|
let is_linux = cfg!(target_os = "linux");
|
||||||
let is_windows = cfg!(target_os = "windows");
|
let is_windows = cfg!(target_os = "windows");
|
||||||
let fg = self.fg(cx);
|
|
||||||
let hover_fg = self.hover_fg(cx);
|
let hover_fg = self.hover_fg(cx);
|
||||||
|
let bg = cx.theme().title_bar;
|
||||||
let hover_bg = self.hover_bg(cx);
|
let hover_bg = self.hover_bg(cx);
|
||||||
|
let active_bg = self.active_bg(cx);
|
||||||
let icon = self.clone();
|
let icon = self.clone();
|
||||||
let on_close_window = match &self {
|
let on_close_window = match &self {
|
||||||
ControlIcon::Close { on_close_window } => on_close_window.clone(),
|
ControlIcon::Close { on_close_window } => on_close_window.clone(),
|
||||||
|
|
@ -169,10 +167,14 @@ impl RenderOnce for ControlIcon {
|
||||||
.flex()
|
.flex()
|
||||||
.w(TITLE_BAR_HEIGHT)
|
.w(TITLE_BAR_HEIGHT)
|
||||||
.h_full()
|
.h_full()
|
||||||
|
.flex_shrink_0()
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.content_center()
|
.content_center()
|
||||||
.items_center()
|
.items_center()
|
||||||
.text_color(fg)
|
.bg(bg)
|
||||||
|
.text_color(cx.theme().foreground)
|
||||||
|
.hover(|style| style.bg(hover_bg).text_color(hover_fg))
|
||||||
|
.active(|style| style.bg(active_bg).text_color(hover_fg))
|
||||||
.when(is_windows, |this| {
|
.when(is_windows, |this| {
|
||||||
this.window_control_area(self.window_control_area())
|
this.window_control_area(self.window_control_area())
|
||||||
})
|
})
|
||||||
|
|
@ -196,8 +198,6 @@ impl RenderOnce for ControlIcon {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.hover(|style| style.bg(hover_bg).text_color(hover_fg))
|
|
||||||
.active(|style| style.bg(hover_bg.opacity(0.7)))
|
|
||||||
.child(Icon::new(self.icon()).small())
|
.child(Icon::new(self.icon()).small())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -218,18 +218,12 @@ impl RenderOnce for WindowControls {
|
||||||
.items_center()
|
.items_center()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.h_full()
|
.h_full()
|
||||||
.child(
|
.child(ControlIcon::minimize())
|
||||||
h_flex()
|
.child(if window.is_maximized() {
|
||||||
.justify_center()
|
ControlIcon::restore()
|
||||||
.content_stretch()
|
} else {
|
||||||
.h_full()
|
ControlIcon::maximize()
|
||||||
.child(ControlIcon::minimize())
|
})
|
||||||
.child(if window.is_maximized() {
|
|
||||||
ControlIcon::restore()
|
|
||||||
} else {
|
|
||||||
ControlIcon::maximize()
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.child(ControlIcon::close(self.on_close_window))
|
.child(ControlIcon::close(self.on_close_window))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -246,11 +240,25 @@ impl ParentElement for TitleBar {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct TitleBarState {
|
||||||
|
should_move: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remove this when GPUI has released v0.2.3
|
||||||
|
impl Render for TitleBarState {
|
||||||
|
fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
|
||||||
|
div()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl RenderOnce for TitleBar {
|
impl RenderOnce for TitleBar {
|
||||||
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||||
|
let is_client_decorated = matches!(window.window_decorations(), Decorations::Client { .. });
|
||||||
let is_linux = cfg!(target_os = "linux");
|
let is_linux = cfg!(target_os = "linux");
|
||||||
let is_macos = cfg!(target_os = "macos");
|
let is_macos = cfg!(target_os = "macos");
|
||||||
|
|
||||||
|
let state = window.use_state(cx, |_, _| TitleBarState { should_move: false });
|
||||||
|
|
||||||
div().flex_shrink_0().child(
|
div().flex_shrink_0().child(
|
||||||
div()
|
div()
|
||||||
.id("title-bar")
|
.id("title-bar")
|
||||||
|
|
@ -270,16 +278,37 @@ impl RenderOnce for TitleBar {
|
||||||
.when(is_macos, |this| {
|
.when(is_macos, |this| {
|
||||||
this.on_double_click(|_, window, _| window.titlebar_double_click())
|
this.on_double_click(|_, window, _| window.titlebar_double_click())
|
||||||
})
|
})
|
||||||
|
.on_mouse_down_out(window.listener_for(&state, |state, _, _, _| {
|
||||||
|
state.should_move = false;
|
||||||
|
}))
|
||||||
|
.on_mouse_down(
|
||||||
|
MouseButton::Left,
|
||||||
|
window.listener_for(&state, |state, _, _, _| {
|
||||||
|
state.should_move = true;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.on_mouse_up(
|
||||||
|
MouseButton::Left,
|
||||||
|
window.listener_for(&state, |state, _, _, _| {
|
||||||
|
state.should_move = false;
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.on_mouse_move(window.listener_for(&state, |state, _, window, _| {
|
||||||
|
if state.should_move {
|
||||||
|
state.should_move = false;
|
||||||
|
window.start_window_move();
|
||||||
|
}
|
||||||
|
}))
|
||||||
.child(
|
.child(
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("bar")
|
.id("bar")
|
||||||
.when(window.is_fullscreen(), |this| this.pl_3())
|
|
||||||
.window_control_area(WindowControlArea::Drag)
|
.window_control_area(WindowControlArea::Drag)
|
||||||
|
.when(window.is_fullscreen(), |this| this.pl_3())
|
||||||
.h_full()
|
.h_full()
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.flex_shrink_0()
|
.flex_shrink_0()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.when(is_linux, |this| {
|
.when(is_linux && is_client_decorated, |this| {
|
||||||
this.child(
|
this.child(
|
||||||
div()
|
div()
|
||||||
.top_0()
|
.top_0()
|
||||||
|
|
@ -287,7 +316,9 @@ impl RenderOnce for TitleBar {
|
||||||
.absolute()
|
.absolute()
|
||||||
.size_full()
|
.size_full()
|
||||||
.h_full()
|
.h_full()
|
||||||
.child(TitleBarElement {}),
|
.on_mouse_down(MouseButton::Right, move |ev, window, _| {
|
||||||
|
window.show_window_menu(ev.position)
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.children(self.children),
|
.children(self.children),
|
||||||
|
|
@ -298,85 +329,3 @@ impl RenderOnce for TitleBar {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A TitleBar Element that can be move the window.
|
|
||||||
pub struct TitleBarElement {}
|
|
||||||
|
|
||||||
impl IntoElement for TitleBarElement {
|
|
||||||
type Element = Self;
|
|
||||||
|
|
||||||
fn into_element(self) -> Self::Element {
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Element for TitleBarElement {
|
|
||||||
type RequestLayoutState = ();
|
|
||||||
|
|
||||||
type PrepaintState = ();
|
|
||||||
|
|
||||||
fn id(&self) -> Option<gpui::ElementId> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_layout(
|
|
||||||
&mut self,
|
|
||||||
_: Option<&gpui::GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) -> (gpui::LayoutId, Self::RequestLayoutState) {
|
|
||||||
let mut style = Style::default();
|
|
||||||
style.flex_grow = 1.0;
|
|
||||||
style.flex_shrink = 1.0;
|
|
||||||
style.size.width = relative(1.).into();
|
|
||||||
style.size.height = relative(1.).into();
|
|
||||||
|
|
||||||
let id = window.request_layout(style, [], cx);
|
|
||||||
(id, ())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn prepaint(
|
|
||||||
&mut self,
|
|
||||||
_: Option<&gpui::GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
_: gpui::Bounds<Pixels>,
|
|
||||||
_: &mut Self::RequestLayoutState,
|
|
||||||
_window: &mut Window,
|
|
||||||
_cx: &mut App,
|
|
||||||
) -> Self::PrepaintState {
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn paint(
|
|
||||||
&mut self,
|
|
||||||
_: Option<&gpui::GlobalElementId>,
|
|
||||||
_: Option<&gpui::InspectorElementId>,
|
|
||||||
bounds: gpui::Bounds<Pixels>,
|
|
||||||
_: &mut Self::RequestLayoutState,
|
|
||||||
_: &mut Self::PrepaintState,
|
|
||||||
window: &mut Window,
|
|
||||||
cx: &mut App,
|
|
||||||
) {
|
|
||||||
use gpui::{MouseButton, MouseMoveEvent, MouseUpEvent};
|
|
||||||
window.on_mouse_event(
|
|
||||||
move |ev: &MouseMoveEvent, _, window: &mut Window, cx: &mut App| {
|
|
||||||
if bounds.contains(&ev.position) && ev.pressed_button == Some(MouseButton::Left) {
|
|
||||||
window.start_window_move();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
window.on_mouse_event(
|
|
||||||
move |ev: &MouseUpEvent, _, window: &mut Window, cx: &mut App| {
|
|
||||||
if bounds.contains(&ev.position) && ev.button == MouseButton::Right {
|
|
||||||
window.show_window_menu(ev.position);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue