From ce84a2a67196876dc5d1d76ecb1c0b37cd8cb9c4 Mon Sep 17 00:00:00 2001 From: Dzmitry Malyshau Date: Thu, 1 Feb 2024 00:01:50 -0800 Subject: [PATCH] linux: refactor window structure, support move callback --- crates/gpui/examples/hello_world.rs | 5 +- crates/gpui/src/platform/linux/platform.rs | 55 +++-- crates/gpui/src/platform/linux/window.rs | 227 +++++++++++++-------- 3 files changed, 171 insertions(+), 116 deletions(-) diff --git a/crates/gpui/examples/hello_world.rs b/crates/gpui/examples/hello_world.rs index 736fd14450..d0578e6681 100644 --- a/crates/gpui/examples/hello_world.rs +++ b/crates/gpui/examples/hello_world.rs @@ -9,9 +9,12 @@ impl Render for HelloWorld { div() .flex() .bg(rgb(0x2e7d32)) - .size_full() + .size(Length::Definite(Pixels(300.0).into())) .justify_center() .items_center() + .shadow_lg() + .border() + .border_color(rgb(0x0000ff)) .text_xl() .text_color(rgb(0xffffff)) .child(format!("Hello, {}!", &self.text)) diff --git a/crates/gpui/src/platform/linux/platform.rs b/crates/gpui/src/platform/linux/platform.rs index 398d741ff3..f5f251f1ef 100644 --- a/crates/gpui/src/platform/linux/platform.rs +++ b/crates/gpui/src/platform/linux/platform.rs @@ -1,11 +1,10 @@ #![allow(unused)] use crate::{ - Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, + Action, AnyWindowHandle, BackgroundExecutor, Bounds, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor, Keymap, LinuxDispatcher, LinuxDisplay, LinuxTextSystem, LinuxWindow, - LinuxWindowState, LinuxWindowStatePtr, Menu, PathPromptOptions, Platform, PlatformDisplay, - PlatformInput, PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, - WindowOptions, + LinuxWindowState, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, + PlatformTextSystem, PlatformWindow, Point, Result, SemanticVersion, Size, Task, WindowOptions, }; use collections::{HashMap, HashSet}; @@ -35,12 +34,12 @@ xcb::atoms_struct! { pub(crate) struct LinuxPlatform(Mutex); pub(crate) struct LinuxPlatformState { - xcb_connection: xcb::Connection, + xcb_connection: Arc, x_root_index: i32, atoms: XcbAtoms, background_executor: BackgroundExecutor, foreground_executor: ForegroundExecutor, - windows: HashMap, + windows: HashMap>, text_system: Arc, } @@ -58,7 +57,7 @@ impl LinuxPlatform { let dispatcher = Arc::new(LinuxDispatcher::new()); Self(Mutex::new(LinuxPlatformState { - xcb_connection, + xcb_connection: Arc::new(xcb_connection), x_root_index, atoms, background_executor: BackgroundExecutor::new(dispatcher.clone()), @@ -87,44 +86,38 @@ impl Platform for LinuxPlatform { while !self.0.lock().windows.is_empty() { let event = self.0.lock().xcb_connection.wait_for_event().unwrap(); - let mut repaint_x_window = None; match event { xcb::Event::X(x::Event::ClientMessage(ev)) => { if let x::ClientMessageData::Data32([atom, ..]) = ev.data() { let mut this = self.0.lock(); if atom == this.atoms.wm_del_window.resource_id() { // window "x" button clicked by user, we gracefully exit - { - let mut window = this.windows[&ev.window()].lock(); - window.destroy(); - } - this.xcb_connection.send_request(&x::UnmapWindow { - window: ev.window(), - }); - this.xcb_connection.send_request(&x::DestroyWindow { - window: ev.window(), - }); - this.windows.remove(&ev.window()); + let window = this.windows.remove(&ev.window()).unwrap(); + window.destroy(); break; } } } xcb::Event::X(x::Event::Expose(ev)) => { - repaint_x_window = Some(ev.window()); + let this = self.0.lock(); + this.windows[&ev.window()].expose(); } xcb::Event::X(x::Event::ConfigureNotify(ev)) => { + let bounds = Bounds { + origin: Point { + x: ev.x().into(), + y: ev.y().into(), + }, + size: Size { + width: ev.width().into(), + height: ev.height().into(), + }, + }; let this = self.0.lock(); - LinuxWindowState::resize(&this.windows[&ev.window()], ev.width(), ev.height()); - this.xcb_connection.flush(); + this.windows[&ev.window()].configure(bounds); } _ => {} } - - if let Some(x_window) = repaint_x_window { - let this = self.0.lock(); - LinuxWindowState::request_frame(&this.windows[&x_window]); - this.xcb_connection.flush(); - } } } @@ -173,14 +166,14 @@ impl Platform for LinuxPlatform { let mut this = self.0.lock(); let x_window = this.xcb_connection.generate_id(); - let window_ptr = LinuxWindowState::new_ptr( + let window_ptr = Arc::new(LinuxWindowState::new( options, &this.xcb_connection, this.x_root_index, x_window, &this.atoms, - ); - this.windows.insert(x_window, window_ptr.clone()); + )); + this.windows.insert(x_window, Arc::clone(&window_ptr)); Box::new(LinuxWindow(window_ptr)) } diff --git a/crates/gpui/src/platform/linux/window.rs b/crates/gpui/src/platform/linux/window.rs index 0f0916b0db..71c80df1ba 100644 --- a/crates/gpui/src/platform/linux/window.rs +++ b/crates/gpui/src/platform/linux/window.rs @@ -1,12 +1,13 @@ use super::BladeRenderer; use crate::{ - AnyWindowHandle, BladeAtlas, LinuxDisplay, Pixels, PlatformDisplay, PlatformInputHandler, + BladeAtlas, Bounds, GlobalPixels, LinuxDisplay, Pixels, PlatformDisplay, PlatformInputHandler, PlatformWindow, Point, Size, WindowAppearance, WindowBounds, WindowOptions, XcbAtoms, }; use blade_graphics as gpu; use parking_lot::Mutex; use std::{ ffi::c_void, + mem, rc::Rc, sync::{self, Arc}, }; @@ -15,24 +16,52 @@ use xcb::{x, Xid as _}; #[derive(Default)] struct Callbacks { request_frame: Option>, + input: Option bool>>, + active_status_change: Option>, resize: Option, f32)>>, + fullscreen: Option>, moved: Option>, + should_close: Option bool>>, + close: Option>, + appearance_changed: Option>, +} + +struct LinuxWindowInner { + bounds: Bounds, + title_height: i32, + border_width: i32, + scale_factor: f32, + renderer: BladeRenderer, +} + +impl LinuxWindowInner { + fn render_extent(&self) -> gpu::Extent { + gpu::Extent { + width: (self.bounds.size.width - 2 * self.border_width) as u32, + height: (self.bounds.size.height - 2 * self.border_width - self.title_height) as u32, + depth: 1, + } + } + fn content_size(&self) -> Size { + let extent = self.render_extent(); + Size { + width: extent.width.into(), + height: extent.height.into(), + } + } } pub(crate) struct LinuxWindowState { + xcb_connection: Arc, display: Rc, x_window: x::Window, - window_bounds: WindowBounds, - content_size: Size, + callbacks: Mutex, + inner: Mutex, sprite_atlas: Arc, - renderer: BladeRenderer, - //TODO: move out into a separate struct - callbacks: Callbacks, } -pub(crate) type LinuxWindowStatePtr = Arc>; #[derive(Clone)] -pub(crate) struct LinuxWindow(pub(crate) LinuxWindowStatePtr); +pub(crate) struct LinuxWindow(pub(crate) Arc); struct RawWindow { connection: *mut c_void, @@ -58,13 +87,13 @@ unsafe impl raw_window_handle::HasRawDisplayHandle for RawWindow { } impl LinuxWindowState { - pub fn new_ptr( + pub fn new( options: WindowOptions, - xcb_connection: &xcb::Connection, + xcb_connection: &Arc, x_main_screen_index: i32, x_window: x::Window, atoms: &XcbAtoms, - ) -> LinuxWindowStatePtr { + ) -> Self { let x_screen_index = options .display_id .map_or(x_main_screen_index, |did| did.0 as i32); @@ -81,27 +110,27 @@ impl LinuxWindowState { ), ]; - let (bound_x, bound_y, bound_width, bound_height) = match options.bounds { - WindowBounds::Fullscreen | WindowBounds::Maximized => { - (0, 0, screen.width_in_pixels(), screen.height_in_pixels()) - } - WindowBounds::Fixed(bounds) => ( - bounds.origin.x.0 as i16, - bounds.origin.y.0 as i16, - bounds.size.width.0 as u16, - bounds.size.height.0 as u16, - ), + let bounds = match options.bounds { + WindowBounds::Fullscreen | WindowBounds::Maximized => Bounds { + origin: Point::default(), + size: Size { + width: screen.width_in_pixels() as i32, + height: screen.height_in_pixels() as i32, + }, + }, + WindowBounds::Fixed(bounds) => bounds.map(|p| p.0 as i32), }; + let border_width = 0i32; xcb_connection.send_request(&x::CreateWindow { depth: x::COPY_FROM_PARENT as u8, wid: x_window, parent: screen.root(), - x: bound_x, - y: bound_y, - width: bound_width, - height: bound_height, - border_width: 0, + x: bounds.origin.x as i16, + y: bounds.origin.y as i16, + width: bounds.size.width as u16, + height: bounds.size.height as u16, + border_width: border_width as u16, class: x::WindowClass::InputOutput, visual: screen.root_visual(), value_list: &xcb_values, @@ -151,76 +180,93 @@ impl LinuxWindowState { } .unwrap(), ); + let gpu_extent = gpu::Extent { - width: bound_width as u32, - height: bound_height as u32, + width: bounds.size.width as u32, + height: bounds.size.height as u32, depth: 1, }; + let sprite_atlas = Arc::new(BladeAtlas::new(&gpu)); - Arc::new(Mutex::new(Self { + Self { + xcb_connection: Arc::clone(xcb_connection), display: Rc::new(LinuxDisplay::new(xcb_connection, x_screen_index)), x_window, - window_bounds: options.bounds, - content_size: Size { - width: Pixels(bound_width as f32), - height: Pixels(bound_height as f32), - }, - sprite_atlas: Arc::new(BladeAtlas::new(&gpu)), - renderer: BladeRenderer::new(gpu, gpu_extent), - callbacks: Callbacks::default(), - })) + callbacks: Mutex::new(Callbacks::default()), + inner: Mutex::new(LinuxWindowInner { + bounds, + title_height: 0, //TODO + border_width, + scale_factor: 1.0, + renderer: BladeRenderer::new(gpu, gpu_extent), + }), + sprite_atlas, + } } - pub fn destroy(&mut self) { + pub fn destroy(&self) { self.sprite_atlas.destroy(); - self.renderer.destroy(); - } - - pub fn resize(self_ptr: &LinuxWindowStatePtr, width: u16, height: u16) { - let content_size = Size { - width: Pixels(width as f32), - height: Pixels(height as f32), - }; - - let mut fun = match self_ptr.lock().callbacks.resize.take() { - Some(fun) => fun, - None => return, - }; - fun(content_size, 1.0); - - let mut this = self_ptr.lock(); - this.callbacks.resize = Some(fun); - this.content_size = content_size; - this.renderer.resize(gpu::Extent { - width: width as u32, - height: height as u32, - depth: 1, + { + let mut inner = self.inner.lock(); + inner.renderer.destroy(); + } + self.xcb_connection.send_request(&x::UnmapWindow { + window: self.x_window, }); + self.xcb_connection.send_request(&x::DestroyWindow { + window: self.x_window, + }); + if let Some(fun) = self.callbacks.lock().close.take() { + fun(); + } } - pub fn request_frame(self_ptr: &LinuxWindowStatePtr) { - let mut fun = match self_ptr.lock().callbacks.request_frame.take() { - Some(fun) => fun, - None => return, - }; - fun(); + pub fn expose(&self) { + let mut cb = self.callbacks.lock(); + if let Some(ref mut fun) = cb.request_frame { + fun(); + } + } - self_ptr.lock().callbacks.request_frame = Some(fun); + pub fn configure(&self, bounds: Bounds) { + let mut resize_args = None; + let mut do_move = false; + { + let mut inner = self.inner.lock(); + let old_bounds = mem::replace(&mut inner.bounds, bounds); + do_move = old_bounds.origin != bounds.origin; + if old_bounds.size != bounds.size { + let extent = inner.render_extent(); + inner.renderer.resize(extent); + resize_args = Some((inner.content_size(), inner.scale_factor)); + } + } + + let mut callbacks = self.callbacks.lock(); + if let Some((content_size, scale_factor)) = resize_args { + if let Some(ref mut fun) = callbacks.resize { + fun(content_size, scale_factor) + } + } + if do_move { + if let Some(ref mut fun) = callbacks.moved { + fun() + } + } } } impl PlatformWindow for LinuxWindow { fn bounds(&self) -> WindowBounds { - //TODO: update when window moves - self.0.lock().window_bounds + WindowBounds::Fixed(self.0.inner.lock().bounds.map(|v| GlobalPixels(v as f32))) } fn content_size(&self) -> Size { - self.0.lock().content_size + self.0.inner.lock().content_size() } fn scale_factor(&self) -> f32 { - 1.0 + self.0.inner.lock().scale_factor } fn titlebar_height(&self) -> Pixels { @@ -232,7 +278,7 @@ impl PlatformWindow for LinuxWindow { } fn display(&self) -> Rc { - Rc::clone(&self.0.lock().display) + Rc::clone(&self.0.display) } fn mouse_position(&self) -> Point { @@ -286,28 +332,40 @@ impl PlatformWindow for LinuxWindow { } fn on_request_frame(&self, callback: Box) { - self.0.lock().callbacks.request_frame = Some(callback); + self.0.callbacks.lock().request_frame = Some(callback); } - fn on_input(&self, callback: Box bool>) {} + fn on_input(&self, callback: Box bool>) { + self.0.callbacks.lock().input = Some(callback); + } - fn on_active_status_change(&self, callback: Box) {} + fn on_active_status_change(&self, callback: Box) { + self.0.callbacks.lock().active_status_change = Some(callback); + } fn on_resize(&self, callback: Box, f32)>) { - self.0.lock().callbacks.resize = Some(callback); + self.0.callbacks.lock().resize = Some(callback); } - fn on_fullscreen(&self, _callback: Box) {} + fn on_fullscreen(&self, callback: Box) { + self.0.callbacks.lock().fullscreen = Some(callback); + } fn on_moved(&self, callback: Box) { - self.0.lock().callbacks.moved = Some(callback); + self.0.callbacks.lock().moved = Some(callback); } - fn on_should_close(&self, _callback: Box bool>) {} + fn on_should_close(&self, callback: Box bool>) { + self.0.callbacks.lock().should_close = Some(callback); + } - fn on_close(&self, _callback: Box) {} + fn on_close(&self, callback: Box) { + self.0.callbacks.lock().close = Some(callback); + } - fn on_appearance_changed(&self, _callback: Box) {} + fn on_appearance_changed(&self, callback: Box) { + self.0.callbacks.lock().appearance_changed = Some(callback); + } fn is_topmost_for_position(&self, _position: crate::Point) -> bool { unimplemented!() @@ -316,10 +374,11 @@ impl PlatformWindow for LinuxWindow { fn invalidate(&self) {} fn draw(&self, scene: &crate::Scene) { - self.0.lock().renderer.draw(scene); + let mut inner = self.0.inner.lock(); + inner.renderer.draw(scene); } fn sprite_atlas(&self) -> sync::Arc { - self.0.lock().sprite_atlas.clone() + self.0.sprite_atlas.clone() } }