1//! The default fallback frame which is intended to show some very basic derocations.
2
3use std::mem;
4use std::sync::Arc;
5use std::time::Duration;
6use std::{error::Error, num::NonZeroU32};
7
8use crate::reexports::client::{
9 protocol::{wl_shm, wl_subsurface::WlSubsurface, wl_surface::WlSurface},
10 Dispatch, Proxy, QueueHandle,
11};
12use crate::reexports::csd_frame::{
13 DecorationsFrame, FrameAction, FrameClick, ResizeEdge, WindowManagerCapabilities, WindowState,
14};
15
16use crate::{
17 compositor::SurfaceData,
18 seat::pointer::CursorIcon,
19 shell::WaylandSurface,
20 shm::{slot::SlotPool, Shm},
21 subcompositor::{SubcompositorState, SubsurfaceData},
22};
23
24use wayland_backend::client::ObjectId;
25
26/// The size of the header bar.
27const HEADER_SIZE: u32 = 24;
28
29/// The size of the border.
30const BORDER_SIZE: u32 = 4;
31
32const HEADER: usize = 0;
33const TOP_BORDER: usize = 1;
34const RIGHT_BORDER: usize = 2;
35const BOTTOM_BORDER: usize = 3;
36const LEFT_BORDER: usize = 4;
37
38const BTN_ICON_COLOR: u32 = 0xFFCCCCCC;
39const BTN_HOVER_BG: u32 = 0xFF808080;
40
41const PRIMARY_COLOR_ACTIVE: u32 = 0xFF3A3A3A;
42const PRIMARY_COLOR_INACTIVE: u32 = 0xFF242424;
43
44/// The default ugly frame.
45#[derive(Debug)]
46pub struct FallbackFrame<State> {
47 /// The parent surface.
48 parent: WlSurface,
49
50 /// The latest window state.
51 state: WindowState,
52
53 /// The wm capabilities.
54 wm_capabilities: WindowManagerCapabilities,
55
56 /// Whether the frame is resizable.
57 resizable: bool,
58
59 /// Whether the frame is waiting for redraw.
60 dirty: bool,
61
62 /// The location of the mouse.
63 mouse_location: Location,
64
65 /// The location of the mouse.
66 mouse_coords: (i32, i32),
67
68 /// The frame rendering data. When `None` the frame is hidden.
69 render_data: Option<FrameRenderData>,
70
71 /// Whether the frame should sync with the parent.
72 ///
73 /// This should happen in reaction to scale or resize changes.
74 should_sync: bool,
75
76 /// The active scale factor of the frame.
77 scale_factor: f64,
78
79 /// The frame queue handle.
80 queue_handle: QueueHandle<State>,
81
82 /// The memory pool to use for drawing.
83 pool: SlotPool,
84
85 /// The subcompositor.
86 subcompositor: Arc<SubcompositorState>,
87
88 /// Buttons state.
89 buttons: [Option<UIButton>; 3],
90}
91
92impl<State> FallbackFrame<State>
93where
94 State: Dispatch<WlSurface, SurfaceData> + Dispatch<WlSubsurface, SubsurfaceData> + 'static,
95{
96 pub fn new(
97 parent: &impl WaylandSurface,
98 shm: &Shm,
99 subcompositor: Arc<SubcompositorState>,
100 queue_handle: QueueHandle<State>,
101 ) -> Result<Self, Box<dyn Error>> {
102 let parent = parent.wl_surface().clone();
103 let pool = SlotPool::new(1, shm)?;
104 let render_data = Some(FrameRenderData::new(&parent, &subcompositor, &queue_handle));
105
106 let wm_capabilities = WindowManagerCapabilities::all();
107 Ok(Self {
108 parent,
109 resizable: true,
110 state: WindowState::empty(),
111 wm_capabilities,
112 dirty: true,
113 scale_factor: 1.,
114 pool,
115 should_sync: true,
116 queue_handle,
117 subcompositor,
118 render_data,
119 mouse_location: Location::None,
120 mouse_coords: (0, 0),
121 buttons: Self::supported_buttons(wm_capabilities),
122 })
123 }
124
125 fn supported_buttons(wm_capabilities: WindowManagerCapabilities) -> [Option<UIButton>; 3] {
126 let maximize = wm_capabilities
127 .contains(WindowManagerCapabilities::MAXIMIZE)
128 .then_some(UIButton::Maximize);
129 let minimize = wm_capabilities
130 .contains(WindowManagerCapabilities::MINIMIZE)
131 .then_some(UIButton::Minimize);
132 [Some(UIButton::Close), maximize, minimize]
133 }
134
135 fn precise_location(
136 buttons: &[Option<UIButton>],
137 old: Location,
138 width: u32,
139 x: f64,
140 y: f64,
141 ) -> Location {
142 match old {
143 Location::Head | Location::Button(_) => Self::find_button(buttons, x, y, width),
144
145 Location::Top | Location::TopLeft | Location::TopRight => {
146 if x <= f64::from(BORDER_SIZE) {
147 Location::TopLeft
148 } else if x >= f64::from(width - BORDER_SIZE) {
149 Location::TopRight
150 } else {
151 Location::Top
152 }
153 }
154
155 Location::Bottom | Location::BottomLeft | Location::BottomRight => {
156 if x <= f64::from(BORDER_SIZE) {
157 Location::BottomLeft
158 } else if x >= f64::from(width - BORDER_SIZE) {
159 Location::BottomRight
160 } else {
161 Location::Bottom
162 }
163 }
164
165 other => other,
166 }
167 }
168
169 fn find_button(buttons: &[Option<UIButton>], x: f64, y: f64, w: u32) -> Location {
170 for (idx, &button) in buttons.iter().flatten().enumerate() {
171 let idx = idx as u32;
172 if w >= (idx + 1) * HEADER_SIZE
173 && x >= f64::from(w - (idx + 1) * HEADER_SIZE)
174 && x <= f64::from(w - idx * HEADER_SIZE)
175 && y <= f64::from(HEADER_SIZE)
176 && y >= f64::from(0)
177 {
178 return Location::Button(button);
179 }
180 }
181
182 Location::Head
183 }
184
185 #[inline]
186 fn part_index_for_surface(&mut self, surface_id: &ObjectId) -> Option<usize> {
187 self.render_data.as_ref()?.parts.iter().position(|part| &part.surface.id() == surface_id)
188 }
189
190 fn draw_buttons(
191 buttons: &[Option<UIButton>],
192 canvas: &mut [u8],
193 width: u32,
194 scale: u32,
195 is_active: bool,
196 mouse_location: &Location,
197 ) {
198 let scale = scale as usize;
199 for (idx, &button) in buttons.iter().flatten().enumerate() {
200 if width >= (idx + 1) as u32 * HEADER_SIZE {
201 if is_active && mouse_location == &Location::Button(button) {
202 Self::draw_button(
203 canvas,
204 idx * HEADER_SIZE as usize,
205 scale,
206 width as usize,
207 BTN_HOVER_BG.to_le_bytes(),
208 );
209 }
210 Self::draw_icon(
211 canvas,
212 width as usize,
213 idx * HEADER_SIZE as usize,
214 scale,
215 BTN_ICON_COLOR.to_le_bytes(),
216 button,
217 );
218 }
219 }
220 }
221
222 fn draw_button(
223 canvas: &mut [u8],
224 x_offset: usize,
225 scale: usize,
226 width: usize,
227 btn_color: [u8; 4],
228 ) {
229 let h = HEADER_SIZE as usize;
230 let x_start = width - h - x_offset;
231 // main square
232 for y in 0..h * scale {
233 let canvas = &mut canvas
234 [(x_start + y * width) * 4 * scale..(x_start + y * width + h) * scale * 4];
235 for pixel in canvas.chunks_exact_mut(4) {
236 pixel[0] = btn_color[0];
237 pixel[1] = btn_color[1];
238 pixel[2] = btn_color[2];
239 pixel[3] = btn_color[3];
240 }
241 }
242 }
243
244 fn draw_icon(
245 canvas: &mut [u8],
246 width: usize,
247 x_offset: usize,
248 scale: usize,
249 icon_color: [u8; 4],
250 icon: UIButton,
251 ) {
252 let h = HEADER_SIZE as usize;
253 let sh = scale * h;
254 let x_start = width - h - x_offset;
255
256 match icon {
257 UIButton::Close => {
258 // Draw black rectangle
259 for y in sh / 4..3 * sh / 4 {
260 let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale
261 ..(x_start + y * width + 3 * h / 4) * 4 * scale];
262 for pixel in line.chunks_exact_mut(4) {
263 pixel[0] = icon_color[0];
264 pixel[1] = icon_color[1];
265 pixel[2] = icon_color[2];
266 pixel[3] = icon_color[3];
267 }
268 }
269 }
270 UIButton::Maximize => {
271 // Draw an empty rectangle
272 for y in 2 * sh / 8..3 * sh / 8 {
273 let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale
274 ..(x_start + y * width + 3 * h / 4) * 4 * scale];
275 for pixel in line.chunks_exact_mut(4) {
276 pixel[0] = icon_color[0];
277 pixel[1] = icon_color[1];
278 pixel[2] = icon_color[2];
279 pixel[3] = icon_color[3];
280 }
281 }
282 for y in 3 * sh / 8..5 * sh / 8 {
283 let line = &mut canvas[(x_start + y * width + 2 * h / 8) * 4 * scale
284 ..(x_start + y * width + 3 * h / 8) * 4 * scale];
285 for pixel in line.chunks_exact_mut(4) {
286 pixel[0] = icon_color[0];
287 pixel[1] = icon_color[1];
288 pixel[2] = icon_color[2];
289 pixel[3] = icon_color[3];
290 }
291 let line = &mut canvas[(x_start + y * width + 5 * h / 8) * 4 * scale
292 ..(x_start + y * width + 6 * h / 8) * 4 * scale];
293 for pixel in line.chunks_exact_mut(4) {
294 pixel[0] = icon_color[0];
295 pixel[1] = icon_color[1];
296 pixel[2] = icon_color[2];
297 pixel[3] = icon_color[3];
298 }
299 }
300 for y in 5 * sh / 8..6 * sh / 8 {
301 let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale
302 ..(x_start + y * width + 3 * h / 4) * 4 * scale];
303 for pixel in line.chunks_exact_mut(4) {
304 pixel[0] = icon_color[0];
305 pixel[1] = icon_color[1];
306 pixel[2] = icon_color[2];
307 pixel[3] = icon_color[3];
308 }
309 }
310 }
311 UIButton::Minimize => {
312 // Draw an underline
313 for y in 5 * sh / 8..3 * sh / 4 {
314 let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale
315 ..(x_start + y * width + 3 * h / 4) * 4 * scale];
316 for pixel in line.chunks_exact_mut(4) {
317 pixel[0] = icon_color[0];
318 pixel[1] = icon_color[1];
319 pixel[2] = icon_color[2];
320 pixel[3] = icon_color[3];
321 }
322 }
323 }
324 }
325 }
326}
327
328impl<State> DecorationsFrame for FallbackFrame<State>
329where
330 State: Dispatch<WlSurface, SurfaceData> + Dispatch<WlSubsurface, SubsurfaceData> + 'static,
331{
332 fn set_scaling_factor(&mut self, scale_factor: f64) {
333 self.scale_factor = scale_factor;
334 self.dirty = true;
335 self.should_sync = true;
336 }
337
338 fn on_click(
339 &mut self,
340 _timestamp: Duration,
341 click: FrameClick,
342 pressed: bool,
343 ) -> Option<FrameAction> {
344 // Handle alternate click before everything else.
345 if click == FrameClick::Alternate {
346 return if Location::Head != self.mouse_location
347 || !self.wm_capabilities.contains(WindowManagerCapabilities::WINDOW_MENU)
348 {
349 None
350 } else {
351 Some(FrameAction::ShowMenu(
352 self.mouse_coords.0,
353 self.mouse_coords.1 - HEADER_SIZE as i32,
354 ))
355 };
356 }
357
358 let resize = pressed && self.resizable;
359 match self.mouse_location {
360 Location::Head if pressed => Some(FrameAction::Move),
361 Location::Button(UIButton::Close) if !pressed => Some(FrameAction::Close),
362 Location::Button(UIButton::Minimize) if !pressed => Some(FrameAction::Minimize),
363 Location::Button(UIButton::Maximize)
364 if !pressed && !self.state.contains(WindowState::MAXIMIZED) =>
365 {
366 Some(FrameAction::Maximize)
367 }
368 Location::Button(UIButton::Maximize)
369 if !pressed && self.state.contains(WindowState::MAXIMIZED) =>
370 {
371 Some(FrameAction::UnMaximize)
372 }
373 Location::Top if resize => Some(FrameAction::Resize(ResizeEdge::Top)),
374 Location::TopLeft if resize => Some(FrameAction::Resize(ResizeEdge::TopLeft)),
375 Location::Left if resize => Some(FrameAction::Resize(ResizeEdge::Left)),
376 Location::BottomLeft if resize => Some(FrameAction::Resize(ResizeEdge::BottomLeft)),
377 Location::Bottom if resize => Some(FrameAction::Resize(ResizeEdge::Bottom)),
378 Location::BottomRight if resize => Some(FrameAction::Resize(ResizeEdge::BottomRight)),
379 Location::Right if resize => Some(FrameAction::Resize(ResizeEdge::Right)),
380 Location::TopRight if resize => Some(FrameAction::Resize(ResizeEdge::TopRight)),
381 _ => None,
382 }
383 }
384
385 fn click_point_moved(
386 &mut self,
387 _timestamp: Duration,
388 surface_id: &ObjectId,
389 x: f64,
390 y: f64,
391 ) -> Option<CursorIcon> {
392 let part_index = self.part_index_for_surface(surface_id)?;
393 let location = match part_index {
394 LEFT_BORDER => Location::Left,
395 RIGHT_BORDER => Location::Right,
396 BOTTOM_BORDER => Location::Bottom,
397 TOP_BORDER => Location::Top,
398 _ => Location::Head,
399 };
400
401 let old_location = self.mouse_location;
402 self.mouse_coords = (x as i32, y as i32);
403 self.mouse_location = Self::precise_location(
404 &self.buttons,
405 location,
406 self.render_data.as_ref().unwrap().parts[part_index].width,
407 x,
408 y,
409 );
410
411 // Set dirty if we moved the cursor between the buttons.
412 self.dirty |= (matches!(old_location, Location::Button(_))
413 || matches!(self.mouse_location, Location::Button(_)))
414 && old_location != self.mouse_location;
415
416 Some(match self.mouse_location {
417 Location::Top => CursorIcon::NResize,
418 Location::TopRight => CursorIcon::NeResize,
419 Location::Right => CursorIcon::EResize,
420 Location::BottomRight => CursorIcon::SeResize,
421 Location::Bottom => CursorIcon::SResize,
422 Location::BottomLeft => CursorIcon::SwResize,
423 Location::Left => CursorIcon::WResize,
424 Location::TopLeft => CursorIcon::NwResize,
425 _ => CursorIcon::Default,
426 })
427 }
428
429 fn click_point_left(&mut self) {
430 self.mouse_location = Location::None;
431 self.dirty = true;
432 }
433
434 fn set_hidden(&mut self, hidden: bool) {
435 if self.is_hidden() == hidden {
436 return;
437 }
438
439 if hidden {
440 self.render_data = None;
441 } else {
442 let _ = self.pool.resize(1);
443 self.render_data =
444 Some(FrameRenderData::new(&self.parent, &self.subcompositor, &self.queue_handle));
445 }
446 }
447
448 fn set_resizable(&mut self, resizable: bool) {
449 self.resizable = resizable;
450 }
451
452 fn update_state(&mut self, state: WindowState) {
453 let difference = self.state.symmetric_difference(state);
454 self.state = state;
455 self.dirty |= !difference
456 .intersection(WindowState::ACTIVATED | WindowState::FULLSCREEN | WindowState::MAXIMIZED)
457 .is_empty();
458 }
459
460 fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) {
461 let parts = &mut self.render_data.as_mut().expect("trying to resize hidden frame").parts;
462
463 let width = width.get();
464 let height = height.get();
465
466 parts[HEADER].width = width;
467
468 parts[TOP_BORDER].width = width + 2 * BORDER_SIZE;
469
470 parts[BOTTOM_BORDER].width = width + 2 * BORDER_SIZE;
471 parts[BOTTOM_BORDER].pos.1 = height as i32;
472
473 parts[LEFT_BORDER].height = height + HEADER_SIZE;
474
475 parts[RIGHT_BORDER].height = parts[LEFT_BORDER].height;
476 parts[RIGHT_BORDER].pos.0 = width as i32;
477
478 self.dirty = true;
479 self.should_sync = true;
480 }
481
482 fn subtract_borders(
483 &self,
484 width: NonZeroU32,
485 height: NonZeroU32,
486 ) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
487 if self.state.contains(WindowState::FULLSCREEN) || self.render_data.is_none() {
488 (Some(width), Some(height))
489 } else {
490 (
491 NonZeroU32::new(width.get().saturating_sub(2 * BORDER_SIZE)),
492 NonZeroU32::new(height.get().saturating_sub(HEADER_SIZE + 2 * BORDER_SIZE)),
493 )
494 }
495 }
496
497 fn add_borders(&self, width: u32, height: u32) -> (u32, u32) {
498 if self.state.contains(WindowState::FULLSCREEN) || self.render_data.is_none() {
499 (width, height)
500 } else {
501 (width + 2 * BORDER_SIZE, height + (HEADER_SIZE + 2 * BORDER_SIZE))
502 }
503 }
504
505 fn is_hidden(&self) -> bool {
506 self.render_data.is_none()
507 }
508
509 fn location(&self) -> (i32, i32) {
510 if self.state.contains(WindowState::FULLSCREEN) || self.is_hidden() {
511 (0, 0)
512 } else {
513 self.render_data.as_ref().unwrap().parts[TOP_BORDER].pos
514 }
515 }
516
517 fn is_dirty(&self) -> bool {
518 self.dirty
519 }
520
521 fn draw(&mut self) -> bool {
522 let render_data = match self.render_data.as_mut() {
523 Some(render_data) => render_data,
524 None => return false,
525 };
526
527 // Reset the dirty bit and sync option.
528 self.dirty = false;
529 let should_sync = mem::take(&mut self.should_sync);
530
531 if self.state.contains(WindowState::FULLSCREEN) {
532 // Don't draw the decorations for the full screen surface.
533 for part in &render_data.parts {
534 part.surface.attach(None, 0, 0);
535 part.surface.commit();
536 }
537 return should_sync;
538 }
539
540 let is_active = self.state.contains(WindowState::ACTIVATED);
541 let fill_color =
542 if is_active { PRIMARY_COLOR_ACTIVE } else { PRIMARY_COLOR_INACTIVE }.to_le_bytes();
543
544 for (idx, part) in render_data.parts.iter().enumerate() {
545 // We don't support fractinal scaling here, so round up.
546 let scale = self.scale_factor.ceil() as i32;
547
548 let (buffer, canvas) = match self.pool.create_buffer(
549 part.width as i32 * scale,
550 part.height as i32 * scale,
551 part.width as i32 * 4 * scale,
552 wl_shm::Format::Argb8888,
553 ) {
554 Ok((buffer, canvas)) => (buffer, canvas),
555 Err(_) => continue,
556 };
557
558 // Fill the canvas.
559 for pixel in canvas.chunks_exact_mut(4) {
560 pixel[0] = fill_color[0];
561 pixel[1] = fill_color[1];
562 pixel[2] = fill_color[2];
563 pixel[3] = fill_color[3];
564 }
565
566 // Draw the buttons for the header.
567 if idx == HEADER {
568 Self::draw_buttons(
569 &self.buttons,
570 canvas,
571 part.width,
572 scale as u32,
573 is_active,
574 &self.mouse_location,
575 );
576 }
577
578 part.surface.set_buffer_scale(scale);
579 if should_sync {
580 part.subsurface.set_sync();
581 } else {
582 part.subsurface.set_desync();
583 }
584
585 // Update the subsurface position.
586 part.subsurface.set_position(part.pos.0, part.pos.1);
587
588 buffer.attach_to(&part.surface).expect("failed to attach the buffer");
589 if part.surface.version() >= 4 {
590 part.surface.damage_buffer(0, 0, i32::MAX, i32::MAX);
591 } else {
592 part.surface.damage(0, 0, i32::MAX, i32::MAX);
593 }
594
595 part.surface.commit();
596 }
597
598 should_sync
599 }
600
601 fn update_wm_capabilities(&mut self, capabilities: WindowManagerCapabilities) {
602 self.dirty |= self.wm_capabilities != capabilities;
603 self.wm_capabilities = capabilities;
604 self.buttons = Self::supported_buttons(capabilities);
605 }
606
607 fn set_title(&mut self, _: impl Into<String>) {}
608}
609
610/// Inner state to simplify dropping.
611#[derive(Debug)]
612struct FrameRenderData {
613 /// The header subsurface.
614 parts: [FramePart; 5],
615}
616
617impl FrameRenderData {
618 fn new<State>(
619 parent: &WlSurface,
620 subcompositor: &SubcompositorState,
621 queue_handle: &QueueHandle<State>,
622 ) -> Self
623 where
624 State: Dispatch<WlSurface, SurfaceData> + Dispatch<WlSubsurface, SubsurfaceData> + 'static,
625 {
626 let parts = [
627 // Header.
628 FramePart::new(
629 subcompositor.create_subsurface(parent.clone(), queue_handle),
630 0,
631 HEADER_SIZE,
632 (0, -(HEADER_SIZE as i32)),
633 ),
634 // Top border.
635 FramePart::new(
636 subcompositor.create_subsurface(parent.clone(), queue_handle),
637 0,
638 BORDER_SIZE,
639 (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32 + BORDER_SIZE as i32)),
640 ),
641 // Right border.
642 FramePart::new(
643 subcompositor.create_subsurface(parent.clone(), queue_handle),
644 BORDER_SIZE,
645 0,
646 (0, -(HEADER_SIZE as i32)),
647 ),
648 // Bottom border.
649 FramePart::new(
650 subcompositor.create_subsurface(parent.clone(), queue_handle),
651 0,
652 BORDER_SIZE,
653 (-(BORDER_SIZE as i32), 0),
654 ),
655 // Left border.
656 FramePart::new(
657 subcompositor.create_subsurface(parent.clone(), queue_handle),
658 BORDER_SIZE,
659 0,
660 (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32)),
661 ),
662 ];
663
664 Self { parts }
665 }
666}
667
668#[derive(Debug)]
669struct FramePart {
670 /// The surface used for the frame part.
671 subsurface: WlSubsurface,
672
673 /// The surface used for this part.
674 surface: WlSurface,
675
676 /// The width of the Frame part in logical pixels.
677 width: u32,
678
679 /// The height of the Frame part in logical pixels.
680 height: u32,
681
682 /// The position for the subsurface.
683 pos: (i32, i32),
684}
685
686impl FramePart {
687 fn new(surfaces: (WlSubsurface, WlSurface), width: u32, height: u32, pos: (i32, i32)) -> Self {
688 let (subsurface, surface) = surfaces;
689 // XXX sync subsurfaces with the main surface.
690 subsurface.set_sync();
691 Self { surface, subsurface, width, height, pos }
692 }
693}
694
695impl Drop for FramePart {
696 fn drop(&mut self) {
697 self.subsurface.destroy();
698 self.surface.destroy();
699 }
700}
701
702/// The location inside the
703#[derive(Debug, Copy, Clone, PartialEq, Eq)]
704enum Location {
705 /// The location doesn't belong to the frame.
706 None,
707 /// Header bar.
708 Head,
709 /// Top border.
710 Top,
711 /// Top right corner.
712 TopRight,
713 /// Right border.
714 Right,
715 /// Bottom right corner.
716 BottomRight,
717 /// Bottom border.
718 Bottom,
719 /// Bottom left corner.
720 BottomLeft,
721 /// Left border.
722 Left,
723 /// Top left corner.
724 TopLeft,
725 /// One of the buttons.
726 Button(UIButton),
727}
728
729/// The frame button.
730#[derive(Debug, Copy, Clone, PartialEq, Eq)]
731enum UIButton {
732 /// The minimize button, the left most.
733 Minimize,
734 /// The maximize button, in the middle.
735 Maximize,
736 /// The close botton, the right most.
737 Close,
738}
739