linux/x11: Fix gap when tiling windows side by side (#13859)

By leveraging the `_GTK_EDGE_CONSTRAINTS` atom we can get all four
booleans for the `Tiling` struct and figure out which side is free when
the window is tiled to half of the screen.

For the logic behind the `_GTK_EDGE_CONSTRAINTS` see:
-
8e9d13aa3b/src/x11/window-x11.c (L65-L75)
-
8e9d13aa3b/src/x11/window-x11.c (L1205-L1231)

(I used Claude 3.5 Sonnet with our code and these pieces from `mutter`
to generate the Rust code, that was pretty sweet)

This fixes the gap in the middle when a GPUI window is tiled to the left
and another window to the right.

It's not _perfect_ but it looks a lot better.

Here's a diff that makes it look better:

```diff
diff --git a/crates/gpui/examples/window_shadow.rs b/crates/gpui/examples/window_shadow.rs
index 122231f6b..7fa29dadc 100644
--- a/crates/gpui/examples/window_shadow.rs
+++ b/crates/gpui/examples/window_shadow.rs
@@ -72,8 +72,8 @@ impl Render for WindowShadow {
                     .when(!(tiling.top || tiling.left), |div| div.rounded_tl(rounding))
                     .when(!tiling.top, |div| div.pt(shadow_size))
                     .when(!tiling.bottom, |div| div.pb(shadow_size))
-                    .when(!tiling.left, |div| div.pl(shadow_size))
-                    .when(!tiling.right, |div| div.pr(shadow_size))
+                    .when(!tiling.left, |div| div.pl(shadow_size - border_size))
+                    .when(!tiling.right, |div| div.pr(shadow_size - border_size))
                     .on_mouse_move(|_e, cx| cx.refresh())
                     .on_mouse_down(MouseButton::Left, move |e, cx| {
                         let size = cx.window_bounds().get_bounds().size;
```

But that makes it look weird on Wayland, so I didn't do it.

I think it's fine for now. Chromium looks bad and has a gap, so we're
already better.

## Before

![before_1](https://github.com/zed-industries/zed/assets/1185253/875c5cdd-c0be-4295-beb0-bb9ba5beaa52)


![before_2](https://github.com/zed-industries/zed/assets/1185253/0b96be70-4c34-4e99-aeb2-ab741171ad14)

## After

![after_1](https://github.com/zed-industries/zed/assets/1185253/aa51da77-daf1-4ef8-a33f-a83731e0c7e1)

![after_2](https://github.com/zed-industries/zed/assets/1185253/8ce7902d-90b6-4f06-ba2c-626e643abe56)


Release Notes:

- N/A
This commit is contained in:
Thorsten Ball 2024-07-05 15:02:14 +02:00 committed by GitHub
parent c4dbe32f20
commit e69f9d6cf9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 99 additions and 17 deletions

View File

@ -86,6 +86,49 @@ impl ResizeEdge {
}
}
#[derive(Debug)]
struct EdgeConstraints {
top_tiled: bool,
#[allow(dead_code)]
top_resizable: bool,
right_tiled: bool,
#[allow(dead_code)]
right_resizable: bool,
bottom_tiled: bool,
#[allow(dead_code)]
bottom_resizable: bool,
left_tiled: bool,
#[allow(dead_code)]
left_resizable: bool,
}
impl EdgeConstraints {
fn from_atom(atom: u32) -> Self {
EdgeConstraints {
top_tiled: (atom & (1 << 0)) != 0,
top_resizable: (atom & (1 << 1)) != 0,
right_tiled: (atom & (1 << 2)) != 0,
right_resizable: (atom & (1 << 3)) != 0,
bottom_tiled: (atom & (1 << 4)) != 0,
bottom_resizable: (atom & (1 << 5)) != 0,
left_tiled: (atom & (1 << 6)) != 0,
left_resizable: (atom & (1 << 7)) != 0,
}
}
fn to_tiling(&self) -> Tiling {
Tiling {
top: self.top_tiled,
right: self.right_tiled,
bottom: self.bottom_tiled,
left: self.left_tiled,
}
}
}
#[derive(Debug)]
struct Visual {
id: xproto::Visualid,
@ -198,6 +241,7 @@ pub struct X11WindowState {
active: bool,
fullscreen: bool,
decorations: WindowDecorations,
edge_constraints: Option<EdgeConstraints>,
pub handle: AnyWindowHandle,
last_insets: [u32; 4],
}
@ -497,6 +541,7 @@ impl X11WindowState {
destroyed: false,
decorations: WindowDecorations::Server,
last_insets: [0, 0, 0, 0],
edge_constraints: None,
counter_id: sync_request_counter,
last_sync_counter: None,
refresh_rate,
@ -686,10 +731,32 @@ 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
|| event.atom == state.atoms._GTK_EDGE_CONSTRAINTS
{
if event.atom == state.atoms._NET_WM_STATE {
self.set_wm_properties(state);
} else if event.atom == state.atoms._GTK_EDGE_CONSTRAINTS {
self.set_edge_constraints(state);
}
}
fn set_edge_constraints(&self, mut state: std::cell::RefMut<X11WindowState>) {
let reply = self
.xcb_connection
.get_property(
false,
self.x_window,
state.atoms._GTK_EDGE_CONSTRAINTS,
xproto::AtomEnum::CARDINAL,
0,
4,
)
.unwrap()
.reply()
.unwrap();
if reply.value_len != 0 {
let atom = u32::from_ne_bytes(reply.value[0..4].try_into().unwrap());
let edge_constraints = EdgeConstraints::from_atom(atom);
state.edge_constraints.replace(edge_constraints);
}
}
@ -1194,15 +1261,19 @@ impl PlatformWindow for X11Window {
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 {
let tiling = if let Some(edge_constraints) = &state.edge_constraints {
edge_constraints.to_tiling()
} else {
// https://source.chromium.org/chromium/chromium/src/+/main:ui/ozone/platform/x11/x11_window.cc;l=2519;drc=1f14cc876cc5bf899d13284a12c451498219bb2d
Tiling {
top: state.maximized_vertical,
bottom: state.maximized_vertical,
left: state.maximized_horizontal,
right: state.maximized_horizontal,
},
}
}
};
Decorations::Client { tiling }
}
}
}
@ -1212,17 +1283,26 @@ impl PlatformWindow for X11Window {
let dp = (inset.0 * state.scale_factor) as u32;
let (left, right) = if state.maximized_horizontal {
(0, 0)
let insets = if let Some(edge_constraints) = &state.edge_constraints {
let left = if edge_constraints.left_tiled { 0 } else { dp };
let top = if edge_constraints.top_tiled { 0 } else { dp };
let right = if edge_constraints.right_tiled { 0 } else { dp };
let bottom = if edge_constraints.bottom_tiled { 0 } else { dp };
[left, right, top, bottom]
} else {
(dp, dp)
let (left, right) = if state.maximized_horizontal {
(0, 0)
} else {
(dp, dp)
};
let (top, bottom) = if state.maximized_vertical {
(0, 0)
} else {
(dp, dp)
};
[left, right, top, bottom]
};
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;

View File

@ -6491,6 +6491,8 @@ pub fn client_side_decorations(element: impl IntoElement, cx: &mut WindowContext
cx.set_client_inset(theme::CLIENT_SIDE_DECORATION_SHADOW);
}
println!("decorations: {:?}", decorations);
struct GlobalResizeEdge(ResizeEdge);
impl Global for GlobalResizeEdge {}