Skip to content

Commit

Permalink
wayland: Window controls and drag (#11525)
Browse files Browse the repository at this point in the history
Based on #11046

- Partially fixes #10346 
- Fixes #9964

## Features
Window buttons

![image](https://github.com/zed-industries/zed/assets/71973804/1b7e0504-3925-45ba-90b5-5adb55e0d739)

Window drag

![image](https://github.com/zed-industries/zed/assets/71973804/9c509a37-e5a5-484c-9f80-c722aeee4380)

Native window context menu

![image](https://github.com/zed-industries/zed/assets/71973804/048ecf52-e277-49bb-a106-91cad226fd8a)

### Limitations

- No resizing
- Wayland only (though X11 always has window decorations)

### Technical

This PR adds three APIs to gpui.

1. `show_window_menu`: Triggers the native title bar context menu.
2. `start_system_move`: Tells the compositor to start dragging the
window.
3. `should_render_window_controls`: Whether the compositor doesn't
support server side decorations.

These APIs have only been implemented for Wayland, but they should be
portable to other platforms.

Release Notes:

- N/A

---------

Co-authored-by: Akilan Elango <akilan1997@gmail.com>
  • Loading branch information
apricotbucket28 and aki237 committed May 14, 2024
1 parent db89353 commit d1ee2d0
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 11 deletions.
7 changes: 5 additions & 2 deletions crates/collab_ui/src/collab_titlebar_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ impl Render for CollabTitlebarItem {
let project_id = self.project.read(cx).remote_id();
let workspace = self.workspace.upgrade();

TitleBar::new("collab-titlebar")
TitleBar::new("collab-titlebar", Box::new(workspace::CloseWindow))
// note: on windows titlebar behaviour is handled by the platform implementation
.when(cfg!(not(windows)), |this| {
this.on_click(|event, cx| {
Expand All @@ -73,7 +73,8 @@ impl Render for CollabTitlebarItem {
.gap_1()
.children(self.render_project_host(cx))
.child(self.render_project_name(cx))
.children(self.render_project_branch(cx)),
.children(self.render_project_branch(cx))
.on_mouse_move(|_, cx| cx.stop_propagation()),
)
.child(
h_flex()
Expand Down Expand Up @@ -105,6 +106,7 @@ impl Render for CollabTitlebarItem {

this.children(current_user_face_pile.map(|face_pile| {
v_flex()
.on_mouse_move(|_, cx| cx.stop_propagation())
.child(face_pile)
.child(render_color_ribbon(player_colors.local().cursor))
}))
Expand Down Expand Up @@ -167,6 +169,7 @@ impl Render for CollabTitlebarItem {
h_flex()
.gap_1()
.pr_1()
.on_mouse_move(|_, cx| cx.stop_propagation())
.when_some(room, |this, room| {
let room = room.read(cx);
let project = self.project.read(cx);
Expand Down
4 changes: 4 additions & 0 deletions crates/gpui/src/platform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,10 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
#[cfg(target_os = "windows")]
fn get_raw_handle(&self) -> windows::HWND;

fn show_window_menu(&self, position: Point<Pixels>);
fn start_system_move(&self);
fn should_render_window_controls(&self) -> bool;

#[cfg(any(test, feature = "test-support"))]
fn as_test(&mut self) -> Option<&mut TestWindow> {
None
Expand Down
17 changes: 16 additions & 1 deletion crates/gpui/src/platform/linux/wayland/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ use wayland_client::globals::{registry_queue_init, GlobalList, GlobalListContent
use wayland_client::protocol::wl_callback::{self, WlCallback};
use wayland_client::protocol::wl_data_device_manager::DndAction;
use wayland_client::protocol::wl_pointer::{AxisRelativeDirection, AxisSource};
use wayland_client::protocol::wl_seat::WlSeat;
use wayland_client::protocol::{
wl_data_device, wl_data_device_manager, wl_data_offer, wl_data_source, wl_output, wl_region,
};
Expand Down Expand Up @@ -80,6 +81,7 @@ pub struct Globals {
pub data_device_manager: Option<wl_data_device_manager::WlDataDeviceManager>,
pub wm_base: xdg_wm_base::XdgWmBase,
pub shm: wl_shm::WlShm,
pub seat: wl_seat::WlSeat,
pub viewporter: Option<wp_viewporter::WpViewporter>,
pub fractional_scale_manager:
Option<wp_fractional_scale_manager_v1::WpFractionalScaleManagerV1>,
Expand All @@ -93,6 +95,7 @@ impl Globals {
globals: GlobalList,
executor: ForegroundExecutor,
qh: QueueHandle<WaylandClientStatePtr>,
seat: wl_seat::WlSeat,
) -> Self {
Globals {
activation: globals.bind(&qh, 1..=1, ()).ok(),
Expand All @@ -113,6 +116,7 @@ impl Globals {
)
.ok(),
shm: globals.bind(&qh, 1..=1, ()).unwrap(),
seat,
wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
Expand Down Expand Up @@ -193,6 +197,10 @@ impl WaylandClientStatePtr {
.expect("The pointer should always be valid when dispatching in wayland")
}

pub fn get_serial(&self, kind: SerialKind) -> u32 {
self.0.upgrade().unwrap().borrow().serial_tracker.get(kind)
}

pub fn drop_window(&self, surface_id: &ObjectId) {
let mut client = self.get_client();
let mut state = client.borrow_mut();
Expand Down Expand Up @@ -303,7 +311,12 @@ impl WaylandClient {
});

let seat = seat.unwrap();
let globals = Globals::new(globals, common.foreground_executor.clone(), qh.clone());
let globals = Globals::new(
globals,
common.foreground_executor.clone(),
qh.clone(),
seat.clone(),
);

let data_device = globals
.data_device_manager
Expand Down Expand Up @@ -962,6 +975,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
} => {
state.serial_tracker.update(SerialKind::MouseEnter, serial);
state.mouse_location = Some(point(px(surface_x as f32), px(surface_y as f32)));
state.button_pressed = None;

if let Some(window) = get_window(&mut state, &surface.id()) {
state.mouse_focused_window = Some(window.clone());
Expand Down Expand Up @@ -990,6 +1004,7 @@ impl Dispatch<wl_pointer::WlPointer, ()> for WaylandClientStatePtr {
});
state.mouse_focused_window = None;
state.mouse_location = None;
state.button_pressed = None;

drop(state);
focused_window.handle_input(input);
Expand Down
26 changes: 24 additions & 2 deletions crates/gpui/src/platform/linux/wayland/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,13 @@ use wayland_protocols_plasma::blur::client::{org_kde_kwin_blur, org_kde_kwin_blu

use crate::platform::blade::{BladeRenderer, BladeSurfaceConfig};
use crate::platform::linux::wayland::display::WaylandDisplay;
use crate::platform::linux::wayland::serial::SerialKind;
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
use crate::scene::Scene;
use crate::{
px, size, Bounds, DevicePixels, Globals, Modifiers, Pixels, PlatformDisplay, PlatformInput,
Point, PromptLevel, Size, WaylandClientState, WaylandClientStatePtr, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowParams,
Point, PromptLevel, Size, WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance,
WindowBounds, WindowParams,
};

#[derive(Default)]
Expand Down Expand Up @@ -753,6 +754,27 @@ impl PlatformWindow for WaylandWindow {
let state = self.borrow();
state.renderer.sprite_atlas().clone()
}

fn show_window_menu(&self, position: Point<Pixels>) {
let state = self.borrow();
let serial = state.client.get_serial(SerialKind::MousePress);
state.toplevel.show_window_menu(
&state.globals.seat,
serial,
position.x.0 as i32,
position.y.0 as i32,
);
}

fn start_system_move(&self) {
let state = self.borrow();
let serial = state.client.get_serial(SerialKind::MousePress);
state.toplevel._move(&state.globals.seat, serial);
}

fn should_render_window_controls(&self) -> bool {
self.borrow().decoration_state == WaylandDecorationState::Client
}
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
Expand Down
11 changes: 11 additions & 0 deletions crates/gpui/src/platform/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{
Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowOptions,
WindowParams, X11Client, X11ClientState, X11ClientStatePtr,
};

use blade_graphics as gpu;
use parking_lot::Mutex;
use raw_window_handle as rwh;
Expand Down Expand Up @@ -719,4 +720,14 @@ impl PlatformWindow for X11Window {
let inner = self.0.state.borrow();
inner.renderer.sprite_atlas().clone()
}

// todo(linux)
fn show_window_menu(&self, _position: Point<Pixels>) {}

// todo(linux)
fn start_system_move(&self) {}

fn should_render_window_controls(&self) -> bool {
false
}
}
8 changes: 8 additions & 0 deletions crates/gpui/src/platform/mac/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1100,6 +1100,14 @@ impl PlatformWindow for MacWindow {
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
self.0.lock().renderer.sprite_atlas().clone()
}

fn show_window_menu(&self, _position: Point<Pixels>) {}

fn start_system_move(&self) {}

fn should_render_window_controls(&self) -> bool {
false
}
}

impl rwh::HasWindowHandle for MacWindow {
Expand Down
12 changes: 12 additions & 0 deletions crates/gpui/src/platform/test/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,18 @@ impl PlatformWindow for TestWindow {
fn get_raw_handle(&self) -> windows::Win32::Foundation::HWND {
unimplemented!()
}

fn show_window_menu(&self, _position: Point<Pixels>) {
unimplemented!()
}

fn start_system_move(&self) {
unimplemented!()
}

fn should_render_window_controls(&self) -> bool {
false
}
}

pub(crate) struct TestAtlasState {
Expand Down
8 changes: 8 additions & 0 deletions crates/gpui/src/platform/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,14 @@ impl PlatformWindow for WindowsWindow {
fn get_raw_handle(&self) -> HWND {
self.0.hwnd
}

fn show_window_menu(&self, _position: Point<Pixels>) {}

fn start_system_move(&self) {}

fn should_render_window_controls(&self) -> bool {
false
}
}

#[implement(IDropTarget)]
Expand Down
17 changes: 17 additions & 0 deletions crates/gpui/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1143,6 +1143,23 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.zoom();
}

/// Opens the native title bar context menu, useful when implementing client side decorations (Wayland only)
pub fn show_window_menu(&self, position: Point<Pixels>) {
self.window.platform_window.show_window_menu(position)
}

/// Tells the compositor to take control of window movement (Wayland only)
///
/// Events may not be received during a move operation.
pub fn start_system_move(&self) {
self.window.platform_window.start_system_move()
}

/// Returns whether the title bar window controls need to be rendered by the application (Wayland and X11)
pub fn should_render_window_controls(&self) -> bool {
self.window.platform_window.should_render_window_controls()
}

/// Updates the window's title at the platform level.
pub fn set_window_title(&mut self, title: &str) {
self.window.platform_window.set_title(title);
Expand Down
8 changes: 4 additions & 4 deletions crates/ui/src/components/stories/title_bar.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use gpui::Render;
use gpui::{NoAction, Render};
use story::{StoryContainer, StoryItem, StorySection};

use crate::{prelude::*, PlatformStyle, TitleBar};
Expand All @@ -19,7 +19,7 @@ impl Render for TitleBarStory {
StorySection::new().child(
StoryItem::new(
"Default (macOS)",
TitleBar::new("macos")
TitleBar::new("macos", Box::new(NoAction))
.platform_style(PlatformStyle::Mac)
.map(add_sample_children),
)
Expand All @@ -31,7 +31,7 @@ impl Render for TitleBarStory {
StorySection::new().child(
StoryItem::new(
"Default (Linux)",
TitleBar::new("linux")
TitleBar::new("linux", Box::new(NoAction))
.platform_style(PlatformStyle::Linux)
.map(add_sample_children),
)
Expand All @@ -43,7 +43,7 @@ impl Render for TitleBarStory {
StorySection::new().child(
StoryItem::new(
"Default (Windows)",
TitleBar::new("windows")
TitleBar::new("windows", Box::new(NoAction))
.platform_style(PlatformStyle::Windows)
.map(add_sample_children),
)
Expand Down
1 change: 1 addition & 0 deletions crates/ui/src/components/title_bar.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
mod linux_window_controls;
mod title_bar;
mod windows_window_controls;

Expand Down

0 comments on commit d1ee2d0

Please sign in to comment.