← luna

Commit ee4f8d4

8
files
+445
added
-37
deleted
@@ -454,6 +454,7 @@ dependencies = [
454 454 "libluna-render",
455 455 "rustix 0.38.44",
456 456 "wayland-protocols",
457 + "wayland-protocols-wlr",
457 458 "wayland-server",
458 459 ]
459 460
@@ -669,6 +670,7 @@ dependencies = [
669 670 "wayland-client",
670 671 "wayland-protocols",
671 672 "wayland-scanner",
673 + "wayland-server",
672 674 ]
673 675
674 676 [[package]]
@@ -19,4 +19,5 @@ libluna-platform.workspace = true
19 19
20 20 wayland-server = "0.31"
21 21 wayland-protocols = { version = "0.32", features = ["server"] }
22 +wayland-protocols-wlr = { version = "0.3", features = ["server"] }
22 23 rustix = { version = "0.38", features = ["event", "fs", "time", "mm"] }
@@ -4,11 +4,13 @@ use std::sync::Mutex;
4 4
5 5 use libluna_render::ImageData;
6 6 use wayland_protocols::xdg::shell::server::{xdg_popup, xdg_surface, xdg_toplevel};
7 +use wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_surface_v1;
7 8 use wayland_server::{
8 9 protocol::{wl_buffer, wl_callback, wl_compositor, wl_region, wl_surface},
9 10 Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource,
10 11 };
11 12
13 +use crate::layer::LayerData;
12 14 use crate::shell::{XdgSurfaceData, XdgToplevelData};
13 15 use crate::shm::BufferData;
14 16 use crate::state::{State, Window};
@@ -23,6 +25,8 @@ pub struct SurfaceData {
23 25 pub buffer: Mutex<Option<wl_buffer::WlBuffer>>,
24 26 /// Slot okna w modelu (przydzielany przy pierwszym commicie).
25 27 pub slot: Mutex<Option<usize>>,
28 + /// Powiązana powierzchnia warstwowa (`wlr-layer-shell`), jeśli klient ją utworzył.
29 + pub layer: Mutex<Option<zwlr_layer_surface_v1::ZwlrLayerSurfaceV1>>,
26 30 }
27 31
28 32 impl SurfaceData {
@@ -32,6 +36,7 @@ impl SurfaceData {
32 36 xdg_surface: Mutex::new(None),
33 37 buffer: Mutex::new(None),
34 38 slot: Mutex::new(None),
39 + layer: Mutex::new(None),
35 40 }
36 41 }
37 42 }
@@ -138,8 +143,13 @@ impl Dispatch<wl_surface::WlSurface, SurfaceData> for State {
138 143 }
139 144 }
140 145
141 -/// Skomponuj powierzchnię do modelu okien (toplevel lub popup z buforem `wl_shm`).
146 +/// Skomponuj powierzchnię do modelu okien (toplevel, popup lub powierzchnia warstwowa).
142 147 fn composite(state: &mut State, surface: &wl_surface::WlSurface, data: &SurfaceData) {
148 + // Powierzchnia warstwowa (panel/dock) — własna ścieżka (bez xdg_surface).
149 + if let Some(ls) = data.layer.lock().unwrap().clone() {
150 + composite_layer(state, surface, data, &ls);
151 + return;
152 + }
143 153 let Some(xdg_surface) = data.xdg_surface.lock().unwrap().clone() else {
144 154 return;
145 155 };
@@ -185,51 +195,144 @@ fn composite(state: &mut State, surface: &wl_surface::WlSurface, data: &SurfaceD
185 195 String::new()
186 196 };
187 197
198 + let count = state.windows.lock().unwrap().len();
199 + let (px, py) = popup_xy.unwrap_or((60.0 + count as f64 * 36.0, 60.0 + count as f64 * 36.0));
200 + let (_, created) = upsert_window(
201 + state, surface, data, px, py, w, h, z, &title, is_popup, false, false, image,
202 + );
203 + // Popup: wyślij konfigurację (rozmiar + pozycja).
204 + if is_popup && created {
205 + send_popup_configure(state, &xdg_surface, popup_xy.unwrap_or((0.0, 0.0)), w, h);
206 + }
207 + // Pozwól klientowi ponownie użyć bufora.
208 + let _ = buffer.send_event(wl_buffer::Event::Release);
209 +}
210 +
211 +/// Skomponuj powierzchnię warstwową (`wlr-layer-shell`): konfiguracja + kotwiczenie.
212 +fn composite_layer(
213 + state: &mut State,
214 + surface: &wl_surface::WlSurface,
215 + data: &SurfaceData,
216 + ls: &zwlr_layer_surface_v1::ZwlrLayerSurfaceV1,
217 +) {
218 + let Some(ld) = ls.data::<LayerData>() else {
219 + return;
220 + };
221 + let (out_w, out_h) = output_size(state);
222 + let buffer = data.buffer.lock().unwrap().clone();
223 + // Pierwszy `commit` (zwykle bez bufora) → wyślij `configure` z rozmiarem.
224 + if !*ld.configured.lock().unwrap() {
225 + let st = *ld.state.lock().unwrap();
226 + let (cw, ch) = st.configure_size(out_w, out_h);
227 + let serial = state.next_serial();
228 + let _ = ls.send_event(zwlr_layer_surface_v1::Event::Configure {
229 + serial,
230 + width: cw.max(0) as u32,
231 + height: ch.max(0) as u32,
232 + });
233 + *ld.configured.lock().unwrap() = true;
234 + if buffer.is_none() {
235 + return;
236 + }
237 + }
238 + let Some(buffer) = buffer else {
239 + return;
240 + };
241 + let Some(buf_data) = buffer.data::<BufferData>() else {
242 + return;
243 + };
244 + let (w, h) = (buf_data.width, buf_data.height);
245 + let Some(rgba) = buf_data.to_rgba() else {
246 + return;
247 + };
248 + let image = std::sync::Arc::new(ImageData::new(w as u32, h as u32, rgba));
249 + let st = *ld.state.lock().unwrap();
250 + let (x, y) = st.position(out_w, out_h, w as f64, h as f64);
251 + let z = crate::layer::layer_z(st.layer);
252 + upsert_window(
253 + state, surface, data, x, y, w, h, z, "", false, true, true, image,
254 + );
255 + let _ = buffer.send_event(wl_buffer::Event::Release);
256 +}
257 +
258 +/// Dodaje nowe okno lub aktualizuje istniejące (slot trzymany w [`SurfaceData`]).
259 +#[allow(clippy::too_many_arguments)]
260 +fn upsert_window(
261 + state: &mut State,
262 + surface: &wl_surface::WlSurface,
263 + data: &SurfaceData,
264 + x: f64,
265 + y: f64,
266 + w: i32,
267 + h: i32,
268 + z: i32,
269 + title: &str,
270 + popup: bool,
271 + layer: bool,
272 + reposition: bool,
273 + image: std::sync::Arc<ImageData>,
274 +) -> (usize, bool) {
188 275 let mut slot = data.slot.lock().unwrap();
189 276 let mut windows = state.windows.lock().unwrap();
190 277 let mut created = false;
191 278 let idx = match *slot {
192 - Some(i) => i,
279 + Some(i) => {
280 + if let Some(win) = windows.get_mut(i) {
281 + // Pozycję stałą okna zmieniamy tylko dla powierzchni kotwiczonych.
282 + if reposition {
283 + win.x = x;
284 + win.y = y;
285 + win.z = z;
286 + }
287 + win.width = w;
288 + win.height = h;
289 + win.mapped = true;
290 + win.popup = popup;
291 + win.layer = layer;
292 + if !title.is_empty() {
293 + win.title = title.to_owned();
294 + }
295 + win.data = image;
296 + }
297 + i
298 + }
193 299 None => {
194 300 let i = windows.len();
195 - let (px, py) = popup_xy.unwrap_or((60.0 + i as f64 * 36.0, 60.0 + i as f64 * 36.0));
196 301 windows.push(Window {
197 - x: px,
198 - y: py,
302 + x,
303 + y,
199 304 width: w,
200 305 height: h,
201 306 z,
202 - title: title.clone(),
203 - popup: is_popup,
307 + title: title.to_owned(),
308 + popup,
309 + layer,
204 310 mapped: true,
205 - data: image.clone(),
311 + data: image,
206 312 });
207 313 *slot = Some(i);
208 314 created = true;
209 315 i
210 316 }
211 317 };
212 - if let Some(window) = windows.get_mut(idx) {
213 - window.width = w;
214 - window.height = h;
215 - window.mapped = true;
216 - if !title.is_empty() {
217 - window.title = title;
218 - }
219 - window.data = image;
220 - }
221 318 drop(windows);
222 319 drop(slot);
223 320 // Zarejestruj powierzchnię okna (indeks = slot) do trasowania wejścia.
224 321 if created && state.slots.len() == idx {
225 322 state.slots.push(surface.clone());
226 323 }
227 - // Popup: wyślij konfigurację (rozmiar + pozycja).
228 - if is_popup && created {
229 - send_popup_configure(state, &xdg_surface, popup_xy.unwrap_or((0.0, 0.0)), w, h);
230 - }
231 - // Pozwól klientowi ponownie użyć bufora.
232 - let _ = buffer.send_event(wl_buffer::Event::Release);
324 + (idx, created)
325 +}
326 +
327 +/// Rozmiar monitora (na razie pierwszego; model gotowy na wiele).
328 +fn output_size(state: &State) -> (f64, f64) {
329 + state
330 + .outputs
331 + .lock()
332 + .unwrap()
333 + .first()
334 + .map(|o| (o.width as f64, o.height as f64))
335 + .unwrap_or((1280.0, 800.0))
233 336 }
234 337
235 338 /// Pozycja popupu względem treści rodzica (prostokąt kotwicy + offset).
236 339 new file mode 100644
@@ -0,0 +1,291 @@
1 +//! `zwlr_layer_shell_v1` / `zwlr_layer_surface_v1` — powierzchnie warstwowe (panele, docki).
2 +//!
3 +//! To fundament przyszłego pulpitu Luna: pasek górny, dock i tło to powierzchnie
4 +//! kotwiczone do krawędzi monitora, niezależne od zwykłych okien `xdg_toplevel`.
5 +
6 +use std::sync::Mutex;
7 +
8 +use wayland_protocols_wlr::layer_shell::v1::server::{
9 + zwlr_layer_shell_v1 as layer_shell, zwlr_layer_surface_v1 as layer_surface,
10 +};
11 +use wayland_server::protocol::wl_surface::WlSurface;
12 +use wayland_server::{
13 + Client, DataInit, Dispatch, DisplayHandle, GlobalDispatch, New, Resource, WEnum,
14 +};
15 +
16 +use crate::compositor::SurfaceData;
17 +use crate::state::State;
18 +
19 +/// Kotwice krawędzi (`zwlr_layer_surface_v1.anchor`).
20 +pub const ANCHOR_TOP: u32 = 1;
21 +pub const ANCHOR_BOTTOM: u32 = 2;
22 +pub const ANCHOR_LEFT: u32 = 4;
23 +pub const ANCHOR_RIGHT: u32 = 8;
24 +
25 +/// Zebrane właściwości powierzchni warstwowej.
26 +#[derive(Debug, Clone, Copy, Default)]
27 +pub struct LayerState {
28 + /// Warstwa (`background`/`bottom`/`top`/`overlay`).
29 + pub layer: u32,
30 + /// Bitmaska kotwic.
31 + pub anchor: u32,
32 + /// Żądana szerokość (0 = wybiera kompozytor/klient).
33 + pub width: i32,
34 + /// Żądana wysokość (0 = wybiera kompozytor/klient).
35 + pub height: i32,
36 + /// Strefa wyłączna (zarezerwowane miejsce na krawędzi).
37 + pub exclusive_zone: i32,
38 + pub margin_top: i32,
39 + pub margin_right: i32,
40 + pub margin_bottom: i32,
41 + pub margin_left: i32,
42 + /// `none`/`exclusive`/`on_demand`.
43 + pub keyboard_interactivity: u32,
44 +}
45 +
46 +impl LayerState {
47 + fn anchors(&self) -> (bool, bool, bool, bool) {
48 + (
49 + self.anchor & ANCHOR_LEFT != 0,
50 + self.anchor & ANCHOR_RIGHT != 0,
51 + self.anchor & ANCHOR_TOP != 0,
52 + self.anchor & ANCHOR_BOTTOM != 0,
53 + )
54 + }
55 +
56 + /// Rozmiar, jaki kompozytor proponuje klientowi w `configure`.
57 + pub fn configure_size(&self, out_w: f64, out_h: f64) -> (i32, i32) {
58 + let (left, right, top, bottom) = self.anchors();
59 + let w = if left && right && self.width == 0 {
60 + (out_w - self.margin_left as f64 - self.margin_right as f64).max(0.0) as i32
61 + } else {
62 + self.width
63 + };
64 + let h = if top && bottom && self.height == 0 {
65 + (out_h - self.margin_top as f64 - self.margin_bottom as f64).max(0.0) as i32
66 + } else {
67 + self.height
68 + };
69 + (w, h)
70 + }
71 +
72 + /// Pozycja lewego-górnego narożnika na monitorze (z uwzględnieniem kotwic i marginesów).
73 + pub fn position(&self, out_w: f64, out_h: f64, w: f64, h: f64) -> (f64, f64) {
74 + let (left, right, top, bottom) = self.anchors();
75 + let x = if left {
76 + self.margin_left as f64
77 + } else if right {
78 + out_w - self.margin_right as f64 - w
79 + } else {
80 + ((out_w - w) / 2.0).max(0.0)
81 + };
82 + let y = if top {
83 + self.margin_top as f64
84 + } else if bottom {
85 + out_h - self.margin_bottom as f64 - h
86 + } else {
87 + ((out_h - h) / 2.0).max(0.0)
88 + };
89 + (x, y)
90 + }
91 +}
92 +
93 +/// Dane powierzchni warstwowej.
94 +pub struct LayerData {
95 + /// Powiązana powierzchnia `wl_surface`.
96 + pub surface: WlSurface,
97 + /// Właściwości (aktualizowane przez `set_*`).
98 + pub state: Mutex<LayerState>,
99 + /// Czy wysłano już `configure`.
100 + pub configured: Mutex<bool>,
101 +}
102 +
103 +impl LayerData {
104 + pub fn new(surface: WlSurface, layer: u32) -> Self {
105 + Self {
106 + surface,
107 + state: Mutex::new(LayerState {
108 + layer,
109 + ..LayerState::default()
110 + }),
111 + configured: Mutex::new(false),
112 + }
113 + }
114 +}
115 +
116 +/// Kolejność rysowania wynikająca z warstwy (panele nad oknami, tło pod nimi).
117 +pub fn layer_z(layer: u32) -> i32 {
118 + match layer {
119 + 0 => -200, // background
120 + 1 => -100, // bottom
121 + 2 => 1_000_000, // top
122 + _ => 2_000_000, // overlay
123 + }
124 +}
125 +
126 +fn anchor_bits(a: WEnum<layer_surface::Anchor>) -> u32 {
127 + match a {
128 + WEnum::Value(v) => v.bits(),
129 + WEnum::Unknown(v) => v,
130 + }
131 +}
132 +
133 +impl GlobalDispatch<layer_shell::ZwlrLayerShellV1, ()> for State {
134 + fn bind(
135 + _state: &mut Self,
136 + _handle: &DisplayHandle,
137 + _client: &Client,
138 + resource: New<layer_shell::ZwlrLayerShellV1>,
139 + _data: &(),
140 + data_init: &mut DataInit<'_, Self>,
141 + ) {
142 + data_init.init(resource, ());
143 + }
144 +}
145 +
146 +impl Dispatch<layer_shell::ZwlrLayerShellV1, ()> for State {
147 + fn request(
148 + _state: &mut Self,
149 + _client: &Client,
150 + _resource: &layer_shell::ZwlrLayerShellV1,
151 + request: layer_shell::Request,
152 + _data: &(),
153 + _handle: &DisplayHandle,
154 + data_init: &mut DataInit<'_, Self>,
155 + ) {
156 + if let layer_shell::Request::GetLayerSurface {
157 + id, surface, layer, ..
158 + } = request
159 + {
160 + let layer = match layer {
161 + WEnum::Value(v) => v as u32,
162 + WEnum::Unknown(v) => v,
163 + };
164 + let ls = data_init.init(id, LayerData::new(surface.clone(), layer));
165 + // Zapamiętaj powiązanie po stronie `wl_surface` (potrzebne przy `commit`).
166 + if let Some(sd) = surface.data::<SurfaceData>() {
167 + *sd.layer.lock().unwrap() = Some(ls);
168 + }
169 + }
170 + }
171 +}
172 +
173 +impl Dispatch<layer_surface::ZwlrLayerSurfaceV1, LayerData> for State {
174 + fn request(
175 + state: &mut Self,
176 + _client: &Client,
177 + resource: &layer_surface::ZwlrLayerSurfaceV1,
178 + request: layer_surface::Request,
179 + data: &LayerData,
180 + _handle: &DisplayHandle,
181 + _data_init: &mut DataInit<'_, Self>,
182 + ) {
183 + let mut st = data.state.lock().unwrap();
184 + match request {
185 + layer_surface::Request::SetSize { width, height } => {
186 + st.width = width as i32;
187 + st.height = height as i32;
188 + }
189 + layer_surface::Request::SetAnchor { anchor } => {
190 + st.anchor = anchor_bits(anchor);
191 + }
192 + layer_surface::Request::SetExclusiveZone { zone } => {
193 + st.exclusive_zone = zone;
194 + }
195 + layer_surface::Request::SetMargin {
196 + top,
197 + right,
198 + bottom,
199 + left,
200 + } => {
201 + st.margin_top = top;
202 + st.margin_right = right;
203 + st.margin_bottom = bottom;
204 + st.margin_left = left;
205 + }
206 + layer_surface::Request::SetKeyboardInteractivity {
207 + keyboard_interactivity,
208 + } => {
209 + st.keyboard_interactivity = match keyboard_interactivity {
210 + WEnum::Value(v) => v as u32,
211 + WEnum::Unknown(v) => v,
212 + };
213 + }
214 + layer_surface::Request::SetLayer { layer } => {
215 + st.layer = match layer {
216 + WEnum::Value(v) => v as u32,
217 + WEnum::Unknown(v) => v,
218 + };
219 + }
220 + layer_surface::Request::Destroy => {
221 + // Zdejmij powierzchnię z widoku.
222 + drop(st);
223 + if let Some(sd) = data.surface.data::<SurfaceData>() {
224 + if let Some(slot) = *sd.slot.lock().unwrap() {
225 + if let Some(w) = state.windows.lock().unwrap().get_mut(slot) {
226 + w.mapped = false;
227 + }
228 + }
229 + }
230 + let _ = resource;
231 + }
232 + // `get_popup` i pozostałe: poza zakresem MVP.
233 + _ => {}
234 + }
235 + }
236 +}
237 +
238 +#[cfg(test)]
239 +mod tests {
240 + use super::*;
241 +
242 + fn st(anchor: u32, width: i32, height: i32) -> LayerState {
243 + LayerState {
244 + anchor,
245 + width,
246 + height,
247 + ..LayerState::default()
248 + }
249 + }
250 +
251 + #[test]
252 + fn top_panel_stretches_full_width() {
253 + // Górny pasek: TOP|LEFT|RIGHT, wysokość 32, szerokość 0 → pełna szerokość.
254 + let s = st(ANCHOR_TOP | ANCHOR_LEFT | ANCHOR_RIGHT, 0, 32);
255 + assert_eq!(s.configure_size(1280.0, 800.0), (1280, 32));
256 + assert_eq!(s.position(1280.0, 800.0, 1280.0, 32.0), (0.0, 0.0));
257 + }
258 +
259 + #[test]
260 + fn bottom_right_dock_is_placed_at_corner() {
261 + // Dock przy prawym-dolnym narożniku, marginesy 8.
262 + let mut s = st(ANCHOR_BOTTOM | ANCHOR_RIGHT, 200, 400);
263 + s.margin_right = 8;
264 + s.margin_bottom = 8;
265 + assert_eq!(s.configure_size(1280.0, 800.0), (200, 400));
266 + assert_eq!(s.position(1280.0, 800.0, 200.0, 400.0), (1072.0, 392.0));
267 + }
268 +
269 + #[test]
270 + fn unanchored_surface_is_centered() {
271 + let s = st(0, 400, 200);
272 + assert_eq!(s.position(1280.0, 800.0, 400.0, 200.0), (440.0, 300.0));
273 + }
274 +
275 + #[test]
276 + fn exclusive_zone_does_not_resize_below_zero() {
277 + let mut s = st(ANCHOR_TOP | ANCHOR_LEFT | ANCHOR_RIGHT, 0, 0);
278 + // Marginesy większe niż ekran nie mogą dać ujemnego rozmiaru.
279 + s.margin_left = 5000;
280 + assert_eq!(s.configure_size(1280.0, 800.0).0, 0);
281 + }
282 +
283 + #[test]
284 + fn layer_order_puts_top_above_windows_and_background_below() {
285 + assert!(layer_z(2) > 0);
286 + assert!(layer_z(3) > layer_z(2));
287 + assert!(layer_z(0) < 0);
288 + // `bottom` leży nad `background`.
289 + assert!(layer_z(1) > layer_z(0));
290 + }
291 +}
@@ -7,6 +7,7 @@
7 7
8 8 mod compositor;
9 9 mod input;
10 +mod layer;
10 11 mod monitors;
11 12 mod output;
12 13 mod seat;
@@ -20,6 +21,7 @@ use std::sync::Arc;
20 21
21 22 use libluna_core::Event;
22 23 use wayland_protocols::xdg::shell::server::xdg_wm_base;
24 +use wayland_protocols_wlr::layer_shell::v1::server::zwlr_layer_shell_v1;
23 25 use wayland_server::protocol::{wl_compositor, wl_output, wl_seat, wl_shm};
24 26 use wayland_server::{Display, ListeningSocket};
25 27
@@ -38,6 +40,7 @@ fn main() -> Result<(), Box<dyn Error>> {
38 40 handle.create_global::<State, wl_shm::WlShm, ()>(1, ());
39 41 handle.create_global::<State, wl_output::WlOutput, ()>(4, ());
40 42 handle.create_global::<State, xdg_wm_base::XdgWmBase, ()>(1, ());
43 + handle.create_global::<State, zwlr_layer_shell_v1::ZwlrLayerShellV1, ()>(4, ());
41 44 handle.create_global::<State, wl_seat::WlSeat, ()>(1, ());
42 45
43 46 // Gniazdo nasłuchu: `wayland-0`, `wayland-1`, …
@@ -69,8 +69,8 @@ fn draw(frame: &mut Frame, model: &WindowModel) {
69 69 .fill_rect(w.content(), Color::from_rgba8(0x00, 0x00, 0x00, 0xff));
70 70 // Treść okna (bufor klienta).
71 71 frame.scene.image(w.content(), w.data.clone());
72 - // Dekoracja tylko dla topleveli (popupy jej nie mają).
73 - if !w.popup {
72 + // Dekoracja tylko dla topleveli (popupy i panele jej nie mają).
73 + if !w.popup && !w.layer {
74 74 frame
75 75 .scene
76 76 .fill_rect(w.titlebar(), Color::from_rgba8(0x2c, 0x2f, 0x38, 0xff));
@@ -104,13 +104,15 @@ fn draw(frame: &mut Frame, model: &WindowModel) {
104 104 );
105 105 }
106 106 }
107 - // Obramowanie całego okna.
108 - frame.scene.stroke_rounded_rect(
109 - w.outer(),
110 - 6.0,
111 - 1.0,
112 - Color::from_rgba8(0x50, 0x54, 0x60, 0xff),
113 - );
107 + // Obramowanie całego okna (tylko zwykłe okna).
108 + if !w.layer {
109 + frame.scene.stroke_rounded_rect(
110 + w.outer(),
111 + 6.0,
112 + 1.0,
113 + Color::from_rgba8(0x50, 0x54, 0x60, 0xff),
114 + );
115 + }
114 116 }
115 117 }
116 118
@@ -463,7 +463,10 @@ fn raise_and_focus(state: &mut State, slot: usize) {
463 463 {
464 464 let mut windows = state.windows.lock().unwrap();
465 465 if let Some(w) = windows.get_mut(slot) {
466 - w.z = z;
466 + // Powierzchnie warstwowe (panele/docki) trzymają stałą kolejność.
467 + if !w.layer {
468 + w.z = z;
469 + }
467 470 }
468 471 }
469 472 set_keyboard_focus(state, slot);
@@ -702,6 +705,7 @@ mod tests {
702 705 z: 1,
703 706 title: "Test".to_owned(),
704 707 popup: false,
708 + layer: false,
705 709 mapped: true,
706 710 data: Arc::new(ImageData::empty(200, 120)),
707 711 }
@@ -29,6 +29,8 @@ pub struct Window {
29 29 pub title: String,
30 30 /// Czy to `xdg_popup` (bez dekoracji, zamykany kliknięciem poza).
31 31 pub popup: bool,
32 + /// Czy to powierzchnia `wlr-layer-shell` (panel/dock; bez dekoracji, kotwiczona).
33 + pub layer: bool,
32 34 /// Czy powierzchnia jest widoczna (popup znika po `popup_done`).
33 35 pub mapped: bool,
34 36 /// Piksele (premultiplied RGBA8) ostatnio zacommitowanego bufora.
@@ -36,9 +38,9 @@ pub struct Window {
36 38 }
37 39
38 40 impl Window {
39 - /// Wysokość dekoracji (zero dla popupów).
41 + /// Wysokość dekoracji (zero dla popupów i powierzchni warstwowych).
40 42 fn bar(&self) -> f64 {
41 - if self.popup {
43 + if self.popup || self.layer {
42 44 0.0
43 45 } else {
44 46 TITLEBAR
@@ -72,7 +74,7 @@ impl Window {
72 74
73 75 /// Kwadratowy przycisk zamknięcia (tylko dla topleveli).
74 76 pub fn close_rect(&self) -> Rect {
75 - if self.popup {
77 + if self.popup || self.layer {
76 78 return Rect::ZERO;
77 79 }
78 80 Rect::from_xywh(
@@ -85,7 +87,7 @@ impl Window {
85 87
86 88 /// Uchwyt przeskalowania (prawy-dolny narożnik treści; tylko dla topleveli).
87 89 pub fn grip_rect(&self) -> Rect {
88 - if self.popup {
90 + if self.popup || self.layer {
89 91 return Rect::ZERO;
90 92 }
91 93 let c = self.content();