From cbbb90c4ba3bba9155ed8a7bae3d36bbfb2cb201 Mon Sep 17 00:00:00 2001 From: Victor Berger Date: Fri, 5 Mar 2021 16:21:25 +0100 Subject: [PATCH] replace ConceptFrame with FallbackFrame --- CHANGELOG.md | 6 + Cargo.toml | 5 +- examples/image_viewer.rs | 4 +- examples/kbd_input.rs | 4 +- examples/pointer_input.rs | 4 +- examples/selection.rs | 4 +- examples/themed_frame.rs | 180 ----- .../{concept_frame.rs => fallback_frame.rs} | 631 +++++------------- src/window/mod.rs | 103 +-- 9 files changed, 195 insertions(+), 746 deletions(-) delete mode 100644 examples/themed_frame.rs rename src/window/{concept_frame.rs => fallback_frame.rs} (60%) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc12fb5f1..640137809 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ ## Unreleased +#### Breaking Changes + +- `ConceptFrame` is removed, as well as the `frames` cargo feature, and replaced by a more minimalistic + `FallbackFrame`. Dependency on `andrew` and `fontconfig` is dropped in the process. If fancier + decorations are needed, they should be implemented using the `Frame` trait. + ## 0.13.0 - 2021-03-04 #### Breaking Changes diff --git a/Cargo.toml b/Cargo.toml index c969ae2c6..cf8fbc504 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,18 +17,15 @@ nix = "0.19" dlib = "0.5" lazy_static = "1.0" memmap2 = "0.2.0" -andrew = { version = "0.3.0", optional = true } log = "0.4" wayland-client = "0.28" wayland-protocols = { version = "0.28" , features = ["client", "unstable_protocols"] } wayland-cursor = "0.28" calloop = { version = "0.6.1", optional = true } -servo-fontconfig = { version = "0.5", optional = true } [features] -default = ["frames", "calloop", "dlopen"] +default = ["calloop", "dlopen"] dlopen = ["wayland-client/dlopen"] -frames = ["andrew", "servo-fontconfig"] [dev-dependencies] image = "0.23" diff --git a/examples/image_viewer.rs b/examples/image_viewer.rs index 5b64503fe..4fa66e96d 100644 --- a/examples/image_viewer.rs +++ b/examples/image_viewer.rs @@ -6,7 +6,7 @@ use std::io::{BufWriter, Seek, SeekFrom, Write}; use sctk::reexports::client::protocol::{wl_shm, wl_surface}; use sctk::shm::MemPool; -use sctk::window::{ConceptFrame, Event as WEvent, State}; +use sctk::window::{Event as WEvent, FallbackFrame, State}; sctk::default_environment!(ImViewerExample, desktop); @@ -62,7 +62,7 @@ fn main() { // specifies the type we want to use to draw the borders. To create your own // decorations you just need an object to implement the `Frame` trait. let mut window = env - .create_window::( + .create_window::( surface, // the wl_surface that serves as the basis of this window None, // None for theme_manager, since we don't theme pointer outself image.dimensions(), // the initial internal dimensions of the window diff --git a/examples/kbd_input.rs b/examples/kbd_input.rs index 192ea2807..f5a43e347 100644 --- a/examples/kbd_input.rs +++ b/examples/kbd_input.rs @@ -7,7 +7,7 @@ use sctk::reexports::calloop; use sctk::reexports::client::protocol::{wl_keyboard, wl_shm, wl_surface}; use sctk::seat::keyboard::{map_keyboard_repeat, Event as KbEvent, RepeatKind}; use sctk::shm::MemPool; -use sctk::window::{ConceptFrame, Event as WEvent}; +use sctk::window::{Event as WEvent, FallbackFrame}; sctk::default_environment!(KbdInputExample, desktop); @@ -38,7 +38,7 @@ fn main() { let surface = env.create_surface().detach(); let mut window = env - .create_window::( + .create_window::( surface, None, dimensions, diff --git a/examples/pointer_input.rs b/examples/pointer_input.rs index 075cb7052..159317041 100644 --- a/examples/pointer_input.rs +++ b/examples/pointer_input.rs @@ -5,7 +5,7 @@ use std::io::{BufWriter, Seek, SeekFrom, Write}; use sctk::reexports::client::protocol::{wl_pointer, wl_shm, wl_surface}; use sctk::shm::MemPool; -use sctk::window::{ConceptFrame, Event as WEvent}; +use sctk::window::{Event as WEvent, FallbackFrame}; #[derive(Debug)] enum NextAction { @@ -75,7 +75,7 @@ fn main() { .detach(); let mut window = env - .create_window::( + .create_window::( surface, None, window_config.dimensions(), diff --git a/examples/selection.rs b/examples/selection.rs index e9010dc7f..4fa0b6027 100644 --- a/examples/selection.rs +++ b/examples/selection.rs @@ -8,7 +8,7 @@ use sctk::{ primary_selection::PrimarySelectionSourceEvent, seat::keyboard::{map_keyboard_repeat, Event as KbEvent, KeyState, RepeatKind}, shm::MemPool, - window::{ConceptFrame, Event as WEvent}, + window::{Event as WEvent, FallbackFrame}, }; use sctk::reexports::{ @@ -42,7 +42,7 @@ fn main() { let surface = env.create_surface().detach(); let mut window = env - .create_window::( + .create_window::( surface, None, dimensions, diff --git a/examples/themed_frame.rs b/examples/themed_frame.rs deleted file mode 100644 index bd725145a..000000000 --- a/examples/themed_frame.rs +++ /dev/null @@ -1,180 +0,0 @@ -extern crate smithay_client_toolkit as sctk; - -use std::cmp::min; -use std::io::{BufWriter, Seek, SeekFrom, Write}; - -use sctk::reexports::client::protocol::{wl_shm, wl_surface}; -use sctk::shm::MemPool; -use sctk::window::{ButtonColorSpec, ColorSpec, ConceptConfig, ConceptFrame, Event as WEvent}; - -sctk::default_environment!(ThemedFrameExample, desktop); - -// The frame configuration we will use in this example -fn create_frame_config() -> ConceptConfig { - let icon_spec = ButtonColorSpec { - hovered: ColorSpec::identical([0xFF, 0x22, 0x22, 0x22].into()), - idle: ColorSpec::identical([0xFF, 0xff, 0xff, 0xff].into()), - disabled: ColorSpec::invisible(), - }; - - ConceptConfig { - // dark theme - primary_color: ColorSpec { - active: [0xFF, 0x22, 0x22, 0x22].into(), - inactive: [0xFF, 0x33, 0x33, 0x33].into(), - }, - // white separation line - secondary_color: ColorSpec::identical([0xFF, 0xFF, 0xFF, 0xFF].into()), - // red close button - close_button: Some(( - // icon - icon_spec, - // button background - ButtonColorSpec { - hovered: ColorSpec::identical([0xFF, 0xFF, 0x00, 0x00].into()), - idle: ColorSpec::identical([0xFF, 0x88, 0x00, 0x00].into()), - disabled: ColorSpec::invisible(), - }, - )), - // green maximize button - maximize_button: Some(( - // icon - icon_spec, - // button background - ButtonColorSpec { - hovered: ColorSpec::identical([0xFF, 0x00, 0xFF, 0x00].into()), - idle: ColorSpec::identical([0xFF, 0x00, 0x88, 0x00].into()), - disabled: ColorSpec::invisible(), - }, - )), - // blue minimize button - minimize_button: Some(( - // icon - icon_spec, - // button background - ButtonColorSpec { - hovered: ColorSpec::identical([0xFF, 0x00, 0x00, 0xFF].into()), - idle: ColorSpec::identical([0xFF, 0x00, 0x00, 0x88].into()), - disabled: ColorSpec::invisible(), - }, - )), - // same font as default - title_font: Some(("sans".into(), 17.0)), - // clear text over dark background - title_color: ColorSpec::identical([0xFF, 0xD0, 0xD0, 0xD0].into()), - } -} - -fn main() { - /* - * Initial setup - */ - let (env, _display, mut queue) = sctk::new_default_environment!(ThemedFrameExample, desktop) - .expect("Unable to connect to a Wayland compositor"); - /* - * Create a buffer with window contents - */ - - let mut dimensions = (320u32, 240u32); - - /* - * Init wayland objects - */ - - let surface = env.create_surface().detach(); - - let mut window = env - .create_window::( - surface, - None, - dimensions, - move |evt, mut dispatch_data| { - let next_action = dispatch_data.get::>().unwrap(); - // Keep last event in priority order : Close > Configure > Refresh - let replace = match (&evt, &*next_action) { - (_, &None) - | (_, &Some(WEvent::Refresh)) - | (&WEvent::Configure { .. }, &Some(WEvent::Configure { .. })) - | (&WEvent::Close, _) => true, - _ => false, - }; - if replace { - *next_action = Some(evt); - } - }, - ) - .expect("Failed to create a window !"); - - window.set_title("Themed frame".to_string()); - window.set_frame_config(create_frame_config()); - - let mut pools = env.create_double_pool(|_| {}).expect("Failed to create a memory pool !"); - - /* - * Keyboard initialization - */ - - if !env.get_shell().unwrap().needs_configure() { - // initial draw to bootstrap on wl_shell - if let Some(pool) = pools.pool() { - redraw(pool, window.surface(), dimensions).expect("Failed to draw") - } - window.refresh(); - } - - let mut next_action = None; - - loop { - match next_action.take() { - Some(WEvent::Close) => break, - Some(WEvent::Refresh) => { - window.refresh(); - window.surface().commit(); - } - Some(WEvent::Configure { new_size, states }) => { - if let Some((w, h)) = new_size { - window.resize(w, h); - dimensions = (w, h) - } - println!("Window states: {:?}", states); - window.refresh(); - if let Some(pool) = pools.pool() { - redraw(pool, window.surface(), dimensions).expect("Failed to draw") - } - } - None => {} - } - - queue.dispatch(&mut next_action, |_, _, _| {}).unwrap(); - } -} - -fn redraw( - pool: &mut MemPool, - surface: &wl_surface::WlSurface, - (buf_x, buf_y): (u32, u32), -) -> Result<(), ::std::io::Error> { - // resize the pool if relevant - pool.resize((4 * buf_x * buf_y) as usize).expect("Failed to resize the memory pool."); - // write the contents, a nice color gradient =) - pool.seek(SeekFrom::Start(0))?; - { - let mut writer = BufWriter::new(&mut *pool); - for i in 0..(buf_x * buf_y) { - let x = (i % buf_x) as u32; - let y = (i / buf_x) as u32; - let r: u32 = min(((buf_x - x) * 0xFF) / buf_x, ((buf_y - y) * 0xFF) / buf_y); - let g: u32 = min((x * 0xFF) / buf_x, ((buf_y - y) * 0xFF) / buf_y); - let b: u32 = min(((buf_x - x) * 0xFF) / buf_x, (y * 0xFF) / buf_y); - let pixel: u32 = (0xFF << 24) + (r << 16) + (g << 8) + b; - writer.write_all(&pixel.to_ne_bytes())?; - } - writer.flush()?; - } - // get a buffer and attach it - let new_buffer = - pool.buffer(0, buf_x as i32, buf_y as i32, 4 * buf_x as i32, wl_shm::Format::Argb8888); - surface.attach(Some(&new_buffer), 0, 0); - surface.commit(); - Ok(()) -} diff --git a/src/window/concept_frame.rs b/src/window/fallback_frame.rs similarity index 60% rename from src/window/concept_frame.rs rename to src/window/fallback_frame.rs index b5f9752ec..9acb04b8e 100644 --- a/src/window/concept_frame.rs +++ b/src/window/fallback_frame.rs @@ -1,13 +1,7 @@ use std::cell::RefCell; use std::cmp::max; -use std::path::PathBuf; use std::rc::Rc; -use andrew::line; -use andrew::shapes::rectangle; -use andrew::text; -use andrew::{Canvas, Endian}; - use wayland_client::protocol::{ wl_compositor, wl_pointer, wl_seat, wl_shm, wl_subcompositor, wl_subsurface, wl_surface, }; @@ -15,9 +9,7 @@ use wayland_client::{Attached, DispatchData}; use log::error; -use super::{ - ARGBColor, ButtonColorSpec, ButtonState, ColorSpec, Frame, FrameRequest, State, WindowState, -}; +use super::{ButtonState, Frame, FrameRequest, State, WindowState}; use crate::seat::pointer::{ThemeManager, ThemeSpec, ThemedPointer}; use crate::shm::DoubleMemPool; @@ -25,96 +17,14 @@ use crate::shm::DoubleMemPool; * Drawing theme definitions */ -const BORDER_SIZE: u32 = 12; -const HEADER_SIZE: u32 = 30; - -/// Configuration for ConceptFrame -#[derive(Clone, Debug)] -pub struct ConceptConfig { - /// The primary color of the titlebar - pub primary_color: ColorSpec, - /// Secondary color of the theme - /// - /// Used for the division line between the titlebar and the content - pub secondary_color: ColorSpec, - /// Parameters of the "Close" (or "x") button - /// - /// (icon color, button color) - /// - /// if `None` the button will not be drawn - pub close_button: Option<(ButtonColorSpec, ButtonColorSpec)>, - /// Parameters of the "Maximize" (or "^") button - /// - /// (icon color, button color) - /// - /// if `None` the button will not be drawn - pub maximize_button: Option<(ButtonColorSpec, ButtonColorSpec)>, - /// Parameters of the "Minimize" (or "v") button - /// - /// (icon color, button color) - /// - /// if `None` the button will not be drawn - pub minimize_button: Option<(ButtonColorSpec, ButtonColorSpec)>, - /// Font configuration for the titlebar - /// - /// Font name and size. If set to `None`, the title is not drawn. - pub title_font: Option<(String, f32)>, - /// Color for drawing the title text - pub title_color: ColorSpec, -} +const BORDER_SIZE: u32 = 4; +const HEADER_SIZE: u32 = 24; -impl Default for ConceptConfig { - fn default() -> ConceptConfig { - let icon_spec = ButtonColorSpec { - idle: ColorSpec::identical([0xFF, 0x1E, 0x1E, 0x1E].into()), - hovered: ColorSpec::identical([0xFF, 0x1E, 0x1E, 0x1E].into()), - disabled: ColorSpec::invisible(), - }; +const BTN_ICON_COLOR: u32 = 0xFF1E1E1E; +const BTN_HOVER_BG: u32 = 0xFFA8A8A8; - ConceptConfig { - primary_color: ColorSpec { - active: [0xFF, 0xE6, 0xE6, 0xE6].into(), - inactive: [0xFF, 0xDC, 0xDC, 0xDC].into(), - }, - secondary_color: ColorSpec { - active: [0xFF, 0x1E, 0x1E, 0x1E].into(), - inactive: [0xFF, 0x78, 0x78, 0x78].into(), - }, - close_button: Some(( - // icon - icon_spec, - // button background - ButtonColorSpec { - idle: ColorSpec::invisible(), - hovered: ColorSpec::identical([0xFF, 0xD9, 0x43, 0x52].into()), - disabled: ColorSpec::invisible(), - }, - )), - maximize_button: Some(( - // icon - icon_spec, - // button background - ButtonColorSpec { - idle: ColorSpec::invisible(), - hovered: ColorSpec::identical([0xFF, 0x2D, 0xCB, 0x70].into()), - disabled: ColorSpec::invisible(), - }, - )), - minimize_button: Some(( - // icon - icon_spec, - // button background - ButtonColorSpec { - idle: ColorSpec::invisible(), - hovered: ColorSpec::identical([0xFF, 0x3C, 0xAD, 0xE8].into()), - disabled: ColorSpec::invisible(), - }, - )), - title_font: Some(("sans".into(), 17.0)), - title_color: ColorSpec::identical([0xFF, 0x00, 0x00, 0x00].into()), - } - } -} +const PRIMARY_COLOR_ACTIVE: u32 = 0xFFE6E6E6; +const PRIMARY_COLOR_INACTIVE: u32 = 0xFFDCDCDC; /* * Utilities @@ -212,7 +122,6 @@ struct Inner { implem: Box, maximized: bool, fullscreened: bool, - buttons: (bool, bool, bool), } impl Inner { @@ -237,15 +146,9 @@ impl Inner { } } -fn precise_location( - old: Location, - width: u32, - x: f64, - y: f64, - buttons: (bool, bool, bool), -) -> Location { +fn precise_location(old: Location, width: u32, x: f64, y: f64) -> Location { match old { - Location::Head | Location::Button(_) => find_button(x, y, width, buttons), + Location::Head | Location::Button(_) => find_button(x, y, width), Location::Top | Location::TopLeft | Location::TopRight => { if x <= f64::from(BORDER_SIZE) { @@ -271,7 +174,7 @@ fn precise_location( } } -fn find_button(x: f64, y: f64, w: u32, buttons: (bool, bool, bool)) -> Location { +fn find_button(x: f64, y: f64, w: u32) -> Location { if (w >= HEADER_SIZE) && (x >= f64::from(w - HEADER_SIZE)) && (x <= f64::from(w)) @@ -279,12 +182,7 @@ fn find_button(x: f64, y: f64, w: u32, buttons: (bool, bool, bool)) -> Location && (y >= f64::from(0)) { // first button - match buttons { - (true, _, _) => Location::Button(UIButton::Close), - (false, true, _) => Location::Button(UIButton::Maximize), - (false, false, true) => Location::Button(UIButton::Minimize), - _ => Location::Head, - } + Location::Button(UIButton::Close) } else if (w >= 2 * HEADER_SIZE) && (x >= f64::from(w - 2 * HEADER_SIZE)) && (x <= f64::from(w - HEADER_SIZE)) @@ -292,11 +190,7 @@ fn find_button(x: f64, y: f64, w: u32, buttons: (bool, bool, bool)) -> Location && (y >= f64::from(0)) { // second button - match buttons { - (true, true, _) => Location::Button(UIButton::Maximize), - (false, true, true) => Location::Button(UIButton::Minimize), - _ => Location::Head, - } + Location::Button(UIButton::Maximize) } else if (w >= 3 * HEADER_SIZE) && (x >= f64::from(w - 3 * HEADER_SIZE)) && (x <= f64::from(w - 2 * HEADER_SIZE)) @@ -304,25 +198,22 @@ fn find_button(x: f64, y: f64, w: u32, buttons: (bool, bool, bool)) -> Location && (y >= f64::from(0)) { // third button - match buttons { - (true, true, true) => Location::Button(UIButton::Minimize), - _ => Location::Head, - } + Location::Button(UIButton::Minimize) } else { Location::Head } } -/// A clean, modern and stylish set of decorations. +/// A simple set of decorations that can be used as a fallback /// -/// This class draws clean and modern decorations with -/// buttons inspired by breeze, material hover shade and -/// a white header background. +/// This class drawn some simple and minimalistic decorations around +/// a window so that it remains possible to interact with the window +/// even when server-side decorations are not available. /// -/// `ConceptFrame` is hiding its `ClientSide` decorations +/// `FallbackFrame` is hiding its `ClientSide` decorations /// in a `Fullscreen` state and brings them back if those are /// visible when unsetting `Fullscreen` state. -pub struct ConceptFrame { +pub struct FallbackFrame { base_surface: wl_surface::WlSurface, compositor: Attached, subcompositor: Attached, @@ -333,14 +224,11 @@ pub struct ConceptFrame { pointers: Vec, themer: ThemeManager, surface_version: u32, - config: ConceptConfig, - title: Option, - font_data: Option, ()>>, } -impl Frame for ConceptFrame { +impl Frame for FallbackFrame { type Error = ::std::io::Error; - type Config = ConceptConfig; + type Config = (); fn init( base_surface: &wl_surface::WlSurface, compositor: &Attached, @@ -348,7 +236,7 @@ impl Frame for ConceptFrame { shm: &Attached, theme_manager: Option, implementation: Box, - ) -> Result { + ) -> Result { let (themer, theme_over_surface) = if let Some(theme_manager) = theme_manager { (theme_manager, false) } else { @@ -363,7 +251,6 @@ impl Frame for ConceptFrame { theme_over_surface, maximized: false, fullscreened: false, - buttons: (true, true, true), })); let my_inner = inner.clone(); @@ -373,7 +260,7 @@ impl Frame for ConceptFrame { (&mut my_inner.borrow_mut().implem)(FrameRequest::Refresh, 0, ddata); })?; - Ok(ConceptFrame { + Ok(FallbackFrame { base_surface: base_surface.clone(), compositor: compositor.clone(), subcompositor: subcompositor.clone(), @@ -384,9 +271,6 @@ impl Frame for ConceptFrame { pointers: Vec::new(), themer, surface_version: compositor.as_ref().version(), - config: ConceptConfig::default(), - title: None, - font_data: None, }) } @@ -406,7 +290,6 @@ impl Frame for ConceptFrame { inner.size.0, surface_x, surface_y, - inner.buttons, ); data.position = (surface_x, surface_y); change_pointer(&pointer, &inner, data.location, Some(serial)) @@ -418,13 +301,8 @@ impl Frame for ConceptFrame { } Event::Motion { surface_x, surface_y, .. } => { data.position = (surface_x, surface_y); - let newpos = precise_location( - data.location, - inner.size.0, - surface_x, - surface_y, - inner.buttons, - ); + let newpos = + precise_location(data.location, inner.size.0, surface_x, surface_y); if newpos != data.location { match (newpos, data.location) { (Location::Button(_), _) | (_, Location::Button(_)) => { @@ -594,28 +472,23 @@ impl Frame for ConceptFrame { { let mmap = pool.mmap(); { - let color = self.config.primary_color.get_for(self.active).into(); - - let mut header_canvas = Canvas::new( - &mut mmap - [0..scaled_header_height as usize * scaled_header_width as usize * 4], - scaled_header_width as usize, - scaled_header_height as usize, - scaled_header_width as usize * 4, - Endian::native(), - ); - header_canvas.clear(); - - let header_bar = rectangle::Rectangle::new( - (0, 0), - (scaled_header_width as usize, scaled_header_height as usize), - None, - Some(color), - ); - header_canvas.draw(&header_bar); + let canvas = &mut mmap + [0..scaled_header_height as usize * scaled_header_width as usize * 4]; + let color = if self.active == WindowState::Active { + PRIMARY_COLOR_ACTIVE.to_ne_bytes() + } else { + PRIMARY_COLOR_INACTIVE.to_ne_bytes() + }; + + for pixel in canvas.chunks_exact_mut(4) { + pixel[0] = color[0]; + pixel[1] = color[1]; + pixel[2] = color[2]; + pixel[3] = color[3]; + } draw_buttons( - &mut header_canvas, + canvas, width, header_scale, inner.resizable, @@ -633,77 +506,16 @@ impl Frame for ConceptFrame { } }) .collect::>(), - &self.config, ); - if let Some((ref font_face, font_size)) = self.config.title_font { - if let Some(title) = self.title.clone() { - // If theres no stored font data, find the first ttf regular sans font and - // store it - if self.font_data.is_none() { - let font_bytes = - find_font(&font_face).and_then(|font| std::fs::read(font).ok()); - match font_bytes { - Some(bytes) => self.font_data = Some(Ok(bytes)), - None => { - error!("No font could be found"); - self.font_data = Some(Err(())) - } - } - } - // Create text from stored title and font data - if let Some(Ok(ref font_data)) = self.font_data { - let title_color = self.config.title_color.get_for(self.active); - let mut title_text = text::Text::new( - ( - 0, - (HEADER_SIZE as usize / 2) - .saturating_sub((font_size / 2.0).ceil() as usize) - * header_scale as usize, - ), - title_color.into(), - font_data, - font_size * header_scale as f32, - 1.0, - title, - ); - - let mut button_count = 0isize; - if self.config.close_button.is_some() { - button_count += 1; - } - if self.config.maximize_button.is_some() { - button_count += 1; - } - if self.config.minimize_button.is_some() { - button_count += 1; - } - - let scaled_button_size = - HEADER_SIZE as isize * header_scale as isize; - let button_space = button_count * scaled_button_size; - let scaled_header_width = width as isize * header_scale as isize; - - // Check if text is bigger then the available width - if (scaled_header_width - button_space) - > (title_text.get_width() as isize + scaled_button_size) - { - title_text.pos.0 = - (scaled_header_width - button_space) as usize / 2 - - (title_text.get_width() / 2); - header_canvas.draw(&title_text); - } - } - } - } - } - - // For each pixel in borders - { - for b in &mut mmap - [scaled_header_height as usize * scaled_header_width as usize * 4..] - { - *b = 0x00; + // For each pixel in borders + let border_canvas = &mut mmap + [scaled_header_height as usize * scaled_header_width as usize * 4..]; + for pixel in border_canvas.chunks_exact_mut(4) { + pixel[0] = color[0]; + pixel[1] = color[1]; + pixel[2] = color[2]; + pixel[3] = color[3]; } } if let Err(err) = mmap.flush() { @@ -855,7 +667,7 @@ impl Frame for ConceptFrame { if self.hidden || self.inner.borrow().fullscreened { (width, height) } else { - (width, height - HEADER_SIZE as i32) + (width - 2 * BORDER_SIZE as i32, height - HEADER_SIZE as i32 - 2 * BORDER_SIZE as i32) } } @@ -863,7 +675,7 @@ impl Frame for ConceptFrame { if self.hidden || self.inner.borrow().fullscreened { (width, height) } else { - (width, height + HEADER_SIZE as i32) + (width + 2 * BORDER_SIZE as i32, height + HEADER_SIZE as i32 + 2 * BORDER_SIZE as i32) } } @@ -871,26 +683,16 @@ impl Frame for ConceptFrame { if self.hidden || self.inner.borrow().fullscreened { (0, 0) } else { - (0, -(HEADER_SIZE as i32)) + (-(BORDER_SIZE as i32), -(HEADER_SIZE as i32 + BORDER_SIZE as i32)) } } - fn set_config(&mut self, config: ConceptConfig) { - self.config = config; - let mut inner = self.inner.borrow_mut(); - inner.buttons = ( - self.config.close_button.is_some(), - self.config.maximize_button.is_some(), - self.config.minimize_button.is_some(), - ); - } + fn set_config(&mut self, _config: ()) {} - fn set_title(&mut self, title: String) { - self.title = Some(title); - } + fn set_title(&mut self, _title: String) {} } -impl Drop for ConceptFrame { +impl Drop for FallbackFrame { fn drop(&mut self) { for ptr in self.pointers.drain(..) { if ptr.as_ref().version() >= 3 { @@ -982,122 +784,82 @@ fn request_for_location_on_rmb(pointer_data: &PointerUserData) -> Option ARGBColor { - #[inline] - fn gamma_mix(x: u8, y: u8) -> u8 { - let x = x as f32 / 255.0; - let y = y as f32 / 255.0; - let z = ((x * x + y * y) / 2.0).sqrt(); - (z * 255.0) as u8 - } - - ARGBColor { - a: x.a.min(y.a), - r: gamma_mix(x.r, y.r), - g: gamma_mix(x.g, y.g), - b: gamma_mix(x.b, y.b), - } -} - fn draw_buttons( - canvas: &mut Canvas, + canvas: &mut [u8], width: u32, scale: u32, maximizable: bool, state: WindowState, mouses: &[Location], - config: &ConceptConfig, ) { let scale = scale as usize; - // Draw seperator between header and window contents - let line_color = config.secondary_color.get_for(state); - for i in 1..=scale { - let y = HEADER_SIZE as usize * scale - i; - let division_line = - line::Line::new((0, y), (width as usize * scale, y), line_color.into(), false); - canvas.draw(&division_line); - } - - let mut drawn_buttons = 0usize; - if width >= HEADER_SIZE { - if let Some((ref icon_config, ref btn_config)) = config.close_button { - // Draw the close button - let btn_state = if mouses.iter().any(|&l| l == Location::Button(UIButton::Close)) { - ButtonState::Hovered - } else { - ButtonState::Idle - }; - - let icon_color = icon_config.get_for(btn_state).get_for(state); - let button_color = btn_config.get_for(btn_state).get_for(state); + // Draw the close button + let btn_state = if mouses.iter().any(|&l| l == Location::Button(UIButton::Close)) { + ButtonState::Hovered + } else { + ButtonState::Idle + }; - draw_button(canvas, 0, scale, button_color, mix_colors(button_color, line_color)); - draw_icon(canvas, 0, scale, icon_color, Icon::Close); - drawn_buttons += 1; + if state == WindowState::Active && btn_state == ButtonState::Hovered { + draw_button(canvas, 0, scale, width as usize, BTN_HOVER_BG.to_ne_bytes()); } + draw_icon(canvas, width as usize, 0, scale, BTN_ICON_COLOR.to_ne_bytes(), Icon::Close); } - if width as usize >= (drawn_buttons + 1) * HEADER_SIZE as usize { - if let Some((ref icon_config, ref btn_config)) = config.maximize_button { - let btn_state = if !maximizable { - ButtonState::Disabled - } else if mouses.iter().any(|&l| l == Location::Button(UIButton::Maximize)) { - ButtonState::Hovered - } else { - ButtonState::Idle - }; - - let icon_color = icon_config.get_for(btn_state).get_for(state); - let button_color = btn_config.get_for(btn_state).get_for(state); + if width as usize >= 2 * HEADER_SIZE as usize { + let btn_state = if !maximizable { + ButtonState::Disabled + } else if mouses.iter().any(|&l| l == Location::Button(UIButton::Maximize)) { + ButtonState::Hovered + } else { + ButtonState::Idle + }; + if state == WindowState::Active && btn_state == ButtonState::Hovered { draw_button( canvas, - drawn_buttons * HEADER_SIZE as usize, - scale, - button_color, - mix_colors(button_color, line_color), - ); - draw_icon( - canvas, - drawn_buttons * HEADER_SIZE as usize, + HEADER_SIZE as usize, scale, - icon_color, - Icon::Maximize, + width as usize, + BTN_HOVER_BG.to_ne_bytes(), ); - drawn_buttons += 1; } + draw_icon( + canvas, + width as usize, + HEADER_SIZE as usize, + scale, + BTN_ICON_COLOR.to_ne_bytes(), + Icon::Maximize, + ); } - if width as usize >= (drawn_buttons + 1) * HEADER_SIZE as usize { - if let Some((ref icon_config, ref btn_config)) = config.minimize_button { - let btn_state = if mouses.iter().any(|&l| l == Location::Button(UIButton::Minimize)) { - ButtonState::Hovered - } else { - ButtonState::Idle - }; - - let icon_color = icon_config.get_for(btn_state).get_for(state); - let button_color = btn_config.get_for(btn_state).get_for(state); + if width as usize >= 3 * HEADER_SIZE as usize { + let btn_state = if mouses.iter().any(|&l| l == Location::Button(UIButton::Minimize)) { + ButtonState::Hovered + } else { + ButtonState::Idle + }; + if state == WindowState::Active && btn_state == ButtonState::Hovered { draw_button( canvas, - drawn_buttons * HEADER_SIZE as usize, + 2 * HEADER_SIZE as usize, scale, - button_color, - mix_colors(button_color, line_color), - ); - draw_icon( - canvas, - drawn_buttons * HEADER_SIZE as usize, - scale, - icon_color, - Icon::Minimize, + width as usize, + BTN_HOVER_BG.to_ne_bytes(), ); } + draw_icon( + canvas, + width as usize, + 2 * HEADER_SIZE as usize, + scale, + BTN_ICON_COLOR.to_ne_bytes(), + Icon::Minimize, + ); } } @@ -1107,140 +869,101 @@ enum Icon { Minimize, } -fn draw_button( - canvas: &mut Canvas, - x_offset: usize, - scale: usize, - btn_color: ARGBColor, - line_color: ARGBColor, -) { +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 = canvas.width / scale - h - x_offset; + let x_start = width - h - x_offset; // main square - canvas.draw(&rectangle::Rectangle::new( - (x_start * scale, 0), - (h * scale, (h - 1) * scale), - None, - Some(btn_color.into()), - )); - // separation line - canvas.draw(&rectangle::Rectangle::new( - (x_start * scale, (h - 1) * scale), - (h * scale, scale), - None, - Some(line_color.into()), - )); + 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 Canvas, + canvas: &mut [u8], + width: usize, x_offset: usize, scale: usize, - icon_color: ARGBColor, + icon_color: [u8; 4], icon: Icon, ) { let h = HEADER_SIZE as usize; - let cx = canvas.width / scale - h / 2 - x_offset; - let cy = h / 2; - let s = scale; + let sh = scale * h; + let x_start = width - h - x_offset; match icon { Icon::Close => { - // Draw cross to represent the close button - for i in 0..2 * scale { - canvas.draw(&line::Line::new( - ((cx - 4) * s + i, (cy - 4) * s), - ((cx + 4) * s, (cy + 4) * s - i), - icon_color.into(), - true, - )); - canvas.draw(&line::Line::new( - ((cx - 4) * s, (cy - 4) * s + i), - ((cx + 4) * s - i, (cy + 4) * s), - icon_color.into(), - true, - )); - canvas.draw(&line::Line::new( - ((cx + 4) * s - i, (cy - 4) * s), - ((cx - 4) * s, (cy + 4) * s - i), - icon_color.into(), - true, - )); - canvas.draw(&line::Line::new( - ((cx + 4) * s, (cy - 4) * s + i), - ((cx - 4) * s + i, (cy + 4) * s), - icon_color.into(), - true, - )); + // Draw black rectangle + for y in sh / 4..3 * sh / 4 { + let line = &mut canvas[(x_start + y * width + sh / 4) * 4 * scale + ..(x_start + y * width + 3 * sh / 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]; + } } } Icon::Maximize => { - for i in 0..3 * scale { - canvas.draw(&line::Line::new( - ((cx - 4) * s - i, (cy + 2) * s), - (cx * s, (cy - 2) * s - i), - icon_color.into(), - true, - )); - canvas.draw(&line::Line::new( - ((cx + 4) * s + i, (cy + 2) * s), - (cx * s, (cy - 2) * s - i), - icon_color.into(), - true, - )); + // Draw an empty rectangle + for y in 2 * sh / 8..3 * sh / 8 { + let line = &mut canvas[(x_start + y * width + sh / 4) * 4 * scale + ..(x_start + y * width + 3 * sh / 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 * sh / 8) * 4 * scale + ..(x_start + y * width + 3 * sh / 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 * sh / 8) * 4 * scale + ..(x_start + y * width + 6 * sh / 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 + sh / 4) * 4 * scale + ..(x_start + y * width + 3 * sh / 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]; + } } } Icon::Minimize => { - for i in 0..3 * scale { - canvas.draw(&line::Line::new( - ((cx - 4) * s - i, (cy - 3) * s), - (cx * s, (cy + 1) * s + i), - icon_color.into(), - true, - )); - canvas.draw(&line::Line::new( - ((cx + 4) * s + i, (cy - 3) * s), - (cx * s, (cy + 1) * s + i), - icon_color.into(), - true, - )); + // Draw an underline + for y in 5 * sh / 8..3 * sh / 4 { + let line = &mut canvas[(x_start + y * width + sh / 4) * 4 * scale + ..(x_start + y * width + 3 * sh / 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]; + } } } } } - -fn find_font(family: &str) -> Option { - use fontconfig::fontconfig::*; - use std::ffi::{CStr, CString}; - - unsafe { - let fc = FcInitLoadConfigAndFonts(); - - let name = CString::new(family.as_bytes()).unwrap(); - let pattern = FcNameParse(name.as_ptr() as *const _); - // regular font - FcPatternAddInteger(pattern, b"weight\0".as_ptr() as *const _, 80); - FcPatternAddInteger(pattern, b"slant\0".as_ptr() as *const _, 0); - FcConfigSubstitute(fc, pattern, FcMatchPattern); - FcDefaultSubstitute(pattern); - - let mut result = FcResultNoMatch; - let font_pat = FcFontMatch(fc, pattern, &mut result); - - let ret = if font_pat.is_null() { - None - } else { - let mut filename_ptr = std::ptr::null_mut(); - FcPatternGetString(font_pat, b"file\0".as_ptr() as *const _, 0, &mut filename_ptr); - let path = PathBuf::from( - CStr::from_ptr(filename_ptr as *const _).to_string_lossy().into_owned(), - ); - FcPatternDestroy(font_pat); - Some(path) - }; - - FcConfigDestroy(fc); - - ret - } -} diff --git a/src/window/mod.rs b/src/window/mod.rs index aade1f48d..2736647a1 100644 --- a/src/window/mod.rs +++ b/src/window/mod.rs @@ -22,10 +22,8 @@ use crate::{ shell, }; -#[cfg(feature = "frames")] -mod concept_frame; -#[cfg(feature = "frames")] -pub use self::concept_frame::{ConceptConfig, ConceptFrame}; +mod fallback_frame; +pub use self::fallback_frame::FallbackFrame; // Defines the minimum window size. Minimum width is set to 2 pixels to circumvent // a bug in mutter - https://gitlab.gnome.org/GNOME/mutter/issues/259 @@ -499,6 +497,7 @@ impl Window { // the server side decorations if some are presented. decoration.destroy(); self.decoration = None; + self.frame.borrow_mut().set_hidden(false); } Decorations::ServerSide => { decoration.set_mode(Mode::ServerSide); @@ -813,99 +812,3 @@ where Window::::init_with_decorations(self, surface, theme_manager, initial_dims, callback) } } - -// -// Some helpers for Frame configuration -// - -/// Color specification to be used in Frame configuration -/// -/// It regroups two colors, one for when the window is active and -/// one for when it is not. -#[derive(Copy, Clone, Debug)] -pub struct ColorSpec { - /// The active color - pub active: ARGBColor, - /// The inactive color - pub inactive: ARGBColor, -} - -impl ColorSpec { - /// Access the color associated with a certain window state - #[inline] - pub fn get_for(self, state: WindowState) -> ARGBColor { - match state { - WindowState::Active => self.active, - WindowState::Inactive => self.inactive, - } - } - - /// Create a ColorSpec that is always the same color - #[inline] - pub const fn identical(color: ARGBColor) -> ColorSpec { - ColorSpec { active: color, inactive: color } - } - - /// Create a ColorSpec corresponding to an always invisible color - #[inline] - pub const fn invisible() -> ColorSpec { - ColorSpec::identical(ARGBColor::zero()) - } -} - -/// A color specification associated with a button -/// -/// It regroups 3 color specifications depending on the state of the -/// button: idle, hovered, or disabled. -#[derive(Copy, Clone, Debug)] -pub struct ButtonColorSpec { - /// ColorSpec for an idle button - pub idle: ColorSpec, - /// ColorSpec for an hovered button - pub hovered: ColorSpec, - /// ColorSpec for a disabled button - pub disabled: ColorSpec, -} - -impl ButtonColorSpec { - /// Get the ColorSpec associated with a given button state - pub fn get_for(&self, state: ButtonState) -> ColorSpec { - match state { - ButtonState::Idle => self.idle, - ButtonState::Hovered => self.hovered, - ButtonState::Disabled => self.disabled, - } - } -} - -/// Unambiguous representation of an ARGB color -#[derive(Copy, Clone, PartialEq, Eq, Debug)] -pub struct ARGBColor { - /// Alpha channel - pub a: u8, - /// Red channel - pub r: u8, - /// Green channel - pub g: u8, - /// Blue channel - pub b: u8, -} - -impl ARGBColor { - /// The invisible `#00000000` color - pub const fn zero() -> ARGBColor { - ARGBColor { a: 0, r: 0, g: 0, b: 0 } - } -} - -impl From for [u8; 4] { - fn from(color: ARGBColor) -> [u8; 4] { - [color.a, color.r, color.g, color.b] - } -} - -impl From<[u8; 4]> for ARGBColor { - fn from(array: [u8; 4]) -> ARGBColor { - ARGBColor { a: array[0], r: array[1], g: array[2], b: array[3] } - } -}