Linux window decorations (#13611)

This PR adds support for full client side decorations on X11 and Wayland

TODO:
- [x] Adjust GPUI APIs to expose CSD related information
- [x] Implement remaining CSD features (Resizing, window border, window
shadow)
- [x] Integrate with existing background appearance and window
transparency
- [x] Figure out how to check if the window is tiled on X11
- [x] Implement in Zed
- [x] Repeatedly maximizing and unmaximizing can panic
- [x] Resizing is strangely slow
- [x] X11 resizing and movement doesn't work for this:
https://discord.com/channels/869392257814519848/1204679850208657418/1256816908519604305
- [x] The top corner can clip with current styling
- [x] Pressing titlebar buttons doesn't work
- [x] Not showing maximize / unmaximize buttons
- [x] Noisy transparency logs / surface transparency problem
https://github.com/zed-industries/zed/pull/13611#issuecomment-2201685030
- [x] Strange offsets when dragging the project panel
https://github.com/zed-industries/zed/pull/13611#pullrequestreview-2154606261
- [x] Shadow inset with `_GTK_FRAME_EXTENTS` doesn't respect tiling on
X11 (observe by snapping an X11 window in any direction)

Release Notes:

- N/A

---------

Co-authored-by: conrad <conrad@zed.dev>
Co-authored-by: Owen Law <81528246+someone13574@users.noreply.github.com>
Co-authored-by: apricotbucket28 <71973804+apricotbucket28@users.noreply.github.com>
Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>
This commit is contained in:
Mikayla Maki
2024-07-03 11:28:09 -07:00
committed by GitHub
co-authored by conrad Owen Law apricotbucket28 Conrad Irwin
parent 98699a65c1
commit 47aa761ca9
28 changed files with 1633 additions and 540 deletions
+10
View File
@@ -309,6 +309,16 @@ pub fn transparent_black() -> Hsla {
}
}
/// Transparent black in [`Hsla`]
pub fn transparent_white() -> Hsla {
Hsla {
h: 0.,
s: 0.,
l: 1.,
a: 0.,
}
}
/// Opaque grey in [`Hsla`], values will be clamped to the range [0, 1]
pub fn opaque_grey(lightness: f32, opacity: f32) -> Hsla {
Hsla {
+34 -2
View File
@@ -883,6 +883,14 @@ where
self.size.height = self.size.height.clone() + double_amount;
}
/// inset the bounds by a specified amount
/// Note that this may panic if T does not support negative values
pub fn inset(&self, amount: T) -> Self {
let mut result = self.clone();
result.dilate(T::default() - amount);
result
}
/// Returns the center point of the bounds.
///
/// Calculates the center by taking the origin's x and y coordinates and adding half the width and height
@@ -1266,12 +1274,36 @@ where
/// size: Size { width: 10.0, height: 20.0 },
/// });
/// ```
pub fn map_origin(self, f: impl Fn(Point<T>) -> Point<T>) -> Bounds<T> {
pub fn map_origin(self, f: impl Fn(T) -> T) -> Bounds<T> {
Bounds {
origin: f(self.origin),
origin: self.origin.map(f),
size: self.size,
}
}
/// Applies a function to the origin of the bounds, producing a new `Bounds` with the new origin
///
/// # Examples
///
/// ```
/// # use zed::{Bounds, Point, Size};
/// let bounds = Bounds {
/// origin: Point { x: 10.0, y: 10.0 },
/// size: Size { width: 10.0, height: 20.0 },
/// };
/// let new_bounds = bounds.map_size(|value| value * 1.5);
///
/// assert_eq!(new_bounds, Bounds {
/// origin: Point { x: 10.0, y: 10.0 },
/// size: Size { width: 15.0, height: 30.0 },
/// });
/// ```
pub fn map_size(self, f: impl Fn(T) -> T) -> Bounds<T> {
Bounds {
origin: self.origin,
size: self.size.map(f),
}
}
}
/// Checks if the bounds represent an empty area.
+114 -10
View File
@@ -210,6 +210,83 @@ impl Debug for DisplayId {
unsafe impl Send for DisplayId {}
/// Which part of the window to resize
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResizeEdge {
/// The top edge
Top,
/// The top right corner
TopRight,
/// The right edge
Right,
/// The bottom right corner
BottomRight,
/// The bottom edge
Bottom,
/// The bottom left corner
BottomLeft,
/// The left edge
Left,
/// The top left corner
TopLeft,
}
/// A type to describe the appearance of a window
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum WindowDecorations {
#[default]
/// Server side decorations
Server,
/// Client side decorations
Client,
}
/// A type to describe how this window is currently configured
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub enum Decorations {
/// The window is configured to use server side decorations
#[default]
Server,
/// The window is configured to use client side decorations
Client {
/// The edge tiling state
tiling: Tiling,
},
}
/// What window controls this platform supports
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub struct WindowControls {
/// Whether this platform supports fullscreen
pub fullscreen: bool,
/// Whether this platform supports maximize
pub maximize: bool,
/// Whether this platform supports minimize
pub minimize: bool,
/// Whether this platform supports a window menu
pub window_menu: bool,
}
/// A type to describe which sides of the window are currently tiled in some way
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Default)]
pub struct Tiling {
/// Whether the top edge is tiled
pub top: bool,
/// Whether the left edge is tiled
pub left: bool,
/// Whether the right edge is tiled
pub right: bool,
/// Whether the bottom edge is tiled
pub bottom: bool,
}
impl Tiling {
/// Whether any edge is tiled
pub fn is_tiled(&self) -> bool {
self.top || self.left || self.right || self.bottom
}
}
pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn bounds(&self) -> Bounds<Pixels>;
fn is_maximized(&self) -> bool;
@@ -232,10 +309,7 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn activate(&self);
fn is_active(&self) -> bool;
fn set_title(&mut self, title: &str);
fn set_app_id(&mut self, app_id: &str);
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance);
fn set_edited(&mut self, edited: bool);
fn show_character_palette(&self);
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance);
fn minimize(&self);
fn zoom(&self);
fn toggle_fullscreen(&self);
@@ -252,12 +326,31 @@ pub(crate) trait PlatformWindow: HasWindowHandle + HasDisplayHandle {
fn completed_frame(&self) {}
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas>;
// macOS specific methods
fn set_edited(&mut self, _edited: bool) {}
fn show_character_palette(&self) {}
#[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;
// Linux specific methods
fn request_decorations(&self, _decorations: WindowDecorations) {}
fn show_window_menu(&self, _position: Point<Pixels>) {}
fn start_window_move(&self) {}
fn start_window_resize(&self, _edge: ResizeEdge) {}
fn window_decorations(&self) -> Decorations {
Decorations::Server
}
fn set_app_id(&mut self, _app_id: &str) {}
fn window_controls(&self) -> WindowControls {
WindowControls {
fullscreen: true,
maximize: true,
minimize: true,
window_menu: false,
}
}
fn set_client_inset(&self, _inset: Pixels) {}
#[cfg(any(test, feature = "test-support"))]
fn as_test(&mut self) -> Option<&mut TestWindow> {
@@ -570,6 +663,10 @@ pub struct WindowOptions {
/// Window minimum size
pub window_min_size: Option<Size<Pixels>>,
/// Whether to use client or server side decorations. Wayland only
/// Note that this may be ignored.
pub window_decorations: Option<WindowDecorations>,
}
/// The variables that can be configured when creating a new window
@@ -596,8 +693,6 @@ pub(crate) struct WindowParams {
pub display_id: Option<DisplayId>,
pub window_background: WindowBackgroundAppearance,
#[cfg_attr(target_os = "linux", allow(dead_code))]
pub window_min_size: Option<Size<Pixels>>,
}
@@ -649,6 +744,7 @@ impl Default for WindowOptions {
window_background: WindowBackgroundAppearance::default(),
app_id: None,
window_min_size: None,
window_decorations: None,
}
}
}
@@ -659,7 +755,7 @@ pub struct TitlebarOptions {
/// The initial title of the window
pub title: Option<SharedString>,
/// Whether the titlebar should appear transparent
/// Whether the titlebar should appear transparent (macOS only)
pub appears_transparent: bool,
/// The position of the macOS traffic light buttons
@@ -805,6 +901,14 @@ pub enum CursorStyle {
/// corresponds to the CSS cursor value `ns-resize`
ResizeUpDown,
/// A resize cursor directing up-left and down-right
/// corresponds to the CSS cursor value `nesw-resize`
ResizeUpLeftDownRight,
/// A resize cursor directing up-right and down-left
/// corresponds to the CSS cursor value `nwse-resize`
ResizeUpRightDownLeft,
/// A cursor indicating that the item/column can be resized horizontally.
/// corresponds to the CSS curosr value `col-resize`
ResizeColumn,
@@ -572,6 +572,8 @@ impl CursorStyle {
CursorStyle::ResizeUp => Shape::NResize,
CursorStyle::ResizeDown => Shape::SResize,
CursorStyle::ResizeUpDown => Shape::NsResize,
CursorStyle::ResizeUpLeftDownRight => Shape::NwseResize,
CursorStyle::ResizeUpRightDownLeft => Shape::NeswResize,
CursorStyle::ResizeColumn => Shape::ColResize,
CursorStyle::ResizeRow => Shape::RowResize,
CursorStyle::IBeamCursorForVerticalLayout => Shape::VerticalText,
@@ -599,6 +601,8 @@ impl CursorStyle {
CursorStyle::ResizeUp => "n-resize",
CursorStyle::ResizeDown => "s-resize",
CursorStyle::ResizeUpDown => "ns-resize",
CursorStyle::ResizeUpLeftDownRight => "nwse-resize",
CursorStyle::ResizeUpRightDownLeft => "nesw-resize",
CursorStyle::ResizeColumn => "col-resize",
CursorStyle::ResizeRow => "row-resize",
CursorStyle::IBeamCursorForVerticalLayout => "vertical-text",
@@ -138,7 +138,7 @@ impl Globals {
primary_selection_manager: globals.bind(&qh, 1..=1, ()).ok(),
shm: globals.bind(&qh, 1..=1, ()).unwrap(),
seat,
wm_base: globals.bind(&qh, 1..=1, ()).unwrap(),
wm_base: globals.bind(&qh, 2..=5, ()).unwrap(),
viewporter: globals.bind(&qh, 1..=1, ()).ok(),
fractional_scale_manager: globals.bind(&qh, 1..=1, ()).ok(),
decoration_manager: globals.bind(&qh, 1..=1, ()).ok(),
+329 -96
View File
@@ -25,9 +25,10 @@ use crate::platform::linux::wayland::serial::SerialKind;
use crate::platform::{PlatformAtlas, PlatformInputHandler, PlatformWindow};
use crate::scene::Scene;
use crate::{
px, size, AnyWindowHandle, Bounds, Globals, Modifiers, Output, Pixels, PlatformDisplay,
PlatformInput, Point, PromptLevel, Size, WaylandClientStatePtr, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowParams,
px, size, AnyWindowHandle, Bounds, Decorations, Globals, Modifiers, Output, Pixels,
PlatformDisplay, PlatformInput, Point, PromptLevel, ResizeEdge, Size, Tiling,
WaylandClientStatePtr, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowControls, WindowDecorations, WindowParams,
};
#[derive(Default)]
@@ -62,10 +63,12 @@ impl rwh::HasDisplayHandle for RawWindow {
}
}
#[derive(Debug)]
struct InProgressConfigure {
size: Option<Size<Pixels>>,
fullscreen: bool,
maximized: bool,
tiling: Tiling,
}
pub struct WaylandWindowState {
@@ -84,14 +87,20 @@ pub struct WaylandWindowState {
bounds: Bounds<Pixels>,
scale: f32,
input_handler: Option<PlatformInputHandler>,
decoration_state: WaylandDecorationState,
decorations: WindowDecorations,
background_appearance: WindowBackgroundAppearance,
fullscreen: bool,
maximized: bool,
windowed_bounds: Bounds<Pixels>,
tiling: Tiling,
window_bounds: Bounds<Pixels>,
client: WaylandClientStatePtr,
handle: AnyWindowHandle,
active: bool,
in_progress_configure: Option<InProgressConfigure>,
in_progress_window_controls: Option<WindowControls>,
window_controls: WindowControls,
inset: Option<Pixels>,
requested_inset: Option<Pixels>,
}
#[derive(Clone)]
@@ -142,7 +151,7 @@ impl WaylandWindowState {
height: options.bounds.size.height.0 as u32,
depth: 1,
},
transparent: options.window_background != WindowBackgroundAppearance::Opaque,
transparent: true,
};
Ok(Self {
@@ -160,17 +169,34 @@ impl WaylandWindowState {
bounds: options.bounds,
scale: 1.0,
input_handler: None,
decoration_state: WaylandDecorationState::Client,
decorations: WindowDecorations::Client,
background_appearance: WindowBackgroundAppearance::Opaque,
fullscreen: false,
maximized: false,
windowed_bounds: options.bounds,
tiling: Tiling::default(),
window_bounds: options.bounds,
in_progress_configure: None,
client,
appearance,
handle,
active: false,
in_progress_window_controls: None,
// Assume that we can do anything, unless told otherwise
window_controls: WindowControls {
fullscreen: true,
maximize: true,
minimize: true,
window_menu: true,
},
inset: None,
requested_inset: None,
})
}
pub fn is_transparent(&self) -> bool {
self.decorations == WindowDecorations::Client
|| self.background_appearance != WindowBackgroundAppearance::Opaque
}
}
pub(crate) struct WaylandWindow(pub WaylandWindowStatePtr);
@@ -235,7 +261,7 @@ impl WaylandWindow {
.wm_base
.get_xdg_surface(&surface, &globals.qh, surface.id());
let toplevel = xdg_surface.get_toplevel(&globals.qh, surface.id());
toplevel.set_min_size(200, 200);
toplevel.set_min_size(50, 50);
if let Some(fractional_scale_manager) = globals.fractional_scale_manager.as_ref() {
fractional_scale_manager.get_fractional_scale(&surface, &globals.qh, surface.id());
@@ -246,13 +272,7 @@ impl WaylandWindow {
.decoration_manager
.as_ref()
.map(|decoration_manager| {
let decoration = decoration_manager.get_toplevel_decoration(
&toplevel,
&globals.qh,
surface.id(),
);
decoration.set_mode(zxdg_toplevel_decoration_v1::Mode::ClientSide);
decoration
decoration_manager.get_toplevel_decoration(&toplevel, &globals.qh, surface.id())
});
let viewport = globals
@@ -298,7 +318,7 @@ impl WaylandWindowStatePtr {
pub fn frame(&self, request_frame_callback: bool) {
if request_frame_callback {
let state = self.state.borrow_mut();
let mut state = self.state.borrow_mut();
state.surface.frame(&state.globals.qh, state.surface.id());
drop(state);
}
@@ -311,6 +331,18 @@ impl WaylandWindowStatePtr {
pub fn handle_xdg_surface_event(&self, event: xdg_surface::Event) {
match event {
xdg_surface::Event::Configure { serial } => {
{
let mut state = self.state.borrow_mut();
if let Some(window_controls) = state.in_progress_window_controls.take() {
state.window_controls = window_controls;
drop(state);
let mut callbacks = self.callbacks.borrow_mut();
if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
appearance_changed();
}
}
}
{
let mut state = self.state.borrow_mut();
@@ -318,18 +350,21 @@ impl WaylandWindowStatePtr {
let got_unmaximized = state.maximized && !configure.maximized;
state.fullscreen = configure.fullscreen;
state.maximized = configure.maximized;
state.tiling = configure.tiling;
if got_unmaximized {
configure.size = Some(state.windowed_bounds.size);
} else if !configure.fullscreen && !configure.maximized {
configure.size = Some(state.window_bounds.size);
} else if !configure.maximized {
configure.size =
compute_outer_size(state.inset, configure.size, state.tiling);
}
if !configure.fullscreen && !configure.maximized {
if let Some(size) = configure.size {
state.windowed_bounds = Bounds {
state.window_bounds = Bounds {
origin: Point::default(),
size,
};
}
}
drop(state);
if let Some(size) = configure.size {
self.resize(size);
@@ -340,8 +375,11 @@ impl WaylandWindowStatePtr {
state.xdg_surface.ack_configure(serial);
let request_frame_callback = !state.acknowledged_first_configure;
state.acknowledged_first_configure = true;
drop(state);
self.frame(request_frame_callback);
if request_frame_callback {
drop(state);
self.frame(true);
}
}
_ => {}
}
@@ -351,10 +389,21 @@ impl WaylandWindowStatePtr {
match event {
zxdg_toplevel_decoration_v1::Event::Configure { mode } => match mode {
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ServerSide) => {
self.set_decoration_state(WaylandDecorationState::Server)
self.state.borrow_mut().decorations = WindowDecorations::Server;
if let Some(mut appearance_changed) =
self.callbacks.borrow_mut().appearance_changed.as_mut()
{
appearance_changed();
}
}
WEnum::Value(zxdg_toplevel_decoration_v1::Mode::ClientSide) => {
self.set_decoration_state(WaylandDecorationState::Client)
self.state.borrow_mut().decorations = WindowDecorations::Client;
// Update background to be transparent
if let Some(mut appearance_changed) =
self.callbacks.borrow_mut().appearance_changed.as_mut()
{
appearance_changed();
}
}
WEnum::Value(_) => {
log::warn!("Unknown decoration mode");
@@ -389,14 +438,44 @@ impl WaylandWindowStatePtr {
Some(size(px(width as f32), px(height as f32)))
};
let fullscreen = states.contains(&(xdg_toplevel::State::Fullscreen as u8));
let maximized = states.contains(&(xdg_toplevel::State::Maximized as u8));
let states = extract_states::<xdg_toplevel::State>(&states);
let mut tiling = Tiling::default();
let mut fullscreen = false;
let mut maximized = false;
for state in states {
match state {
xdg_toplevel::State::Maximized => {
maximized = true;
}
xdg_toplevel::State::Fullscreen => {
fullscreen = true;
}
xdg_toplevel::State::TiledTop => {
tiling.top = true;
}
xdg_toplevel::State::TiledLeft => {
tiling.left = true;
}
xdg_toplevel::State::TiledRight => {
tiling.right = true;
}
xdg_toplevel::State::TiledBottom => {
tiling.bottom = true;
}
_ => {
// noop
}
}
}
let mut state = self.state.borrow_mut();
state.in_progress_configure = Some(InProgressConfigure {
size,
fullscreen,
maximized,
tiling,
});
false
@@ -415,6 +494,33 @@ impl WaylandWindowStatePtr {
true
}
}
xdg_toplevel::Event::WmCapabilities { capabilities } => {
let mut window_controls = WindowControls::default();
let states = extract_states::<xdg_toplevel::WmCapabilities>(&capabilities);
for state in states {
match state {
xdg_toplevel::WmCapabilities::Maximize => {
window_controls.maximize = true;
}
xdg_toplevel::WmCapabilities::Minimize => {
window_controls.minimize = true;
}
xdg_toplevel::WmCapabilities::Fullscreen => {
window_controls.fullscreen = true;
}
xdg_toplevel::WmCapabilities::WindowMenu => {
window_controls.window_menu = true;
}
_ => {}
}
}
let mut state = self.state.borrow_mut();
state.in_progress_window_controls = Some(window_controls);
false
}
_ => false,
}
}
@@ -545,18 +651,6 @@ impl WaylandWindowStatePtr {
self.set_size_and_scale(None, Some(scale));
}
/// Notifies the window of the state of the decorations.
///
/// # Note
///
/// This API is indirectly called by the wayland compositor and
/// not meant to be called by a user who wishes to change the state
/// of the decorations. This is because the state of the decorations
/// is managed by the compositor and not the client.
pub fn set_decoration_state(&self, state: WaylandDecorationState) {
self.state.borrow_mut().decoration_state = state;
}
pub fn close(&self) {
let mut callbacks = self.callbacks.borrow_mut();
if let Some(fun) = callbacks.close.take() {
@@ -599,6 +693,17 @@ impl WaylandWindowStatePtr {
}
}
fn extract_states<'a, S: TryFrom<u32> + 'a>(states: &'a [u8]) -> impl Iterator<Item = S> + 'a
where
<S as TryFrom<u32>>::Error: 'a,
{
states
.chunks_exact(4)
.flat_map(TryInto::<[u8; 4]>::try_into)
.map(u32::from_ne_bytes)
.flat_map(S::try_from)
}
fn primary_output_scale(state: &mut RefMut<WaylandWindowState>) -> i32 {
let mut scale = 1;
let mut current_output = state.display.take();
@@ -639,9 +744,9 @@ impl PlatformWindow for WaylandWindow {
fn window_bounds(&self) -> WindowBounds {
let state = self.borrow();
if state.fullscreen {
WindowBounds::Fullscreen(state.windowed_bounds)
WindowBounds::Fullscreen(state.window_bounds)
} else if state.maximized {
WindowBounds::Maximized(state.windowed_bounds)
WindowBounds::Maximized(state.window_bounds)
} else {
drop(state);
WindowBounds::Windowed(self.bounds())
@@ -718,52 +823,10 @@ impl PlatformWindow for WaylandWindow {
self.borrow().toplevel.set_app_id(app_id.to_owned());
}
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
let opaque = background_appearance == WindowBackgroundAppearance::Opaque;
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
let mut state = self.borrow_mut();
state.renderer.update_transparency(!opaque);
let region = state
.globals
.compositor
.create_region(&state.globals.qh, ());
region.add(0, 0, i32::MAX, i32::MAX);
if opaque {
// Promise the compositor that this region of the window surface
// contains no transparent pixels. This allows the compositor to
// do skip whatever is behind the surface for better performance.
state.surface.set_opaque_region(Some(&region));
} else {
state.surface.set_opaque_region(None);
}
if let Some(ref blur_manager) = state.globals.blur_manager {
if background_appearance == WindowBackgroundAppearance::Blurred {
if state.blur.is_none() {
let blur = blur_manager.create(&state.surface, &state.globals.qh, ());
blur.set_region(Some(&region));
state.blur = Some(blur);
}
state.blur.as_ref().unwrap().commit();
} else {
// It probably doesn't hurt to clear the blur for opaque windows
blur_manager.unset(&state.surface);
if let Some(b) = state.blur.take() {
b.release()
}
}
}
region.destroy();
}
fn set_edited(&mut self, _edited: bool) {
log::info!("ignoring macOS specific set_edited");
}
fn show_character_palette(&self) {
log::info!("ignoring macOS specific show_character_palette");
state.background_appearance = background_appearance;
update_window(state);
}
fn minimize(&self) {
@@ -831,6 +894,25 @@ impl PlatformWindow for WaylandWindow {
fn completed_frame(&self) {
let mut state = self.borrow_mut();
if let Some(area) = state.requested_inset {
state.inset = Some(area);
}
let window_geometry = inset_by_tiling(
state.bounds.map_origin(|_| px(0.0)),
state.inset.unwrap_or(px(0.0)),
state.tiling,
)
.map(|v| v.0 as i32)
.map_size(|v| if v <= 0 { 1 } else { v });
state.xdg_surface.set_window_geometry(
window_geometry.origin.x,
window_geometry.origin.y,
window_geometry.size.width,
window_geometry.size.height,
);
state.surface.commit();
}
@@ -850,22 +932,173 @@ impl PlatformWindow for WaylandWindow {
);
}
fn start_system_move(&self) {
fn start_window_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
fn start_window_resize(&self, edge: crate::ResizeEdge) {
let state = self.borrow();
state.toplevel.resize(
&state.globals.seat,
state.client.get_serial(SerialKind::MousePress),
edge.to_xdg(),
)
}
fn window_decorations(&self) -> Decorations {
let state = self.borrow();
match state.decorations {
WindowDecorations::Server => Decorations::Server,
WindowDecorations::Client => Decorations::Client {
tiling: state.tiling,
},
}
}
fn request_decorations(&self, decorations: WindowDecorations) {
let mut state = self.borrow_mut();
state.decorations = decorations;
if let Some(decoration) = state.decoration.as_ref() {
decoration.set_mode(decorations.to_xdg());
update_window(state);
}
}
fn window_controls(&self) -> WindowControls {
self.borrow().window_controls
}
fn set_client_inset(&self, inset: Pixels) {
let mut state = self.borrow_mut();
if Some(inset) != state.inset {
state.requested_inset = Some(inset);
update_window(state);
}
}
}
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub enum WaylandDecorationState {
/// Decorations are to be provided by the client
Client,
fn update_window(mut state: RefMut<WaylandWindowState>) {
let opaque = !state.is_transparent();
/// Decorations are provided by the server
Server,
state.renderer.update_transparency(!opaque);
let mut opaque_area = state.window_bounds.map(|v| v.0 as i32);
if let Some(inset) = state.inset {
opaque_area.inset(inset.0 as i32);
}
let region = state
.globals
.compositor
.create_region(&state.globals.qh, ());
region.add(
opaque_area.origin.x,
opaque_area.origin.y,
opaque_area.size.width,
opaque_area.size.height,
);
// Note that rounded corners make this rectangle API hard to work with.
// As this is common when using CSD, let's just disable this API.
if state.background_appearance == WindowBackgroundAppearance::Opaque
&& state.decorations == WindowDecorations::Server
{
// Promise the compositor that this region of the window surface
// contains no transparent pixels. This allows the compositor to
// do skip whatever is behind the surface for better performance.
state.surface.set_opaque_region(Some(&region));
} else {
state.surface.set_opaque_region(None);
}
if let Some(ref blur_manager) = state.globals.blur_manager {
if state.background_appearance == WindowBackgroundAppearance::Blurred {
if state.blur.is_none() {
let blur = blur_manager.create(&state.surface, &state.globals.qh, ());
blur.set_region(Some(&region));
state.blur = Some(blur);
}
state.blur.as_ref().unwrap().commit();
} else {
// It probably doesn't hurt to clear the blur for opaque windows
blur_manager.unset(&state.surface);
if let Some(b) = state.blur.take() {
b.release()
}
}
}
region.destroy();
}
impl WindowDecorations {
fn to_xdg(&self) -> zxdg_toplevel_decoration_v1::Mode {
match self {
WindowDecorations::Client => zxdg_toplevel_decoration_v1::Mode::ClientSide,
WindowDecorations::Server => zxdg_toplevel_decoration_v1::Mode::ServerSide,
}
}
}
impl ResizeEdge {
fn to_xdg(&self) -> xdg_toplevel::ResizeEdge {
match self {
ResizeEdge::Top => xdg_toplevel::ResizeEdge::Top,
ResizeEdge::TopRight => xdg_toplevel::ResizeEdge::TopRight,
ResizeEdge::Right => xdg_toplevel::ResizeEdge::Right,
ResizeEdge::BottomRight => xdg_toplevel::ResizeEdge::BottomRight,
ResizeEdge::Bottom => xdg_toplevel::ResizeEdge::Bottom,
ResizeEdge::BottomLeft => xdg_toplevel::ResizeEdge::BottomLeft,
ResizeEdge::Left => xdg_toplevel::ResizeEdge::Left,
ResizeEdge::TopLeft => xdg_toplevel::ResizeEdge::TopLeft,
}
}
}
/// The configuration event is in terms of the window geometry, which we are constantly
/// updating to account for the client decorations. But that's not the area we want to render
/// to, due to our intrusize CSD. So, here we calculate the 'actual' size, by adding back in the insets
fn compute_outer_size(
inset: Option<Pixels>,
new_size: Option<Size<Pixels>>,
tiling: Tiling,
) -> Option<Size<Pixels>> {
let Some(inset) = inset else { return new_size };
new_size.map(|mut new_size| {
if !tiling.top {
new_size.height += inset;
}
if !tiling.bottom {
new_size.height += inset;
}
if !tiling.left {
new_size.width += inset;
}
if !tiling.right {
new_size.width += inset;
}
new_size
})
}
fn inset_by_tiling(mut bounds: Bounds<Pixels>, inset: Pixels, tiling: Tiling) -> Bounds<Pixels> {
if !tiling.top {
bounds.origin.y += inset;
bounds.size.height -= inset;
}
if !tiling.bottom {
bounds.size.height -= inset;
}
if !tiling.left {
bounds.origin.x += inset;
bounds.size.width -= inset;
}
if !tiling.right {
bounds.size.width -= inset;
}
bounds
}
+11 -1
View File
@@ -512,7 +512,7 @@ impl X11Client {
match event {
Event::ClientMessage(event) => {
let window = self.get_window(event.window)?;
let [atom, ..] = event.data.as_data32();
let [atom, _arg1, arg2, arg3, _arg4] = event.data.as_data32();
let mut state = self.0.borrow_mut();
if atom == state.atoms.WM_DELETE_WINDOW {
@@ -521,6 +521,12 @@ impl X11Client {
// Rest of the close logic is handled in drop_window()
window.close();
}
} else if atom == state.atoms._NET_WM_SYNC_REQUEST {
window.state.borrow_mut().last_sync_counter =
Some(x11rb::protocol::sync::Int64 {
lo: arg2,
hi: arg3 as i32,
})
}
}
Event::ConfigureNotify(event) => {
@@ -537,6 +543,10 @@ impl X11Client {
let window = self.get_window(event.window)?;
window.configure(bounds);
}
Event::PropertyNotify(event) => {
let window = self.get_window(event.window)?;
window.property_notify(event);
}
Event::Expose(event) => {
let window = self.get_window(event.window)?;
window.refresh();
+300 -103
View File
@@ -2,10 +2,11 @@ use anyhow::Context;
use crate::{
platform::blade::{BladeRenderer, BladeSurfaceConfig},
px, size, AnyWindowHandle, Bounds, DevicePixels, ForegroundExecutor, Modifiers, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
PromptLevel, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowKind, WindowParams, X11ClientStatePtr,
px, size, AnyWindowHandle, Bounds, Decorations, DevicePixels, ForegroundExecutor, Modifiers,
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
Point, PromptLevel, ResizeEdge, Scene, Size, Tiling, WindowAppearance,
WindowBackgroundAppearance, WindowBounds, WindowDecorations, WindowKind, WindowParams,
X11ClientStatePtr,
};
use blade_graphics as gpu;
@@ -15,24 +16,17 @@ use x11rb::{
connection::Connection,
protocol::{
randr::{self, ConnectionExt as _},
sync,
xinput::{self, ConnectionExt as _},
xproto::{
self, ClientMessageEvent, ConnectionExt as _, EventMask, TranslateCoordinatesReply,
},
xproto::{self, ClientMessageEvent, ConnectionExt, EventMask, TranslateCoordinatesReply},
},
wrapper::ConnectionExt as _,
xcb_ffi::XCBConnection,
};
use std::{
cell::RefCell,
ffi::c_void,
num::NonZeroU32,
ops::Div,
ptr::NonNull,
rc::Rc,
sync::{self, Arc},
time::Duration,
cell::RefCell, ffi::c_void, mem::size_of, num::NonZeroU32, ops::Div, ptr::NonNull, rc::Rc,
sync::Arc, time::Duration,
};
use super::{X11Display, XINPUT_MASTER_DEVICE};
@@ -50,10 +44,16 @@ x11rb::atom_manager! {
_NET_WM_STATE_HIDDEN,
_NET_WM_STATE_FOCUSED,
_NET_ACTIVE_WINDOW,
_NET_WM_SYNC_REQUEST,
_NET_WM_SYNC_REQUEST_COUNTER,
_NET_WM_BYPASS_COMPOSITOR,
_NET_WM_MOVERESIZE,
_NET_WM_WINDOW_TYPE,
_NET_WM_WINDOW_TYPE_NOTIFICATION,
_NET_WM_SYNC,
_MOTIF_WM_HINTS,
_GTK_SHOW_WINDOW_MENU,
_GTK_FRAME_EXTENTS,
}
}
@@ -70,6 +70,21 @@ fn query_render_extent(xcb_connection: &XCBConnection, x_window: xproto::Window)
}
}
impl ResizeEdge {
fn to_moveresize(&self) -> u32 {
match self {
ResizeEdge::TopLeft => 0,
ResizeEdge::Top => 1,
ResizeEdge::TopRight => 2,
ResizeEdge::Right => 3,
ResizeEdge::BottomRight => 4,
ResizeEdge::Bottom => 5,
ResizeEdge::BottomLeft => 6,
ResizeEdge::Left => 7,
}
}
}
#[derive(Debug)]
struct Visual {
id: xproto::Visualid,
@@ -166,6 +181,8 @@ pub struct X11WindowState {
executor: ForegroundExecutor,
atoms: XcbAtoms,
x_root_window: xproto::Window,
pub(crate) counter_id: sync::Counter,
pub(crate) last_sync_counter: Option<sync::Int64>,
_raw: RawWindow,
bounds: Bounds<Pixels>,
scale_factor: f32,
@@ -173,7 +190,22 @@ pub struct X11WindowState {
display: Rc<dyn PlatformDisplay>,
input_handler: Option<PlatformInputHandler>,
appearance: WindowAppearance,
background_appearance: WindowBackgroundAppearance,
maximized_vertical: bool,
maximized_horizontal: bool,
hidden: bool,
active: bool,
fullscreen: bool,
decorations: WindowDecorations,
pub handle: AnyWindowHandle,
last_insets: [u32; 4],
}
impl X11WindowState {
fn is_transparent(&self) -> bool {
self.decorations == WindowDecorations::Client
|| self.background_appearance != WindowBackgroundAppearance::Opaque
}
}
#[derive(Clone)]
@@ -230,19 +262,11 @@ impl X11WindowState {
.map_or(x_main_screen_index, |did| did.0 as usize);
let visual_set = find_visuals(&xcb_connection, x_screen_index);
let visual_maybe = match params.window_background {
WindowBackgroundAppearance::Opaque => visual_set.opaque,
WindowBackgroundAppearance::Transparent | WindowBackgroundAppearance::Blurred => {
visual_set.transparent
}
};
let visual = match visual_maybe {
let visual = match visual_set.transparent {
Some(visual) => visual,
None => {
log::warn!(
"Unable to find a matching visual for {:?}",
params.window_background
);
log::warn!("Unable to find a transparent visual",);
visual_set.inherit
}
};
@@ -269,7 +293,8 @@ impl X11WindowState {
| xproto::EventMask::STRUCTURE_NOTIFY
| xproto::EventMask::FOCUS_CHANGE
| xproto::EventMask::KEY_PRESS
| xproto::EventMask::KEY_RELEASE,
| xproto::EventMask::KEY_RELEASE
| EventMask::PROPERTY_CHANGE,
);
let mut bounds = params.bounds.to_device_pixels(scale_factor);
@@ -349,7 +374,26 @@ impl X11WindowState {
x_window,
atoms.WM_PROTOCOLS,
xproto::AtomEnum::ATOM,
&[atoms.WM_DELETE_WINDOW],
&[atoms.WM_DELETE_WINDOW, atoms._NET_WM_SYNC_REQUEST],
)
.unwrap();
sync::initialize(xcb_connection, 3, 1).unwrap();
let sync_request_counter = xcb_connection.generate_id().unwrap();
sync::create_counter(
xcb_connection,
sync_request_counter,
sync::Int64 { lo: 0, hi: 0 },
)
.unwrap();
xcb_connection
.change_property32(
xproto::PropMode::REPLACE,
x_window,
atoms._NET_WM_SYNC_REQUEST_COUNTER,
xproto::AtomEnum::CARDINAL,
&[sync_request_counter],
)
.unwrap();
@@ -396,7 +440,8 @@ impl X11WindowState {
// Note: this has to be done after the GPU init, or otherwise
// the sizes are immediately invalidated.
size: query_render_extent(xcb_connection, x_window),
transparent: params.window_background != WindowBackgroundAppearance::Opaque,
// In case we have window decorations to render
transparent: true,
};
xcb_connection.map_window(x_window).unwrap();
@@ -438,9 +483,19 @@ impl X11WindowState {
renderer: BladeRenderer::new(gpu, config),
atoms: *atoms,
input_handler: None,
active: false,
fullscreen: false,
maximized_vertical: false,
maximized_horizontal: false,
hidden: false,
appearance,
handle,
background_appearance: WindowBackgroundAppearance::Opaque,
destroyed: false,
decorations: WindowDecorations::Server,
last_insets: [0, 0, 0, 0],
counter_id: sync_request_counter,
last_sync_counter: None,
refresh_rate,
})
}
@@ -511,7 +566,7 @@ impl X11Window {
scale_factor: f32,
appearance: WindowAppearance,
) -> anyhow::Result<Self> {
Ok(Self(X11WindowStatePtr {
let ptr = X11WindowStatePtr {
state: Rc::new(RefCell::new(X11WindowState::new(
handle,
client,
@@ -527,7 +582,12 @@ impl X11Window {
callbacks: Rc::new(RefCell::new(Callbacks::default())),
xcb_connection: xcb_connection.clone(),
x_window,
}))
};
let state = ptr.state.borrow_mut();
ptr.set_wm_properties(state);
Ok(Self(ptr))
}
fn set_wm_hints(&self, wm_hint_property_state: WmHintPropertyState, prop1: u32, prop2: u32) {
@@ -549,29 +609,6 @@ impl X11Window {
.unwrap();
}
fn get_wm_hints(&self) -> Vec<u32> {
let reply = self
.0
.xcb_connection
.get_property(
false,
self.0.x_window,
self.0.state.borrow().atoms._NET_WM_STATE,
xproto::AtomEnum::ATOM,
0,
u32::MAX,
)
.unwrap()
.reply()
.unwrap();
// Reply is in u8 but atoms are represented as u32
reply
.value
.chunks_exact(4)
.map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
.collect()
}
fn get_root_position(&self, position: Point<Pixels>) -> TranslateCoordinatesReply {
let state = self.0.state.borrow();
self.0
@@ -586,6 +623,48 @@ impl X11Window {
.reply()
.unwrap()
}
fn send_moveresize(&self, flag: u32) {
let state = self.0.state.borrow();
self.0
.xcb_connection
.ungrab_pointer(x11rb::CURRENT_TIME)
.unwrap()
.check()
.unwrap();
let pointer = self
.0
.xcb_connection
.query_pointer(self.0.x_window)
.unwrap()
.reply()
.unwrap();
let message = ClientMessageEvent::new(
32,
self.0.x_window,
state.atoms._NET_WM_MOVERESIZE,
[
pointer.root_x as u32,
pointer.root_y as u32,
flag,
0, // Left mouse button
0,
],
);
self.0
.xcb_connection
.send_event(
false,
state.x_root_window,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message,
)
.unwrap();
self.0.xcb_connection.flush().unwrap();
}
}
impl X11WindowStatePtr {
@@ -600,6 +679,54 @@ impl X11WindowStatePtr {
}
}
pub fn property_notify(&self, event: xproto::PropertyNotifyEvent) {
let mut state = self.state.borrow_mut();
if event.atom == state.atoms._NET_WM_STATE {
self.set_wm_properties(state);
}
}
fn set_wm_properties(&self, mut state: std::cell::RefMut<X11WindowState>) {
let reply = self
.xcb_connection
.get_property(
false,
self.x_window,
state.atoms._NET_WM_STATE,
xproto::AtomEnum::ATOM,
0,
u32::MAX,
)
.unwrap()
.reply()
.unwrap();
let atoms = reply
.value
.chunks_exact(4)
.map(|chunk| u32::from_ne_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
state.active = false;
state.fullscreen = false;
state.maximized_vertical = false;
state.maximized_horizontal = false;
state.hidden = true;
for atom in atoms {
if atom == state.atoms._NET_WM_STATE_FOCUSED {
state.active = true;
} else if atom == state.atoms._NET_WM_STATE_FULLSCREEN {
state.fullscreen = true;
} else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_VERT {
state.maximized_vertical = true;
} else if atom == state.atoms._NET_WM_STATE_MAXIMIZED_HORZ {
state.maximized_horizontal = true;
} else if atom == state.atoms._NET_WM_STATE_HIDDEN {
state.hidden = true;
}
}
}
pub fn close(&self) {
let mut callbacks = self.callbacks.borrow_mut();
if let Some(fun) = callbacks.close.take() {
@@ -715,6 +842,9 @@ impl X11WindowStatePtr {
));
resize_args = Some((state.content_size(), state.scale_factor));
}
if let Some(value) = state.last_sync_counter.take() {
sync::set_counter(&self.xcb_connection, state.counter_id, value).unwrap();
}
}
let mut callbacks = self.callbacks.borrow_mut();
@@ -737,8 +867,12 @@ impl X11WindowStatePtr {
}
pub fn set_appearance(&mut self, appearance: WindowAppearance) {
self.state.borrow_mut().appearance = appearance;
let mut state = self.state.borrow_mut();
state.appearance = appearance;
let is_transparent = state.is_transparent();
state.renderer.update_transparency(is_transparent);
state.appearance = appearance;
drop(state);
let mut callbacks = self.callbacks.borrow_mut();
if let Some(ref mut fun) = callbacks.appearance_changed {
(fun)()
@@ -757,11 +891,9 @@ impl PlatformWindow for X11Window {
fn is_maximized(&self) -> bool {
let state = self.0.state.borrow();
let wm_hints = self.get_wm_hints();
// A maximized window that gets minimized will still retain its maximized state.
!wm_hints.contains(&state.atoms._NET_WM_STATE_HIDDEN)
&& wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_VERT)
&& wm_hints.contains(&state.atoms._NET_WM_STATE_MAXIMIZED_HORZ)
!state.hidden && state.maximized_vertical && state.maximized_horizontal
}
fn window_bounds(&self) -> WindowBounds {
@@ -862,9 +994,7 @@ impl PlatformWindow for X11Window {
}
fn is_active(&self) -> bool {
let state = self.0.state.borrow();
self.get_wm_hints()
.contains(&state.atoms._NET_WM_STATE_FOCUSED)
self.0.state.borrow().active
}
fn set_title(&mut self, title: &str) {
@@ -913,10 +1043,11 @@ impl PlatformWindow for X11Window {
log::info!("ignoring macOS specific set_edited");
}
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
let mut inner = self.0.state.borrow_mut();
let transparent = background_appearance != WindowBackgroundAppearance::Opaque;
inner.renderer.update_transparency(transparent);
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
let mut state = self.0.state.borrow_mut();
state.background_appearance = background_appearance;
let transparent = state.is_transparent();
state.renderer.update_transparency(transparent);
}
fn show_character_palette(&self) {
@@ -962,9 +1093,7 @@ impl PlatformWindow for X11Window {
}
fn is_fullscreen(&self) -> bool {
let state = self.0.state.borrow();
self.get_wm_hints()
.contains(&state.atoms._NET_WM_STATE_FULLSCREEN)
self.0.state.borrow().fullscreen
}
fn on_request_frame(&self, callback: Box<dyn FnMut()>) {
@@ -1004,7 +1133,7 @@ impl PlatformWindow for X11Window {
inner.renderer.draw(scene);
}
fn sprite_atlas(&self) -> sync::Arc<dyn PlatformAtlas> {
fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
let inner = self.0.state.borrow();
inner.renderer.sprite_atlas().clone()
}
@@ -1035,41 +1164,109 @@ impl PlatformWindow for X11Window {
.unwrap();
}
fn start_system_move(&self) {
let state = self.0.state.borrow();
let pointer = self
.0
.xcb_connection
.query_pointer(self.0.x_window)
.unwrap()
.reply()
.unwrap();
fn start_window_move(&self) {
const MOVERESIZE_MOVE: u32 = 8;
let message = ClientMessageEvent::new(
32,
self.0.x_window,
state.atoms._NET_WM_MOVERESIZE,
[
pointer.root_x as u32,
pointer.root_y as u32,
MOVERESIZE_MOVE,
1, // Left mouse button
1,
],
);
self.0
.xcb_connection
.send_event(
false,
state.x_root_window,
EventMask::SUBSTRUCTURE_REDIRECT | EventMask::SUBSTRUCTURE_NOTIFY,
message,
)
.unwrap();
self.send_moveresize(MOVERESIZE_MOVE);
}
fn should_render_window_controls(&self) -> bool {
false
fn start_window_resize(&self, edge: ResizeEdge) {
self.send_moveresize(edge.to_moveresize());
}
fn window_decorations(&self) -> crate::Decorations {
let state = self.0.state.borrow();
match state.decorations {
WindowDecorations::Server => Decorations::Server,
WindowDecorations::Client => {
// https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
Decorations::Client {
tiling: Tiling {
top: state.maximized_vertical,
bottom: state.maximized_vertical,
left: state.maximized_horizontal,
right: state.maximized_horizontal,
},
}
}
}
}
fn set_client_inset(&self, inset: Pixels) {
let mut state = self.0.state.borrow_mut();
let dp = (inset.0 * state.scale_factor) as u32;
let (left, right) = if state.maximized_horizontal {
(0, 0)
} else {
(dp, dp)
};
let (top, bottom) = if state.maximized_vertical {
(0, 0)
} else {
(dp, dp)
};
let insets = [left, right, top, bottom];
if state.last_insets != insets {
state.last_insets = insets;
self.0
.xcb_connection
.change_property(
xproto::PropMode::REPLACE,
self.0.x_window,
state.atoms._GTK_FRAME_EXTENTS,
xproto::AtomEnum::CARDINAL,
size_of::<u32>() as u8 * 8,
4,
bytemuck::cast_slice::<u32, u8>(&insets),
)
.unwrap();
}
}
fn request_decorations(&self, decorations: crate::WindowDecorations) {
// https://github.com/rust-windowing/winit/blob/master/src/platform_impl/linux/x11/util/hint.rs#L53-L87
let hints_data: [u32; 5] = match decorations {
WindowDecorations::Server => [1 << 1, 0, 1, 0, 0],
WindowDecorations::Client => [1 << 1, 0, 0, 0, 0],
};
let mut state = self.0.state.borrow_mut();
self.0
.xcb_connection
.change_property(
xproto::PropMode::REPLACE,
self.0.x_window,
state.atoms._MOTIF_WM_HINTS,
state.atoms._MOTIF_WM_HINTS,
std::mem::size_of::<u32>() as u8 * 8,
5,
bytemuck::cast_slice::<u32, u8>(&hints_data),
)
.unwrap();
match decorations {
WindowDecorations::Server => {
state.decorations = WindowDecorations::Server;
let is_transparent = state.is_transparent();
state.renderer.update_transparency(is_transparent);
}
WindowDecorations::Client => {
state.decorations = WindowDecorations::Client;
let is_transparent = state.is_transparent();
state.renderer.update_transparency(is_transparent);
}
}
drop(state);
let mut callbacks = self.0.callbacks.borrow_mut();
if let Some(appearance_changed) = callbacks.appearance_changed.as_mut() {
appearance_changed();
}
}
}
+13 -3
View File
@@ -796,14 +796,24 @@ impl Platform for MacPlatform {
CursorStyle::ClosedHand => msg_send![class!(NSCursor), closedHandCursor],
CursorStyle::OpenHand => msg_send![class!(NSCursor), openHandCursor],
CursorStyle::PointingHand => msg_send![class!(NSCursor), pointingHandCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), verticalResizeCursor],
CursorStyle::ResizeLeft => msg_send![class!(NSCursor), resizeLeftCursor],
CursorStyle::ResizeRight => msg_send![class!(NSCursor), resizeRightCursor],
CursorStyle::ResizeLeftRight => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::ResizeColumn => msg_send![class!(NSCursor), resizeLeftRightCursor],
CursorStyle::ResizeRow => msg_send![class!(NSCursor), resizeUpDownCursor],
CursorStyle::ResizeUp => msg_send![class!(NSCursor), resizeUpCursor],
CursorStyle::ResizeDown => msg_send![class!(NSCursor), resizeDownCursor],
CursorStyle::ResizeUpDown => msg_send![class!(NSCursor), resizeUpDownCursor],
CursorStyle::ResizeRow => msg_send![class!(NSCursor), resizeUpDownCursor],
// Undocumented, private class methods:
// https://stackoverflow.com/questions/27242353/cocoa-predefined-resize-mouse-cursor
CursorStyle::ResizeUpLeftDownRight => {
msg_send![class!(NSCursor), _windowResizeNorthWestSouthEastCursor]
}
CursorStyle::ResizeUpRightDownLeft => {
msg_send![class!(NSCursor), _windowResizeNorthEastSouthWestCursor]
}
CursorStyle::IBeamCursorForVerticalLayout => {
msg_send![class!(NSCursor), IBeamCursorForVerticalLayout]
}
+2 -13
View File
@@ -497,7 +497,6 @@ impl MacWindow {
pub fn open(
handle: AnyWindowHandle,
WindowParams {
window_background,
bounds,
titlebar,
kind,
@@ -603,7 +602,7 @@ impl MacWindow {
native_window as *mut _,
native_view as *mut _,
bounds.size.map(|pixels| pixels.0),
window_background != WindowBackgroundAppearance::Opaque,
false,
),
request_frame_callback: None,
event_callback: None,
@@ -676,8 +675,6 @@ impl MacWindow {
native_window.setContentView_(native_view.autorelease());
native_window.makeFirstResponder_(native_view);
window.set_background_appearance(window_background);
match kind {
WindowKind::Normal => {
native_window.setLevel_(NSNormalWindowLevel);
@@ -956,7 +953,7 @@ impl PlatformWindow for MacWindow {
fn set_app_id(&mut self, _app_id: &str) {}
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
let mut this = self.0.as_ref().lock();
this.renderer
.update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
@@ -1092,14 +1089,6 @@ 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 {
+2 -8
View File
@@ -188,9 +188,7 @@ impl PlatformWindow for TestWindow {
fn set_app_id(&mut self, _app_id: &str) {}
fn set_background_appearance(&mut self, _background: WindowBackgroundAppearance) {
unimplemented!()
}
fn set_background_appearance(&self, _background: WindowBackgroundAppearance) {}
fn set_edited(&mut self, edited: bool) {
self.0.lock().edited = edited;
@@ -262,13 +260,9 @@ impl PlatformWindow for TestWindow {
unimplemented!()
}
fn start_system_move(&self) {
fn start_window_move(&self) {
unimplemented!()
}
fn should_render_window_controls(&self) -> bool {
false
}
}
pub(crate) struct TestAtlasState {
+2 -18
View File
@@ -274,7 +274,7 @@ impl WindowsWindow {
handle,
hide_title_bar,
display,
transparent: params.window_background != WindowBackgroundAppearance::Opaque,
transparent: true,
executor,
current_cursor,
};
@@ -511,9 +511,7 @@ impl PlatformWindow for WindowsWindow {
.ok();
}
fn set_app_id(&mut self, _app_id: &str) {}
fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
self.0
.state
.borrow_mut()
@@ -521,12 +519,6 @@ impl PlatformWindow for WindowsWindow {
.update_transparency(background_appearance != WindowBackgroundAppearance::Opaque);
}
// todo(windows)
fn set_edited(&mut self, _edited: bool) {}
// todo(windows)
fn show_character_palette(&self) {}
fn minimize(&self) {
unsafe { ShowWindowAsync(self.0.hwnd, SW_MINIMIZE).ok().log_err() };
}
@@ -645,14 +637,6 @@ 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)]
+48 -20
View File
@@ -1,19 +1,20 @@
use crate::{
hash, point, prelude::*, px, size, transparent_black, Action, AnyDrag, AnyElement, AnyTooltip,
AnyView, AppContext, Arena, Asset, AsyncWindowContext, AvailableSpace, Bounds, BoxShadow,
Context, Corners, CursorStyle, DevicePixels, DispatchActionListener, DispatchNodeId,
DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter, FileDropEvent, Flatten,
FontId, Global, GlobalElementId, GlyphId, Hsla, ImageData, InputHandler, IsZero, KeyBinding,
KeyContext, KeyDownEvent, KeyEvent, KeyMatch, KeymatchResult, Keystroke, KeystrokeEvent,
LayoutId, LineLayoutIndex, Model, ModelContext, Modifiers, ModifiersChangedEvent,
MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent, Path, Pixels,
PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow, Point,
PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams, RenderImageParams,
RenderSvgParams, ScaledPixels, Scene, Shadow, SharedString, Size, StrikethroughStyle, Style,
SubscriberSet, Subscription, TaffyLayoutEngine, Task, TextStyle, TextStyleRefinement,
TransformationMatrix, Underline, UnderlineStyle, View, VisualContext, WeakView,
WindowAppearance, WindowBackgroundAppearance, WindowBounds, WindowOptions, WindowParams,
WindowTextSystem, SUBPIXEL_VARIANTS,
Context, Corners, CursorStyle, Decorations, DevicePixels, DispatchActionListener,
DispatchNodeId, DispatchTree, DisplayId, Edges, Effect, Entity, EntityId, EventEmitter,
FileDropEvent, Flatten, FontId, Global, GlobalElementId, GlyphId, Hsla, ImageData,
InputHandler, IsZero, KeyBinding, KeyContext, KeyDownEvent, KeyEvent, KeyMatch, KeymatchResult,
Keystroke, KeystrokeEvent, LayoutId, LineLayoutIndex, Model, ModelContext, Modifiers,
ModifiersChangedEvent, MonochromeSprite, MouseButton, MouseEvent, MouseMoveEvent, MouseUpEvent,
Path, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler,
PlatformWindow, Point, PolychromeSprite, PromptLevel, Quad, Render, RenderGlyphParams,
RenderImageParams, RenderSvgParams, ResizeEdge, ScaledPixels, Scene, Shadow, SharedString,
Size, StrikethroughStyle, Style, SubscriberSet, Subscription, TaffyLayoutEngine, Task,
TextStyle, TextStyleRefinement, TransformationMatrix, Underline, UnderlineStyle, View,
VisualContext, WeakView, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
WindowControls, WindowDecorations, WindowOptions, WindowParams, WindowTextSystem,
SUBPIXEL_VARIANTS,
};
use anyhow::{anyhow, Context as _, Result};
use collections::{FxHashMap, FxHashSet};
@@ -610,7 +611,10 @@ fn default_bounds(display_id: Option<DisplayId>, cx: &mut AppContext) -> Bounds<
cx.active_window()
.and_then(|w| w.update(cx, |_, cx| cx.bounds()).ok())
.map(|bounds| bounds.map_origin(|origin| origin + DEFAULT_WINDOW_OFFSET))
.map(|mut bounds| {
bounds.origin += DEFAULT_WINDOW_OFFSET;
bounds
})
.unwrap_or_else(|| {
let display = display_id
.map(|id| cx.find_display(id))
@@ -639,6 +643,7 @@ impl Window {
window_background,
app_id,
window_min_size,
window_decorations,
} = options;
let bounds = window_bounds
@@ -654,7 +659,6 @@ impl Window {
focus,
show,
display_id,
window_background,
window_min_size,
},
)?;
@@ -672,6 +676,10 @@ impl Window {
let next_frame_callbacks: Rc<RefCell<Vec<FrameCallback>>> = Default::default();
let last_input_timestamp = Rc::new(Cell::new(Instant::now()));
platform_window
.request_decorations(window_decorations.unwrap_or(WindowDecorations::Server));
platform_window.set_background_appearance(window_background);
if let Some(ref window_open_state) = window_bounds {
match window_open_state {
WindowBounds::Fullscreen(_) => platform_window.toggle_fullscreen(),
@@ -990,6 +998,16 @@ impl<'a> WindowContext<'a> {
self.window.platform_window.is_maximized()
}
/// request a certain window decoration (Wayland)
pub fn request_decorations(&self, decorations: WindowDecorations) {
self.window.platform_window.request_decorations(decorations);
}
/// Start a window resize operation (Wayland)
pub fn start_window_resize(&self, edge: ResizeEdge) {
self.window.platform_window.start_window_resize(edge);
}
/// Return the `WindowBounds` to indicate that how a window should be opened
/// after it has been closed
pub fn window_bounds(&self) -> WindowBounds {
@@ -1217,13 +1235,23 @@ impl<'a> WindowContext<'a> {
/// Tells the compositor to take control of window movement (Wayland and X11)
///
/// Events may not be received during a move operation.
pub fn start_system_move(&self) {
self.window.platform_window.start_system_move()
pub fn start_window_move(&self) {
self.window.platform_window.start_window_move()
}
/// When using client side decorations, set this to the width of the invisible decorations (Wayland and X11)
pub fn set_client_inset(&self, inset: Pixels) {
self.window.platform_window.set_client_inset(inset);
}
/// 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()
pub fn window_decorations(&self) -> Decorations {
self.window.platform_window.window_decorations()
}
/// Returns which window controls are currently visible (Wayland)
pub fn window_controls(&self) -> WindowControls {
self.window.platform_window.window_controls()
}
/// Updates the window's title at the platform level.
@@ -1237,7 +1265,7 @@ impl<'a> WindowContext<'a> {
}
/// Sets the window background appearance.
pub fn set_background_appearance(&mut self, background_appearance: WindowBackgroundAppearance) {
pub fn set_background_appearance(&self, background_appearance: WindowBackgroundAppearance) {
self.window
.platform_window
.set_background_appearance(background_appearance);