From b28756169e087e3a035e5a6ab7a5c49adf86e3be Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Thu, 2 Mar 2023 18:53:02 +0300 Subject: [PATCH] window: add client side decorations interface While the new decorations look the same as the ones before, the approach in drawing them was changed, and now decorations are more instant mode, meaning that you create and drop them when needed. Fixes #273. --- examples/generic_simple_window.rs | 2 +- examples/simple_layer.rs | 2 +- examples/simple_window.rs | 2 +- ...inter_theme_window.rs => themed_window.rs} | 175 +++++- src/shell/xdg/frame/fallback_frame.rs | 592 ++++++++++++++++++ src/shell/xdg/frame/mod.rs | 129 ++++ src/shell/xdg/mod.rs | 1 + src/shell/xdg/window/mod.rs | 4 +- 8 files changed, 870 insertions(+), 37 deletions(-) rename examples/{pointer_theme_window.rs => themed_window.rs} (67%) create mode 100644 src/shell/xdg/frame/fallback_frame.rs create mode 100644 src/shell/xdg/frame/mod.rs diff --git a/examples/generic_simple_window.rs b/examples/generic_simple_window.rs index f872b5183..7e673c157 100644 --- a/examples/generic_simple_window.rs +++ b/examples/generic_simple_window.rs @@ -279,7 +279,7 @@ impl KeyboardHandler for SimpleWindow { keysyms: &[u32], ) { if self.window.wl_surface() == surface { - println!("Keyboard focus on window with pressed syms: {:?}", keysyms); + println!("Keyboard focus on window with pressed syms: {keysyms:?}"); self.keyboard_focus = true; } } diff --git a/examples/simple_layer.rs b/examples/simple_layer.rs index 334919c84..0b35a0fb6 100644 --- a/examples/simple_layer.rs +++ b/examples/simple_layer.rs @@ -263,7 +263,7 @@ impl KeyboardHandler for SimpleLayer { keysyms: &[u32], ) { if self.layer.wl_surface() == surface { - println!("Keyboard focus on window with pressed syms: {:?}", keysyms); + println!("Keyboard focus on window with pressed syms: {keysyms:?}"); self.keyboard_focus = true; } } diff --git a/examples/simple_window.rs b/examples/simple_window.rs index c7241ddef..7ef310877 100644 --- a/examples/simple_window.rs +++ b/examples/simple_window.rs @@ -283,7 +283,7 @@ impl KeyboardHandler for SimpleWindow { keysyms: &[u32], ) { if self.window.wl_surface() == surface { - println!("Keyboard focus on window with pressed syms: {:?}", keysyms); + println!("Keyboard focus on window with pressed syms: {keysyms:?}"); self.keyboard_focus = true; } } diff --git a/examples/pointer_theme_window.rs b/examples/themed_window.rs similarity index 67% rename from examples/pointer_theme_window.rs rename to examples/themed_window.rs index e9ab612bc..f907f197d 100644 --- a/examples/pointer_theme_window.rs +++ b/examples/themed_window.rs @@ -3,19 +3,23 @@ use std::convert::TryInto; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, delegate_compositor, delegate_keyboard, delegate_output, delegate_pointer, delegate_registry, - delegate_seat, delegate_shm, delegate_xdg_shell, delegate_xdg_window, + delegate_seat, delegate_shm, delegate_subcompositor, delegate_xdg_shell, delegate_xdg_window, output::{OutputHandler, OutputState}, registry::{ProvidesRegistryState, RegistryState}, registry_handlers, seat::{ keyboard::{KeyEvent, KeyboardHandler, Modifiers}, - pointer::{PointerEvent, PointerEventKind, PointerHandler, ThemeSpec, ThemedPointer}, + pointer::{ + PointerData, PointerEvent, PointerEventKind, PointerHandler, ThemeSpec, ThemedPointer, + }, Capability, SeatHandler, SeatState, }, shell::{ xdg::{ - window::{Window, WindowConfigure, WindowDecorations, WindowHandler}, - XdgShell, + frame::fallback_frame::FallbackFrame, + frame::{DecorationsFrame, FrameAction, FrameClick}, + window::{DecorationMode, Window, WindowConfigure, WindowDecorations, WindowHandler}, + XdgShell, XdgSurface, }, WaylandSurface, }, @@ -23,11 +27,12 @@ use smithay_client_toolkit::{ slot::{Buffer, SlotPool}, Shm, ShmHandler, }, + subcompositor::SubcompositorState, }; use wayland_client::{ globals::registry_queue_init, protocol::{wl_keyboard, wl_output, wl_pointer, wl_seat, wl_shm, wl_surface}, - Connection, QueueHandle, + Connection, Proxy, QueueHandle, }; fn main() { @@ -42,6 +47,9 @@ fn main() { let output_state = OutputState::new(&globals, &qh); let compositor_state = CompositorState::bind(&globals, &qh).expect("wl_compositor not available"); + let subcompositor_state = + SubcompositorState::bind(compositor_state.wl_compositor().clone(), &globals, &qh) + .expect("wl_subcompositor not available"); let shm_state = Shm::bind(&globals, &qh).expect("wl_shm not available"); let xdg_shell_state = XdgShell::bind(&globals, &qh).expect("xdg shell not available"); @@ -72,6 +80,7 @@ fn main() { seat_state, output_state, _compositor_state: compositor_state, + subcompositor_state, shm_state, _xdg_shell_state: xdg_shell_state, @@ -83,11 +92,13 @@ fn main() { shift: None, buffer: None, window, + window_frame: None, pointer_surface, keyboard: None, keyboard_focus: false, themed_pointer: None, set_cursor: false, + cursor_icon: String::from("diamond_cross"), }; // We don't draw immediately, the configure will notify us when to first draw. @@ -107,6 +118,7 @@ struct SimpleWindow { seat_state: SeatState, output_state: OutputState, _compositor_state: CompositorState, + subcompositor_state: SubcompositorState, shm_state: Shm, _xdg_shell_state: XdgShell, @@ -118,11 +130,13 @@ struct SimpleWindow { shift: Option, buffer: Option, window: Window, + window_frame: Option, pointer_surface: wl_surface::WlSurface, keyboard: Option, keyboard_focus: bool, themed_pointer: Option, set_cursor: bool, + cursor_icon: String, } impl CompositorHandler for SimpleWindow { @@ -186,22 +200,60 @@ impl WindowHandler for SimpleWindow { &mut self, conn: &Connection, qh: &QueueHandle, - _window: &Window, + window: &Window, configure: WindowConfigure, _serial: u32, ) { - match configure.new_size { - Some(size) => { - self.width = size.0; - self.height = size.1; - self.buffer = None; - } - None => { - self.width = 256; - self.height = 256; - self.buffer = None; - } - } + self.buffer = None; + + println!( + "Configure size {:?}, decorations: {:?}", + configure.new_size, configure.decoration_mode + ); + + let (width, height) = if configure.decoration_mode == DecorationMode::Client { + let window_frame = self.window_frame.get_or_insert_with(|| { + FallbackFrame::new(&self.window, &self.shm_state, &self.subcompositor_state, qh) + .expect("failed to create client side decorations frame.") + }); + + // Configure state before touching any resizing. + window_frame.configure_state(&configure.states); + + let (width, height) = match configure.new_size { + Some((width, height)) => { + // The size could be 0. + window_frame.subtract_borders(width, height) + } + None => { + // You might want to consider checking for configure bounds. + (self.width, self.height) + } + }; + + println!("{width}, {height}"); + window_frame.resize(width, height); + + let (x, y) = window_frame.location(); + let outer_size = window_frame.add_borders(width, height); + window.xdg_surface().set_window_geometry( + x, + y, + outer_size.0 as i32, + outer_size.1 as i32, + ); + + (width, height) + } else { + self.window_frame = None; + let (width, height) = configure.new_size.unwrap_or((self.width, self.height)); + self.window.xdg_surface().set_window_geometry(0, 0, width as i32, height as i32); + (width, height) + }; + + // Update new width and height; + self.width = width; + self.height = height; // Initiate the first draw. if self.first_configure { @@ -334,38 +386,89 @@ impl PointerHandler for SimpleWindow { &mut self, _conn: &Connection, _qh: &QueueHandle, - _pointer: &wl_pointer::WlPointer, + pointer: &wl_pointer::WlPointer, events: &[PointerEvent], ) { use PointerEventKind::*; for event in events { - // Ignore events for other surfaces - if &event.surface != self.window.wl_surface() { - continue; - } + let (x, y) = event.position; match event.kind { Enter { .. } => { - println!("Pointer entered @{:?}", event.position); self.set_cursor = true; + self.cursor_icon = self + .window_frame + .as_mut() + .and_then(|frame| frame.click_point_moved(&event.surface, x, y)) + .unwrap_or("diamond_cross") + .to_owned(); + + if &event.surface == self.window.wl_surface() { + println!("Pointer entered @{:?}", event.position); + } } Leave { .. } => { + if &event.surface != self.window.wl_surface() { + if let Some(window_frame) = self.window_frame.as_mut() { + window_frame.click_point_left(); + } + } println!("Pointer left"); } - Motion { .. } => {} - Press { button, .. } => { - println!("Press {:x} @ {:?}", button, event.position); - self.shift = self.shift.xor(Some(0)); + Motion { .. } => { + if let Some(new_cursor) = self + .window_frame + .as_mut() + .and_then(|frame| frame.click_point_moved(&event.surface, x, y)) + { + self.set_cursor = true; + self.cursor_icon = new_cursor.to_owned(); + } } - Release { button, .. } => { - println!("Release {:x} @ {:?}", button, event.position); + Press { button, serial, .. } | Release { button, serial, .. } => { + let pressed = if matches!(event.kind, Press { .. }) { true } else { false }; + if &event.surface != self.window.wl_surface() { + let click = match button { + 0x110 => FrameClick::Normal, + 0x111 => FrameClick::Alternate, + _ => continue, + }; + + if let Some(action) = self + .window_frame + .as_mut() + .and_then(|frame| frame.on_click(click, pressed)) + { + self.frame_action(pointer, serial, action); + } + } else if pressed { + println!("Press {:x} @ {:?}", button, event.position); + self.shift = self.shift.xor(Some(0)); + } } Axis { horizontal, vertical, .. } => { - println!("Scroll H:{horizontal:?}, V:{vertical:?}"); + if &event.surface == self.window.wl_surface() { + println!("Scroll H:{horizontal:?}, V:{vertical:?}"); + } } } } } } +impl SimpleWindow { + fn frame_action(&mut self, pointer: &wl_pointer::WlPointer, serial: u32, action: FrameAction) { + let pointer_data = pointer.data::().unwrap(); + let seat = pointer_data.seat(); + match action { + FrameAction::Close => self.exit = true, + FrameAction::Minimize => self.window.set_minimized(), + FrameAction::Maximize => self.window.set_maximized(), + FrameAction::UnMaximize => self.window.unset_maximized(), + FrameAction::ShowMenu(x, y) => self.window.show_window_menu(seat, serial, (x, y)), + FrameAction::Resize(edge) => self.window.resize(seat, serial, edge), + FrameAction::Move => self.window.r#move(seat, serial), + } + } +} impl ShmHandler for SimpleWindow { fn shm_state(&mut self) -> &mut Shm { @@ -378,7 +481,7 @@ impl SimpleWindow { if self.set_cursor { let _ = self.themed_pointer.as_mut().unwrap().set_cursor( conn, - "diamond_cross", + &self.cursor_icon, self.shm_state.wl_shm(), &self.pointer_surface, 1, @@ -438,6 +541,13 @@ impl SimpleWindow { } } + // Draw the decorations frame. + self.window_frame.as_mut().map(|frame| { + if frame.is_dirty() { + frame.redraw(); + } + }); + // Damage the entire window self.window.wl_surface().damage_buffer(0, 0, self.width as i32, self.height as i32); @@ -451,6 +561,7 @@ impl SimpleWindow { } delegate_compositor!(SimpleWindow); +delegate_subcompositor!(SimpleWindow); delegate_output!(SimpleWindow); delegate_shm!(SimpleWindow); diff --git a/src/shell/xdg/frame/fallback_frame.rs b/src/shell/xdg/frame/fallback_frame.rs new file mode 100644 index 000000000..945fe0c66 --- /dev/null +++ b/src/shell/xdg/frame/fallback_frame.rs @@ -0,0 +1,592 @@ +//! The default fallback frame which is intended to show some very basic derocations. + +use std::error::Error; + +use wayland_client::{ + protocol::{wl_shm, wl_subsurface::WlSubsurface, wl_surface::WlSurface}, + Dispatch, Proxy, QueueHandle, +}; +use wayland_protocols::xdg::shell::client::xdg_toplevel::{ResizeEdge, State as WindowState}; + +use crate::{ + compositor::SurfaceData, + shell::WaylandSurface, + shm::{slot::SlotPool, Shm}, + subcompositor::{SubcompositorState, SubsurfaceData}, +}; + +use super::{DecorationsFrame, FrameAction, FrameClick}; + +/// The size of the header bar. +const HEADER_SIZE: u32 = 24; + +/// The size of the border. +const BORDER_SIZE: u32 = 4; + +const HEADER: usize = 0; +const TOP_BORDER: usize = 1; +const RIGHT_BORDER: usize = 2; +const BOTTOM_BORDER: usize = 3; +const LEFT_BORDER: usize = 4; + +const BTN_ICON_COLOR: u32 = 0xFFCCCCCC; +const BTN_HOVER_BG: u32 = 0xFF808080; + +const PRIMARY_COLOR_ACTIVE: u32 = 0xFF3A3A3A; +const PRIMARY_COLOR_INACTIVE: u32 = 0xFF242424; + +/// The default ugly frame. +#[derive(Debug)] +pub struct FallbackFrame { + /// The header subsurface. + parts: [FramePart; 5], + + /// The memory pool to use for drawing. + pool: SlotPool, + + /// Whether the frame is full-screened. + is_fullscreen: bool, + + /// Whether the window is maximized. + is_maximized: bool, + + /// Whether the window is active. + is_active: bool, + + /// Whether the frame is waiting for redraw. + dirty: bool, + + /// The location of the mouse. + mouse_location: Location, + + /// The location of the mouse. + mouse_coords: (i32, i32), +} + +impl FallbackFrame { + fn precise_location(old: Location, width: u32, x: f64, y: f64) -> Location { + match old { + Location::Head | Location::Button(_) => Self::find_button(x, y, width), + + Location::Top | Location::TopLeft | Location::TopRight => { + if x <= f64::from(BORDER_SIZE) { + Location::TopLeft + } else if x >= f64::from(width - BORDER_SIZE) { + Location::TopRight + } else { + Location::Top + } + } + + Location::Bottom | Location::BottomLeft | Location::BottomRight => { + if x <= f64::from(BORDER_SIZE) { + Location::BottomLeft + } else if x >= f64::from(width - BORDER_SIZE) { + Location::BottomRight + } else { + Location::Bottom + } + } + + other => other, + } + } + + fn find_button(x: f64, y: f64, w: u32) -> Location { + for (idx, button) in + [UIButton::Close, UIButton::Maximize, UIButton::Minimize].into_iter().enumerate() + { + let idx = idx as u32; + if w >= (idx + 1) * HEADER_SIZE + && x >= f64::from(w - (idx + 1) * HEADER_SIZE) + && x <= f64::from(w - idx * HEADER_SIZE) + && y <= f64::from(HEADER_SIZE) + && y >= f64::from(0) + { + return Location::Button(button); + } + } + + Location::Head + } + + #[inline] + fn part_index_for_surface(&mut self, surface: &WlSurface) -> Option { + self.parts.iter().position(|part| &part.surface == surface) + } + + fn draw_buttons( + canvas: &mut [u8], + width: u32, + scale: u32, + is_active: bool, + mouse_location: &Location, + ) { + let scale = scale as usize; + let buttons = [UIButton::Close, UIButton::Maximize, UIButton::Minimize]; + for (idx, button) in buttons.into_iter().enumerate() { + if width >= (idx + 1) as u32 * HEADER_SIZE { + if is_active && mouse_location == &Location::Button(button) { + Self::draw_button( + canvas, + idx * HEADER_SIZE as usize, + scale, + width as usize, + BTN_HOVER_BG.to_ne_bytes(), + ); + } + Self::draw_icon( + canvas, + width as usize, + idx * HEADER_SIZE as usize, + scale, + BTN_ICON_COLOR.to_ne_bytes(), + button, + ); + } + } + } + + fn draw_button( + canvas: &mut [u8], + x_offset: usize, + scale: usize, + width: usize, + btn_color: [u8; 4], + ) { + let h = HEADER_SIZE as usize; + let x_start = width - h - x_offset; + // main square + for y in 0..h * scale { + let canvas = &mut canvas + [(x_start + y * width) * 4 * scale..(x_start + y * width + h) * scale * 4]; + for pixel in canvas.chunks_exact_mut(4) { + pixel[0] = btn_color[0]; + pixel[1] = btn_color[1]; + pixel[2] = btn_color[2]; + pixel[3] = btn_color[3]; + } + } + } + + fn draw_icon( + canvas: &mut [u8], + width: usize, + x_offset: usize, + scale: usize, + icon_color: [u8; 4], + icon: UIButton, + ) { + let h = HEADER_SIZE as usize; + let sh = scale * h; + let x_start = width - h - x_offset; + + match icon { + UIButton::Close => { + // Draw black rectangle + for y in sh / 4..3 * sh / 4 { + let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale + ..(x_start + y * width + 3 * h / 4) * 4 * scale]; + for pixel in line.chunks_exact_mut(4) { + pixel[0] = icon_color[0]; + pixel[1] = icon_color[1]; + pixel[2] = icon_color[2]; + pixel[3] = icon_color[3]; + } + } + } + UIButton::Maximize => { + // Draw an empty rectangle + for y in 2 * sh / 8..3 * sh / 8 { + let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale + ..(x_start + y * width + 3 * h / 4) * 4 * scale]; + for pixel in line.chunks_exact_mut(4) { + pixel[0] = icon_color[0]; + pixel[1] = icon_color[1]; + pixel[2] = icon_color[2]; + pixel[3] = icon_color[3]; + } + } + for y in 3 * sh / 8..5 * sh / 8 { + let line = &mut canvas[(x_start + y * width + 2 * h / 8) * 4 * scale + ..(x_start + y * width + 3 * h / 8) * 4 * scale]; + for pixel in line.chunks_exact_mut(4) { + pixel[0] = icon_color[0]; + pixel[1] = icon_color[1]; + pixel[2] = icon_color[2]; + pixel[3] = icon_color[3]; + } + let line = &mut canvas[(x_start + y * width + 5 * h / 8) * 4 * scale + ..(x_start + y * width + 6 * h / 8) * 4 * scale]; + for pixel in line.chunks_exact_mut(4) { + pixel[0] = icon_color[0]; + pixel[1] = icon_color[1]; + pixel[2] = icon_color[2]; + pixel[3] = icon_color[3]; + } + } + for y in 5 * sh / 8..6 * sh / 8 { + let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale + ..(x_start + y * width + 3 * h / 4) * 4 * scale]; + for pixel in line.chunks_exact_mut(4) { + pixel[0] = icon_color[0]; + pixel[1] = icon_color[1]; + pixel[2] = icon_color[2]; + pixel[3] = icon_color[3]; + } + } + } + UIButton::Minimize => { + // Draw an underline + for y in 5 * sh / 8..3 * sh / 4 { + let line = &mut canvas[(x_start + y * width + h / 4) * 4 * scale + ..(x_start + y * width + 3 * h / 4) * 4 * scale]; + for pixel in line.chunks_exact_mut(4) { + pixel[0] = icon_color[0]; + pixel[1] = icon_color[1]; + pixel[2] = icon_color[2]; + pixel[3] = icon_color[3]; + } + } + } + } + } +} + +impl DecorationsFrame for FallbackFrame { + type Config = (); + + fn new( + parent: &impl WaylandSurface, + shm: &Shm, + subcomositor: &SubcompositorState, + queue_handle: &QueueHandle, + ) -> Result> + where + State: Dispatch + Dispatch + 'static, + { + let parent = parent.wl_surface(); + + let parts = [ + // Header. + FramePart::new( + subcomositor.create_subsurface(parent.clone(), queue_handle), + 0, + HEADER_SIZE, + (0, -(HEADER_SIZE as i32)), + ), + // Top border. + FramePart::new( + subcomositor.create_subsurface(parent.clone(), queue_handle), + 0, + BORDER_SIZE, + (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32 + BORDER_SIZE as i32)), + ), + // Right border. + FramePart::new( + subcomositor.create_subsurface(parent.clone(), queue_handle), + BORDER_SIZE, + 0, + (0, -(HEADER_SIZE as i32)), + ), + // Bottom border. + FramePart::new( + subcomositor.create_subsurface(parent.clone(), queue_handle), + 0, + BORDER_SIZE, + (-(BORDER_SIZE as i32), 0), + ), + // Left border. + FramePart::new( + subcomositor.create_subsurface(parent.clone(), queue_handle), + BORDER_SIZE, + 0, + (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32)), + ), + ]; + + let pool = SlotPool::new(1024, shm)?; + + Ok(Self { + parts, + pool, + is_fullscreen: false, + is_maximized: false, + is_active: false, + dirty: true, + mouse_location: Location::None, + mouse_coords: (0, 0), + }) + } + + fn on_click(&mut self, click: FrameClick, pressed: bool) -> Option { + // Handle alternate click before everything else. + if click == FrameClick::Alternate { + return if Location::Head != self.mouse_location { + None + } else { + Some(FrameAction::ShowMenu(self.mouse_coords.0, self.mouse_coords.1 - HEADER_SIZE as i32)) + }; + } + + match self.mouse_location { + Location::Head if pressed => Some(FrameAction::Move), + Location::Button(UIButton::Close) if !pressed => Some(FrameAction::Close), + Location::Button(UIButton::Minimize) if !pressed => Some(FrameAction::Minimize), + Location::Button(UIButton::Maximize) if !pressed && !self.is_maximized => { + Some(FrameAction::Maximize) + } + Location::Button(UIButton::Maximize) if !pressed && self.is_maximized => { + Some(FrameAction::UnMaximize) + } + Location::Top if pressed => Some(FrameAction::Resize(ResizeEdge::Top)), + Location::TopLeft if pressed => Some(FrameAction::Resize(ResizeEdge::TopLeft)), + Location::Left if pressed => Some(FrameAction::Resize(ResizeEdge::Left)), + Location::BottomLeft if pressed => Some(FrameAction::Resize(ResizeEdge::BottomLeft)), + Location::Bottom if pressed => Some(FrameAction::Resize(ResizeEdge::Bottom)), + Location::BottomRight if pressed => Some(FrameAction::Resize(ResizeEdge::BottomRight)), + Location::Right if pressed => Some(FrameAction::Resize(ResizeEdge::Right)), + Location::TopRight if pressed => Some(FrameAction::Resize(ResizeEdge::TopRight)), + _ => None, + } + } + + fn click_point_moved(&mut self, surface: &WlSurface, x: f64, y: f64) -> Option<&str> { + let part_index = self.part_index_for_surface(surface)?; + let location = match part_index { + LEFT_BORDER => Location::Left, + RIGHT_BORDER => Location::Right, + BOTTOM_BORDER => Location::Bottom, + TOP_BORDER => Location::Top, + _ => Location::Head, + }; + + let old_location = self.mouse_location; + self.mouse_coords = (x as i32, y as i32); + self.mouse_location = Self::precise_location(location, self.parts[part_index].width, x, y); + + // Set dirty if we moved the cursor between the buttons. + self.dirty |= (matches!(old_location, Location::Button(_)) + || matches!(self.mouse_location, Location::Button(_))) + && old_location != self.mouse_location; + + Some(match self.mouse_location { + Location::Top => "top_side", + Location::TopRight => "top_right_corner", + Location::Right => "right_side", + Location::BottomRight => "bottom_right_corner", + Location::Bottom => "bottom_side", + Location::BottomLeft => "bottom_left_corner", + Location::Left => "left_side", + Location::TopLeft => "top_left_corner", + _ => "left_ptr", + }) + } + + fn click_point_left(&mut self) { + self.mouse_location = Location::None; + self.dirty = true; + } + + fn configure_state(&mut self, configure: &[WindowState]) { + let is_fullscreen = configure.contains(&WindowState::Fullscreen); + let is_maximized = configure.contains(&WindowState::Maximized); + let is_active = configure.contains(&WindowState::Activated); + + if self.is_fullscreen != is_fullscreen { + self.is_fullscreen = is_fullscreen; + self.dirty = true; + } + + if self.is_maximized != is_maximized { + self.is_maximized = is_maximized; + self.dirty = true; + } + + if self.is_active != is_active { + self.is_active = is_active; + self.dirty = true; + } + } + + fn resize(&mut self, width: u32, height: u32) { + self.parts[HEADER].width = width; + + self.parts[TOP_BORDER].width = width + 2 * BORDER_SIZE; + + self.parts[BOTTOM_BORDER].width = width + 2 * BORDER_SIZE; + self.parts[BOTTOM_BORDER].pos.1 = height as i32; + + self.parts[LEFT_BORDER].height = height + HEADER_SIZE; + + self.parts[RIGHT_BORDER].height = self.parts[LEFT_BORDER].height; + self.parts[RIGHT_BORDER].pos.0 = width as i32; + + self.dirty = true; + } + + fn subtract_borders(&self, width: u32, height: u32) -> (u32, u32) { + if self.is_fullscreen { + (width, height) + } else { + ( + width.saturating_sub(2 * BORDER_SIZE), + height.saturating_sub(HEADER_SIZE + 2 * BORDER_SIZE), + ) + } + } + + fn add_borders(&self, width: u32, height: u32) -> (u32, u32) { + if self.is_fullscreen { + (width, height) + } else { + (width + 2 * BORDER_SIZE, height + (HEADER_SIZE + 2 * BORDER_SIZE)) + } + } + + fn location(&self) -> (i32, i32) { + if self.is_fullscreen { + (0, 0) + } else { + self.parts[TOP_BORDER].pos + } + } + + fn is_dirty(&self) -> bool { + self.dirty + } + + fn redraw(&mut self) { + // Reset the dirty bit. + self.dirty = false; + + if self.is_fullscreen { + // Don't draw the decorations for the full screen surface. + for part in &self.parts { + part.surface.attach(None, 0, 0); + part.surface.commit(); + } + return; + } + + let fill_color = if self.is_active { PRIMARY_COLOR_ACTIVE } else { PRIMARY_COLOR_INACTIVE } + .to_ne_bytes(); + + for (idx, part) in self.parts.iter().enumerate() { + let scale = part.surface.data::().unwrap().scale_factor(); + let (buffer, canvas) = match self.pool.create_buffer( + part.width as i32 * scale, + part.height as i32 * scale, + part.width as i32 * 4 * scale, + wl_shm::Format::Argb8888, + ) { + Ok((buffer, canvas)) => (buffer, canvas), + Err(_) => continue, + }; + + // Fill the canvas. + for pixel in canvas.chunks_exact_mut(4) { + pixel[0] = fill_color[0]; + pixel[1] = fill_color[1]; + pixel[2] = fill_color[2]; + pixel[3] = fill_color[3]; + } + + // Draw the buttons for the header. + if idx == HEADER { + Self::draw_buttons( + canvas, + part.width, + scale as u32, + self.is_active, + &self.mouse_location, + ); + } + + part.subsurface.set_position(part.pos.0, part.pos.1); + buffer.attach_to(&part.surface).expect("failed to attach the buffer"); + if part.surface.version() >= 4 { + part.surface.damage_buffer(0, 0, i32::MAX, i32::MAX); + } else { + part.surface.damage(0, 0, i32::MAX, i32::MAX); + } + + part.surface.set_buffer_scale(scale); + part.surface.commit(); + } + } + + fn set_title(&mut self, _: &impl Into) {} + + fn set_config(&mut self, _: Self::Config) {} +} + +#[derive(Debug)] +struct FramePart { + /// The surface used for the frame part. + subsurface: WlSubsurface, + + /// The surface used for this part. + surface: WlSurface, + + /// The width of the Frame part in logical pixels. + width: u32, + + /// The height of the Frame part in logical pixels. + height: u32, + + /// The position for the subsurface. + pos: (i32, i32), +} + +impl FramePart { + fn new(surfaces: (WlSubsurface, WlSurface), width: u32, height: u32, pos: (i32, i32)) -> Self { + let (subsurface, surface) = surfaces; + Self { surface, subsurface, width, height, pos } + } +} + +impl Drop for FramePart { + fn drop(&mut self) { + self.subsurface.destroy(); + self.surface.destroy(); + } +} + +/// The location inside the +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum Location { + /// The location doesn't belong to the frame. + None, + /// Header bar. + Head, + /// Top border. + Top, + /// Top right corner. + TopRight, + /// Right border. + Right, + /// Bottom right corner. + BottomRight, + /// Bottom border. + Bottom, + /// Bottom left corner. + BottomLeft, + /// Left border. + Left, + /// Top left corner. + TopLeft, + /// One of the buttons. + Button(UIButton), +} + +/// The frame button. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum UIButton { + /// The minimize button, the left most. + Minimize, + /// The maximize button, in the middle. + Maximize, + /// The close botton, the right most. + Close, +} diff --git a/src/shell/xdg/frame/mod.rs b/src/shell/xdg/frame/mod.rs new file mode 100644 index 000000000..11b0e3795 --- /dev/null +++ b/src/shell/xdg/frame/mod.rs @@ -0,0 +1,129 @@ +//! The frame to use with XDG shell window. + +use std::error::Error; + +use crate::reexports::client::{ + protocol::{wl_subsurface::WlSubsurface, wl_surface::WlSurface}, + Dispatch, QueueHandle, +}; +use crate::reexports::protocols::xdg::shell::client::xdg_toplevel::{ + ResizeEdge, State as WindowState, +}; + +use crate::{ + compositor::SurfaceData, + shell::WaylandSurface, + shm::Shm, + subcompositor::{SubcompositorState, SubsurfaceData}, +}; + +pub mod fallback_frame; + +/// The interface for the client side decorations. +pub trait DecorationsFrame: Sized { + /// Configurations for this frame. + type Config; + + /// Create new decorations frame. + fn new( + parent: &impl WaylandSurface, + shm: &Shm, + subcomositor: &SubcompositorState, + queue_handle: &QueueHandle, + ) -> Result> + where + State: Dispatch + Dispatch + 'static; + + /// Emulate click on the decorations. + /// + /// The `click` is a variant of click to use, see [`FrameClick`] for more info. + /// The return value is a [`FrameAction`] you should apply, this action could be + /// ignored, in case you don't want to apply it. + /// + /// The location of the click is the one passed to [`Self::click_point_moved`]. + fn on_click(&mut self, click: FrameClick, pressed: bool) -> Option; + + /// Emulate pointer moved event on the decorations frame. + /// + /// The `x` and `y` are location in surface local coordinates relative to `surface`. + /// This function returns the new cursor icon you should apply to provide better visual + /// effect for the end user. + fn click_point_moved(&mut self, surface: &WlSurface, x: f64, y: f64) -> Option<&str>; + + /// All clicks left the decorations. + fn click_point_left(&mut self); + + /// Configure the state of the frame. + /// + /// The state usually obtained from the [`WindowConfigure`] event. + /// + /// [`WindowConfigure`]: crate::shell::window::WindowConfigure + fn configure_state(&mut self, configure: &[WindowState]); + + /// Resize the window to the new size. + /// + /// The size must be without the borders, as in [`Self::subtract_borders]` were used on it. + fn resize(&mut self, width: u32, height: u32); + + /// Return the coordinates of the top-left corner of the borders relative to the content. + /// + /// Values should thus be negative. + fn location(&self) -> (i32, i32) { + (0, 0) + } + + /// Subtract the borders from the given `width` and `height`. + fn subtract_borders(&self, width: u32, height: u32) -> (u32, u32); + + /// Add the borders to the given `width` and `height`. + fn add_borders(&self, width: u32, height: u32) -> (u32, u32); + + /// Whether the given frame is dirty and should be redrawn. + fn is_dirty(&self) -> bool; + + /// Redraw the decorations frame. + /// + /// You **must** commit your parent surface afterwards. + fn redraw(&mut self); + + /// Sets the frames title + fn set_title(&mut self, title: &impl Into); + + /// Sets the configuration for the frame + fn set_config(&mut self, config: Self::Config); +} + +/// The Frame action user should perform in responce to mouse click events. +#[derive(Debug, Clone, Copy)] +pub enum FrameAction { + /// The window should be minimized. + Minimize, + /// The window should be maximized. + Maximize, + /// The window should be unmaximized. + UnMaximize, + /// The window should be closed. + Close, + /// An interactive move should be started. + Move, + /// An interactive resize should be started with the provided edge. + Resize(ResizeEdge), + /// Show window menu. + /// + /// The coordinates are relative to the parent surface, as in should be directly passed + /// to the `xdg_toplevel::show_window_menu`. + ShowMenu(i32, i32), +} + +/// The user clicked or touched the decoractions frame. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum FrameClick { + /// The user done normal click, likely with left mouse button or single finger touch. + Normal, + + /// The user done right mouse click or some touch sequence that was treated as alternate click. + /// + /// The alternate click exists solely to provide alternative action, like show window + /// menu when doing right mouse button cilck on the header decorations, nothing more. + Alternate, +} diff --git a/src/shell/xdg/mod.rs b/src/shell/xdg/mod.rs index a237a92b8..5ceec2e7f 100644 --- a/src/shell/xdg/mod.rs +++ b/src/shell/xdg/mod.rs @@ -25,6 +25,7 @@ use self::window::{ use super::WaylandSurface; +pub mod frame; pub mod popup; pub mod window; diff --git a/src/shell/xdg/window/mod.rs b/src/shell/xdg/window/mod.rs index a833af75c..8743eda25 100644 --- a/src/shell/xdg/window/mod.rs +++ b/src/shell/xdg/window/mod.rs @@ -208,8 +208,8 @@ impl Window { decoration.data::().and_then(|data| data.0.upgrade()).map(Window) } - pub fn show_window_menu(&self, seat: &wl_seat::WlSeat, serial: u32, position: (u32, u32)) { - self.xdg_toplevel().show_window_menu(seat, serial, position.0 as i32, position.1 as i32); + pub fn show_window_menu(&self, seat: &wl_seat::WlSeat, serial: u32, position: (i32, i32)) { + self.xdg_toplevel().show_window_menu(seat, serial, position.0, position.1); } pub fn set_title(&self, title: impl Into) {