gpui: Implement dynamic window control elements (#30828)
Allows setting element as window control elements which consist of `Drag`, `Close`, `Max`, or `Min`. This allows you to implement dynamically sized elements that control the platform window, this is used for areas such as the title bar. Currently only implemented for Windows. Release Notes: - N/A
This commit is contained in:
parent
d9efa2860f
commit
ca3f46588a
11 changed files with 129 additions and 96 deletions
|
@ -21,7 +21,8 @@ use crate::{
|
||||||
HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent,
|
HitboxId, InspectorElementId, IntoElement, IsZero, KeyContext, KeyDownEvent, KeyUpEvent,
|
||||||
LayoutId, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
LayoutId, ModifiersChangedEvent, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
Overflow, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
|
Overflow, ParentElement, Pixels, Point, Render, ScrollWheelEvent, SharedString, Size, Style,
|
||||||
StyleRefinement, Styled, Task, TooltipId, Visibility, Window, point, px, size,
|
StyleRefinement, Styled, Task, TooltipId, Visibility, Window, WindowControlArea, point, px,
|
||||||
|
size,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use refineable::Refineable;
|
use refineable::Refineable;
|
||||||
|
@ -575,6 +576,12 @@ impl Interactivity {
|
||||||
self.hitbox_behavior = HitboxBehavior::BlockMouse;
|
self.hitbox_behavior = HitboxBehavior::BlockMouse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the bounds of this element as a window control area for the platform window.
|
||||||
|
/// The imperative API equivalent to [`InteractiveElement::window_control_area`]
|
||||||
|
pub fn window_control_area(&mut self, area: WindowControlArea) {
|
||||||
|
self.window_control = Some(area);
|
||||||
|
}
|
||||||
|
|
||||||
/// Block non-scroll mouse interactions with elements behind this element's hitbox. See
|
/// Block non-scroll mouse interactions with elements behind this element's hitbox. See
|
||||||
/// [`Hitbox::is_hovered`] for details.
|
/// [`Hitbox::is_hovered`] for details.
|
||||||
///
|
///
|
||||||
|
@ -958,6 +965,13 @@ pub trait InteractiveElement: Sized {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the bounds of this element as a window control area for the platform window.
|
||||||
|
/// The fluent API equivalent to [`Interactivity::window_control_area`]
|
||||||
|
fn window_control_area(mut self, area: WindowControlArea) -> Self {
|
||||||
|
self.interactivity().window_control_area(area);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Block non-scroll mouse interactions with elements behind this element's hitbox. See
|
/// Block non-scroll mouse interactions with elements behind this element's hitbox. See
|
||||||
/// [`Hitbox::is_hovered`] for details.
|
/// [`Hitbox::is_hovered`] for details.
|
||||||
///
|
///
|
||||||
|
@ -1447,6 +1461,7 @@ pub struct Interactivity {
|
||||||
pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
|
pub(crate) drag_listener: Option<(Arc<dyn Any>, DragListener)>,
|
||||||
pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
|
pub(crate) hover_listener: Option<Box<dyn Fn(&bool, &mut Window, &mut App)>>,
|
||||||
pub(crate) tooltip_builder: Option<TooltipBuilder>,
|
pub(crate) tooltip_builder: Option<TooltipBuilder>,
|
||||||
|
pub(crate) window_control: Option<WindowControlArea>,
|
||||||
pub(crate) hitbox_behavior: HitboxBehavior,
|
pub(crate) hitbox_behavior: HitboxBehavior,
|
||||||
|
|
||||||
#[cfg(any(feature = "inspector", debug_assertions))]
|
#[cfg(any(feature = "inspector", debug_assertions))]
|
||||||
|
@ -1611,6 +1626,7 @@ impl Interactivity {
|
||||||
|
|
||||||
fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
|
fn should_insert_hitbox(&self, style: &Style, window: &Window, cx: &App) -> bool {
|
||||||
self.hitbox_behavior != HitboxBehavior::Normal
|
self.hitbox_behavior != HitboxBehavior::Normal
|
||||||
|
|| self.window_control.is_some()
|
||||||
|| style.mouse_cursor.is_some()
|
|| style.mouse_cursor.is_some()
|
||||||
|| self.group.is_some()
|
|| self.group.is_some()
|
||||||
|| self.scroll_offset.is_some()
|
|| self.scroll_offset.is_some()
|
||||||
|
@ -1740,6 +1756,11 @@ impl Interactivity {
|
||||||
GroupHitboxes::push(group, hitbox.id, cx);
|
GroupHitboxes::push(group, hitbox.id, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(area) = self.window_control {
|
||||||
|
window
|
||||||
|
.insert_window_control_hitbox(area, hitbox.clone());
|
||||||
|
}
|
||||||
|
|
||||||
self.paint_mouse_listeners(
|
self.paint_mouse_listeners(
|
||||||
hitbox,
|
hitbox,
|
||||||
element_state.as_mut(),
|
element_state.as_mut(),
|
||||||
|
|
|
@ -36,7 +36,7 @@ use crate::{
|
||||||
ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
|
ForegroundExecutor, GlyphId, GpuSpecs, ImageSource, Keymap, LineLayout, Pixels, PlatformInput,
|
||||||
Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
|
Point, RenderGlyphParams, RenderImage, RenderImageParams, RenderSvgParams, ScaledPixels, Scene,
|
||||||
ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer, SvgSize, Task, TaskLabel, Window,
|
ShapedGlyph, ShapedRun, SharedString, Size, SvgRenderer, SvgSize, Task, TaskLabel, Window,
|
||||||
hash, point, px, size,
|
WindowControlArea, hash, point, px, size,
|
||||||
};
|
};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use async_task::Runnable;
|
use async_task::Runnable;
|
||||||
|
@ -436,6 +436,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
|
||||||
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
|
fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>);
|
||||||
fn on_moved(&self, callback: Box<dyn FnMut()>);
|
fn on_moved(&self, callback: Box<dyn FnMut()>);
|
||||||
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>);
|
||||||
|
fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>);
|
||||||
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
fn on_close(&self, callback: Box<dyn FnOnce()>);
|
||||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>);
|
||||||
fn draw(&self, scene: &Scene);
|
fn draw(&self, scene: &Scene);
|
||||||
|
|
|
@ -31,8 +31,8 @@ use crate::{
|
||||||
AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
|
AnyWindowHandle, Bounds, Decorations, Globals, GpuSpecs, Modifiers, Output, Pixels,
|
||||||
PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
|
PlatformDisplay, PlatformInput, Point, PromptButton, PromptLevel, RequestFrameOptions,
|
||||||
ResizeEdge, ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
|
ResizeEdge, ScaledPixels, Size, Tiling, WaylandClientStatePtr, WindowAppearance,
|
||||||
WindowBackgroundAppearance, WindowBounds, WindowControls, WindowDecorations, WindowParams, px,
|
WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowControls, WindowDecorations,
|
||||||
size,
|
WindowParams, px, size,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
|
@ -978,6 +978,10 @@ impl PlatformWindow for WaylandWindow {
|
||||||
self.0.callbacks.borrow_mut().close = Some(callback);
|
self.0.callbacks.borrow_mut().close = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
||||||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ use crate::{
|
||||||
AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
|
AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, GpuSpecs, Modifiers,
|
||||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
||||||
Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
|
Point, PromptButton, PromptLevel, RequestFrameOptions, ResizeEdge, ScaledPixels, Scene, Size,
|
||||||
Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowDecorations,
|
Tiling, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea,
|
||||||
WindowKind, WindowParams, X11ClientStatePtr, px, size,
|
WindowDecorations, WindowKind, WindowParams, X11ClientStatePtr, px, size,
|
||||||
};
|
};
|
||||||
|
|
||||||
use blade_graphics as gpu;
|
use blade_graphics as gpu;
|
||||||
|
@ -1408,6 +1408,10 @@ impl PlatformWindow for X11Window {
|
||||||
self.0.callbacks.borrow_mut().close = Some(callback);
|
self.0.callbacks.borrow_mut().close = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
||||||
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
self.0.callbacks.borrow_mut().appearance_changed = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,8 +4,8 @@ use crate::{
|
||||||
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
KeyDownEvent, Keystroke, Modifiers, ModifiersChangedEvent, MouseButton, MouseDownEvent,
|
||||||
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
MouseMoveEvent, MouseUpEvent, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||||
PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions, ScaledPixels, Size,
|
PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions, ScaledPixels, Size,
|
||||||
Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowKind, WindowParams,
|
Timer, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea,
|
||||||
platform::PlatformInputHandler, point, px, size,
|
WindowKind, WindowParams, platform::PlatformInputHandler, point, px, size,
|
||||||
};
|
};
|
||||||
use block::ConcreteBlock;
|
use block::ConcreteBlock;
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
|
@ -1146,6 +1146,10 @@ impl PlatformWindow for MacWindow {
|
||||||
self.0.as_ref().lock().close_callback = Some(callback);
|
self.0.as_ref().lock().close_callback = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_hit_test_window_control(&self, _callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
||||||
self.0.lock().appearance_changed_callback = Some(callback);
|
self.0.lock().appearance_changed_callback = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
|
AnyWindowHandle, AtlasKey, AtlasTextureId, AtlasTile, Bounds, DispatchEventResult, GpuSpecs,
|
||||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
||||||
Point, PromptButton, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId,
|
Point, PromptButton, RequestFrameOptions, ScaledPixels, Size, TestPlatform, TileId,
|
||||||
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowParams,
|
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowControlArea, WindowParams,
|
||||||
};
|
};
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
@ -21,6 +21,7 @@ pub(crate) struct TestWindowState {
|
||||||
platform: Weak<TestPlatform>,
|
platform: Weak<TestPlatform>,
|
||||||
sprite_atlas: Arc<dyn PlatformAtlas>,
|
sprite_atlas: Arc<dyn PlatformAtlas>,
|
||||||
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
|
pub(crate) should_close_handler: Option<Box<dyn FnMut() -> bool>>,
|
||||||
|
hit_test_window_control_callback: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
|
||||||
input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
|
input_callback: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
|
||||||
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
active_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
||||||
hover_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
hover_status_change_callback: Option<Box<dyn FnMut(bool)>>,
|
||||||
|
@ -65,6 +66,7 @@ impl TestWindow {
|
||||||
title: Default::default(),
|
title: Default::default(),
|
||||||
edited: false,
|
edited: false,
|
||||||
should_close_handler: None,
|
should_close_handler: None,
|
||||||
|
hit_test_window_control_callback: None,
|
||||||
input_callback: None,
|
input_callback: None,
|
||||||
active_status_change_callback: None,
|
active_status_change_callback: None,
|
||||||
hover_status_change_callback: None,
|
hover_status_change_callback: None,
|
||||||
|
@ -254,6 +256,10 @@ impl PlatformWindow for TestWindow {
|
||||||
|
|
||||||
fn on_close(&self, _callback: Box<dyn FnOnce()>) {}
|
fn on_close(&self, _callback: Box<dyn FnOnce()>) {}
|
||||||
|
|
||||||
|
fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
|
||||||
|
self.0.lock().hit_test_window_control_callback = Some(callback);
|
||||||
|
}
|
||||||
|
|
||||||
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
|
fn on_appearance_changed(&self, _callback: Box<dyn FnMut()>) {}
|
||||||
|
|
||||||
fn draw(&self, _scene: &crate::Scene) {}
|
fn draw(&self, _scene: &crate::Scene) {}
|
||||||
|
|
|
@ -35,7 +35,7 @@ pub(crate) fn handle_msg(
|
||||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||||
) -> LRESULT {
|
) -> LRESULT {
|
||||||
let handled = match msg {
|
let handled = match msg {
|
||||||
WM_ACTIVATE => handle_activate_msg(handle, wparam, state_ptr),
|
WM_ACTIVATE => handle_activate_msg(wparam, state_ptr),
|
||||||
WM_CREATE => handle_create_msg(handle, state_ptr),
|
WM_CREATE => handle_create_msg(handle, state_ptr),
|
||||||
WM_MOVE => handle_move_msg(handle, lparam, state_ptr),
|
WM_MOVE => handle_move_msg(handle, lparam, state_ptr),
|
||||||
WM_SIZE => handle_size_msg(wparam, lparam, state_ptr),
|
WM_SIZE => handle_size_msg(wparam, lparam, state_ptr),
|
||||||
|
@ -778,21 +778,8 @@ fn handle_calc_client_size(
|
||||||
Some(0)
|
Some(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_activate_msg(
|
fn handle_activate_msg(wparam: WPARAM, state_ptr: Rc<WindowsWindowStatePtr>) -> Option<isize> {
|
||||||
handle: HWND,
|
|
||||||
wparam: WPARAM,
|
|
||||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
|
||||||
) -> Option<isize> {
|
|
||||||
let activated = wparam.loword() > 0;
|
let activated = wparam.loword() > 0;
|
||||||
if state_ptr.hide_title_bar {
|
|
||||||
if let Some(titlebar_rect) = state_ptr.state.borrow().get_titlebar_rect().log_err() {
|
|
||||||
unsafe {
|
|
||||||
InvalidateRect(Some(handle), Some(&titlebar_rect), false)
|
|
||||||
.ok()
|
|
||||||
.log_err()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let this = state_ptr.clone();
|
let this = state_ptr.clone();
|
||||||
state_ptr
|
state_ptr
|
||||||
.executor
|
.executor
|
||||||
|
@ -900,9 +887,6 @@ fn handle_hit_test_msg(
|
||||||
if !state_ptr.is_movable {
|
if !state_ptr.is_movable {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if !state_ptr.hide_title_bar {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
// default handler for resize areas
|
// default handler for resize areas
|
||||||
let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
|
let hit = unsafe { DefWindowProcW(handle, msg, wparam, lparam) };
|
||||||
|
@ -938,20 +922,22 @@ fn handle_hit_test_msg(
|
||||||
return Some(HTTOP as _);
|
return Some(HTTOP as _);
|
||||||
}
|
}
|
||||||
|
|
||||||
let titlebar_rect = state_ptr.state.borrow().get_titlebar_rect();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
if let Ok(titlebar_rect) = titlebar_rect {
|
if let Some(mut callback) = lock.callbacks.hit_test_window_control.take() {
|
||||||
if cursor_point.y < titlebar_rect.bottom {
|
drop(lock);
|
||||||
let caption_btn_width = (state_ptr.state.borrow().caption_button_width().0
|
let area = callback();
|
||||||
* state_ptr.state.borrow().scale_factor) as i32;
|
state_ptr
|
||||||
if cursor_point.x >= titlebar_rect.right - caption_btn_width {
|
.state
|
||||||
return Some(HTCLOSE as _);
|
.borrow_mut()
|
||||||
} else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 2 {
|
.callbacks
|
||||||
return Some(HTMAXBUTTON as _);
|
.hit_test_window_control = Some(callback);
|
||||||
} else if cursor_point.x >= titlebar_rect.right - caption_btn_width * 3 {
|
if let Some(area) = area {
|
||||||
return Some(HTMINBUTTON as _);
|
return match area {
|
||||||
}
|
WindowControlArea::Drag => Some(HTCAPTION as _),
|
||||||
|
WindowControlArea::Close => Some(HTCLOSE as _),
|
||||||
return Some(HTCAPTION as _);
|
WindowControlArea::Max => Some(HTMAXBUTTON as _),
|
||||||
|
WindowControlArea::Min => Some(HTMINBUTTON as _),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,10 +949,6 @@ fn handle_nc_mouse_move_msg(
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||||
) -> Option<isize> {
|
) -> Option<isize> {
|
||||||
if !state_ptr.hide_title_bar {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
start_tracking_mouse(handle, &state_ptr, TME_LEAVE | TME_NONCLIENT);
|
start_tracking_mouse(handle, &state_ptr, TME_LEAVE | TME_NONCLIENT);
|
||||||
|
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
|
@ -997,10 +979,6 @@ fn handle_nc_mouse_down_msg(
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||||
) -> Option<isize> {
|
) -> Option<isize> {
|
||||||
if !state_ptr.hide_title_bar {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
if let Some(mut func) = lock.callbacks.input.take() {
|
if let Some(mut func) = lock.callbacks.input.take() {
|
||||||
let scale_factor = lock.scale_factor;
|
let scale_factor = lock.scale_factor;
|
||||||
|
@ -1052,10 +1030,6 @@ fn handle_nc_mouse_up_msg(
|
||||||
lparam: LPARAM,
|
lparam: LPARAM,
|
||||||
state_ptr: Rc<WindowsWindowStatePtr>,
|
state_ptr: Rc<WindowsWindowStatePtr>,
|
||||||
) -> Option<isize> {
|
) -> Option<isize> {
|
||||||
if !state_ptr.hide_title_bar {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut lock = state_ptr.state.borrow_mut();
|
let mut lock = state_ptr.state.borrow_mut();
|
||||||
if let Some(mut func) = lock.callbacks.input.take() {
|
if let Some(mut func) = lock.callbacks.input.take() {
|
||||||
let scale_factor = lock.scale_factor;
|
let scale_factor = lock.scale_factor;
|
||||||
|
|
|
@ -190,40 +190,6 @@ impl WindowsWindowState {
|
||||||
fn content_size(&self) -> Size<Pixels> {
|
fn content_size(&self) -> Size<Pixels> {
|
||||||
self.logical_size
|
self.logical_size
|
||||||
}
|
}
|
||||||
|
|
||||||
fn title_bar_padding(&self) -> Pixels {
|
|
||||||
// using USER_DEFAULT_SCREEN_DPI because GPUI handles the scale with the scale factor
|
|
||||||
let padding = unsafe { GetSystemMetricsForDpi(SM_CXPADDEDBORDER, USER_DEFAULT_SCREEN_DPI) };
|
|
||||||
px(padding as f32)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title_bar_top_offset(&self) -> Pixels {
|
|
||||||
if self.is_maximized() {
|
|
||||||
self.title_bar_padding() * 2
|
|
||||||
} else {
|
|
||||||
px(0.)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn title_bar_height(&self) -> Pixels {
|
|
||||||
// todo(windows) this is hardcoded to match the ui title bar
|
|
||||||
// in the future the ui title bar component will report the size
|
|
||||||
px(32.) + self.title_bar_top_offset()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn caption_button_width(&self) -> Pixels {
|
|
||||||
// todo(windows) this is hardcoded to match the ui title bar
|
|
||||||
// in the future the ui title bar component will report the size
|
|
||||||
px(36.)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn get_titlebar_rect(&self) -> anyhow::Result<RECT> {
|
|
||||||
let height = self.title_bar_height();
|
|
||||||
let mut rect = RECT::default();
|
|
||||||
unsafe { GetClientRect(self.hwnd, &mut rect) }?;
|
|
||||||
rect.bottom = rect.top + ((height.0 * self.scale_factor).round() as i32);
|
|
||||||
Ok(rect)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowsWindowStatePtr {
|
impl WindowsWindowStatePtr {
|
||||||
|
@ -347,6 +313,7 @@ pub(crate) struct Callbacks {
|
||||||
pub(crate) moved: Option<Box<dyn FnMut()>>,
|
pub(crate) moved: Option<Box<dyn FnMut()>>,
|
||||||
pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
|
pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
|
||||||
pub(crate) close: Option<Box<dyn FnOnce()>>,
|
pub(crate) close: Option<Box<dyn FnOnce()>>,
|
||||||
|
pub(crate) hit_test_window_control: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
|
||||||
pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
|
pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -796,6 +763,10 @@ impl PlatformWindow for WindowsWindow {
|
||||||
self.0.state.borrow_mut().callbacks.close = Some(callback);
|
self.0.state.borrow_mut().callbacks.close = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
|
||||||
|
self.0.state.borrow_mut().callbacks.hit_test_window_control = Some(callback);
|
||||||
|
}
|
||||||
|
|
||||||
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
|
||||||
self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
|
self.0.state.borrow_mut().callbacks.appearance_changed = Some(callback);
|
||||||
}
|
}
|
||||||
|
|
|
@ -418,6 +418,19 @@ pub(crate) struct HitTest {
|
||||||
pub(crate) hover_hitbox_count: usize,
|
pub(crate) hover_hitbox_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A type of window control area that corresponds to the platform window.
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub enum WindowControlArea {
|
||||||
|
/// An area that allows dragging of the platform window.
|
||||||
|
Drag,
|
||||||
|
/// An area that allows closing of the platform window.
|
||||||
|
Close,
|
||||||
|
/// An area that allows maximizing of the platform window.
|
||||||
|
Max,
|
||||||
|
/// An area that allows minimizing of the platform window.
|
||||||
|
Min,
|
||||||
|
}
|
||||||
|
|
||||||
/// An identifier for a [Hitbox] which also includes [HitboxBehavior].
|
/// An identifier for a [Hitbox] which also includes [HitboxBehavior].
|
||||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||||
pub struct HitboxId(u64);
|
pub struct HitboxId(u64);
|
||||||
|
@ -604,6 +617,7 @@ pub(crate) struct Frame {
|
||||||
pub(crate) dispatch_tree: DispatchTree,
|
pub(crate) dispatch_tree: DispatchTree,
|
||||||
pub(crate) scene: Scene,
|
pub(crate) scene: Scene,
|
||||||
pub(crate) hitboxes: Vec<Hitbox>,
|
pub(crate) hitboxes: Vec<Hitbox>,
|
||||||
|
pub(crate) window_control_hitboxes: Vec<(WindowControlArea, Hitbox)>,
|
||||||
pub(crate) deferred_draws: Vec<DeferredDraw>,
|
pub(crate) deferred_draws: Vec<DeferredDraw>,
|
||||||
pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>,
|
pub(crate) input_handlers: Vec<Option<PlatformInputHandler>>,
|
||||||
pub(crate) tooltip_requests: Vec<Option<TooltipRequest>>,
|
pub(crate) tooltip_requests: Vec<Option<TooltipRequest>>,
|
||||||
|
@ -647,6 +661,7 @@ impl Frame {
|
||||||
dispatch_tree,
|
dispatch_tree,
|
||||||
scene: Scene::default(),
|
scene: Scene::default(),
|
||||||
hitboxes: Vec::new(),
|
hitboxes: Vec::new(),
|
||||||
|
window_control_hitboxes: Vec::new(),
|
||||||
deferred_draws: Vec::new(),
|
deferred_draws: Vec::new(),
|
||||||
input_handlers: Vec::new(),
|
input_handlers: Vec::new(),
|
||||||
tooltip_requests: Vec::new(),
|
tooltip_requests: Vec::new(),
|
||||||
|
@ -673,6 +688,7 @@ impl Frame {
|
||||||
self.tooltip_requests.clear();
|
self.tooltip_requests.clear();
|
||||||
self.cursor_styles.clear();
|
self.cursor_styles.clear();
|
||||||
self.hitboxes.clear();
|
self.hitboxes.clear();
|
||||||
|
self.window_control_hitboxes.clear();
|
||||||
self.deferred_draws.clear();
|
self.deferred_draws.clear();
|
||||||
self.focus = None;
|
self.focus = None;
|
||||||
|
|
||||||
|
@ -1013,6 +1029,22 @@ impl Window {
|
||||||
.unwrap_or(DispatchEventResult::default())
|
.unwrap_or(DispatchEventResult::default())
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
platform_window.on_hit_test_window_control({
|
||||||
|
let mut cx = cx.to_async();
|
||||||
|
Box::new(move || {
|
||||||
|
handle
|
||||||
|
.update(&mut cx, |_, window, _cx| {
|
||||||
|
for (area, hitbox) in &window.rendered_frame.window_control_hitboxes {
|
||||||
|
if window.mouse_hit_test.ids.contains(&hitbox.id) {
|
||||||
|
return Some(*area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
})
|
||||||
|
.log_err()
|
||||||
|
.unwrap_or(None)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
if let Some(app_id) = app_id {
|
if let Some(app_id) = app_id {
|
||||||
platform_window.set_app_id(&app_id);
|
platform_window.set_app_id(&app_id);
|
||||||
|
@ -3002,6 +3034,14 @@ impl Window {
|
||||||
hitbox
|
hitbox
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set a hitbox which will act as a control area of the platform window.
|
||||||
|
///
|
||||||
|
/// This method should only be called as part of the paint phase of element drawing.
|
||||||
|
pub fn insert_window_control_hitbox(&mut self, area: WindowControlArea, hitbox: Hitbox) {
|
||||||
|
self.invalidator.debug_assert_paint();
|
||||||
|
self.next_frame.window_control_hitboxes.push((area, hitbox));
|
||||||
|
}
|
||||||
|
|
||||||
/// Sets the key context for the current element. This context will be used to translate
|
/// Sets the key context for the current element. This context will be used to translate
|
||||||
/// keybindings into actions.
|
/// keybindings into actions.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use gpui::{Rgba, WindowAppearance, prelude::*};
|
use gpui::{Rgba, WindowAppearance, WindowControlArea, prelude::*};
|
||||||
|
|
||||||
use ui::prelude::*;
|
use ui::prelude::*;
|
||||||
|
|
||||||
|
@ -118,17 +118,12 @@ impl WindowsCaptionButton {
|
||||||
|
|
||||||
impl RenderOnce for WindowsCaptionButton {
|
impl RenderOnce for WindowsCaptionButton {
|
||||||
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
|
||||||
// todo(windows) report this width to the Windows platform API
|
|
||||||
// NOTE: this is intentionally hard coded. An option to use the 'native' size
|
|
||||||
// could be added when the width is reported to the Windows platform API
|
|
||||||
// as this could change between future Windows versions.
|
|
||||||
let width = px(36.);
|
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.justify_center()
|
.justify_center()
|
||||||
.content_center()
|
.content_center()
|
||||||
.w(width)
|
.occlude()
|
||||||
|
.w(px(36.))
|
||||||
.h_full()
|
.h_full()
|
||||||
.text_size(px(10.0))
|
.text_size(px(10.0))
|
||||||
.hover(|style| style.bg(self.hover_background_color))
|
.hover(|style| style.bg(self.hover_background_color))
|
||||||
|
@ -138,6 +133,17 @@ impl RenderOnce for WindowsCaptionButton {
|
||||||
|
|
||||||
style.bg(active_color)
|
style.bg(active_color)
|
||||||
})
|
})
|
||||||
|
.map(|this| match self.icon {
|
||||||
|
WindowsCaptionButtonIcon::Close => {
|
||||||
|
this.window_control_area(WindowControlArea::Close)
|
||||||
|
}
|
||||||
|
WindowsCaptionButtonIcon::Maximize | WindowsCaptionButtonIcon::Restore => {
|
||||||
|
this.window_control_area(WindowControlArea::Max)
|
||||||
|
}
|
||||||
|
WindowsCaptionButtonIcon::Minimize => {
|
||||||
|
this.window_control_area(WindowControlArea::Min)
|
||||||
|
}
|
||||||
|
})
|
||||||
.child(match self.icon {
|
.child(match self.icon {
|
||||||
WindowsCaptionButtonIcon::Minimize => "\u{e921}",
|
WindowsCaptionButtonIcon::Minimize => "\u{e921}",
|
||||||
WindowsCaptionButtonIcon::Restore => "\u{e923}",
|
WindowsCaptionButtonIcon::Restore => "\u{e923}",
|
||||||
|
|
|
@ -22,7 +22,8 @@ use client::{Client, UserStore};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
Action, AnyElement, App, Context, Corner, Decorations, Element, Entity, InteractiveElement,
|
Action, AnyElement, App, Context, Corner, Decorations, Element, Entity, InteractiveElement,
|
||||||
Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
|
Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
|
||||||
StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, actions, div, px,
|
StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window, WindowControlArea,
|
||||||
|
actions, div, px,
|
||||||
};
|
};
|
||||||
use onboarding_banner::OnboardingBanner;
|
use onboarding_banner::OnboardingBanner;
|
||||||
use project::Project;
|
use project::Project;
|
||||||
|
@ -143,6 +144,7 @@ impl Render for TitleBar {
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id("titlebar")
|
.id("titlebar")
|
||||||
|
.window_control_area(WindowControlArea::Drag)
|
||||||
.w_full()
|
.w_full()
|
||||||
.h(height)
|
.h(height)
|
||||||
.map(|this| {
|
.map(|this| {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue