From 6916f03e9492163c42e2d81ec28dd7548b4a5a32 Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Sun, 15 May 2022 20:10:04 +0200 Subject: [PATCH] config: clean up and document theming --- jay-config/src/_private/client.rs | 121 +++++++----- jay-config/src/_private/ipc.rs | 65 ++++--- jay-config/src/input.rs | 8 + jay-config/src/theme.rs | 297 ++++++++++++++++++++++++++--- src/config/handler.rs | 190 +++++++++++------- src/ifs/wl_seat.rs | 16 +- src/it/tests/t0002_window.rs | 4 +- src/it/tests/t0003_multi_window.rs | 4 +- src/render/renderer/framebuffer.rs | 2 +- src/render/renderer/renderer.rs | 37 ++-- src/state.rs | 4 +- src/text.rs | 2 +- src/theme.rs | 175 +++++++++++++---- src/tree/container.rs | 41 ++-- src/tree/float.rs | 24 +-- src/tree/output.rs | 37 ++-- src/tree/placeholder.rs | 3 +- 17 files changed, 745 insertions(+), 285 deletions(-) diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index d981ab03..54f2ed93 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -13,7 +13,7 @@ use { }, input::{acceleration::AccelProfile, capability::Capability, InputDevice, Seat}, keyboard::keymap::Keymap, - theme::Color, + theme::{colors::Colorable, sized::Resizable, Color}, Axis, Command, Direction, LogLevel, ModifiedKeySym, PciId, Timer, Workspace, }, std::{ @@ -149,7 +149,7 @@ pub unsafe extern "C" fn handle_msg(data: *const u8, msg: *const u8, size: usize } macro_rules! get_response { - ($res:expr, $def:expr, $ty:ident, $($field:ident),+) => { + ($res:expr, $def:expr, $ty:ident { $($field:ident),+ }) => { let ($($field,)+) = match $res { Response::$ty { $($field,)+ } => ($($field,)+), _ => { @@ -231,19 +231,19 @@ impl Client { pub fn seats(&self) -> Vec { let res = self.send_with_response(&ClientMessage::GetSeats); - get_response!(res, vec![], GetSeats, seats); + get_response!(res, vec![], GetSeats { seats }); seats } pub fn mono(&self, seat: Seat) -> bool { let res = self.send_with_response(&ClientMessage::GetMono { seat }); - get_response!(res, false, GetMono, mono); + get_response!(res, false, GetMono { mono }); mono } pub fn get_timer(&self, name: &str) -> Timer { let res = self.send_with_response(&ClientMessage::GetTimer { name }); - get_response!(res, Timer(0), GetTimer, timer); + get_response!(res, Timer(0), GetTimer { timer }); timer } @@ -270,13 +270,13 @@ impl Client { pub fn get_workspace(&self, name: &str) -> Workspace { let res = self.send_with_response(&ClientMessage::GetWorkspace { name }); - get_response!(res, Workspace(0), GetWorkspace, workspace); + get_response!(res, Workspace(0), GetWorkspace { workspace }); workspace } pub fn get_connector(&self, ty: ConnectorType, idx: u32) -> Connector { let res = self.send_with_response(&ClientMessage::GetConnector { ty, idx }); - get_response!(res, Connector(0), GetConnector, connector); + get_response!(res, Connector(0), GetConnector { connector }); connector } @@ -290,7 +290,7 @@ impl Client { pub fn split(&self, seat: Seat) -> Axis { let res = self.send_with_response(&ClientMessage::GetSplit { seat }); - get_response!(res, Axis::Horizontal, GetSplit, axis); + get_response!(res, Axis::Horizontal, GetSplit { axis }); axis } @@ -300,48 +300,64 @@ impl Client { pub fn get_fullscreen(&self, seat: Seat) -> bool { let res = self.send_with_response(&ClientMessage::GetFullscreen { seat }); - get_response!(res, false, GetFullscreen, fullscreen); + get_response!(res, false, GetFullscreen { fullscreen }); fullscreen } + pub fn reset_font(&self) { + self.send(&ClientMessage::ResetFont); + } + + pub fn set_font(&self, font: &str) { + self.send(&ClientMessage::SetFont { font }); + } + + pub fn get_font(&self) -> String { + let res = self.send_with_response(&ClientMessage::GetFont); + get_response!(res, String::new(), GetFont { font }); + font + } + + pub fn get_floating(&self, seat: Seat) -> bool { + let res = self.send_with_response(&ClientMessage::GetFloating { seat }); + get_response!(res, false, GetFloating { floating }); + floating + } + + pub fn set_floating(&self, seat: Seat, floating: bool) { + self.send(&ClientMessage::SetFloating { seat, floating }); + } + pub fn toggle_floating(&self, seat: Seat) { - self.send(&ClientMessage::ToggleFloating { seat }); + self.set_floating(seat, !self.get_floating(seat)); } - pub fn set_title_color(&self, color: Color) { - self.send(&ClientMessage::SetTitleColor { color }); + pub fn reset_colors(&self) { + self.send(&ClientMessage::ResetColors); } - pub fn set_border_color(&self, color: Color) { - self.send(&ClientMessage::SetBorderColor { color }); + pub fn reset_sizes(&self) { + self.send(&ClientMessage::ResetSizes); } - pub fn set_title_underline_color(&self, color: Color) { - self.send(&ClientMessage::SetTitleUnderlineColor { color }); + pub fn get_color(&self, colorable: Colorable) -> Color { + let res = self.send_with_response(&ClientMessage::GetColor { colorable }); + get_response!(res, Color::BLACK, GetColor { color }); + color } - pub fn set_background_color(&self, color: Color) { - self.send(&ClientMessage::SetBackgroundColor { color }); + pub fn set_color(&self, colorable: Colorable, color: Color) { + self.send(&ClientMessage::SetColor { colorable, color }); } - pub fn get_title_height(&self) -> i32 { - let res = self.send_with_response(&ClientMessage::GetTitleHeight); - get_response!(res, 0, GetTitleHeight, height); - height + pub fn get_size(&self, sized: Resizable) -> i32 { + let res = self.send_with_response(&ClientMessage::GetSize { sized }); + get_response!(res, 0, GetSize { size }); + size } - pub fn get_border_width(&self) -> i32 { - let res = self.send_with_response(&ClientMessage::GetBorderWidth); - get_response!(res, 0, GetBorderWidth, width); - width - } - - pub fn set_title_height(&self, height: i32) { - self.send(&ClientMessage::SetTitleHeight { height }) - } - - pub fn set_border_width(&self, width: i32) { - self.send(&ClientMessage::SetBorderWidth { width }) + pub fn set_size(&self, sized: Resizable, size: i32) { + self.send(&ClientMessage::SetSize { sized, size }) } pub fn set_mono(&self, seat: Seat, mono: bool) { @@ -374,13 +390,13 @@ impl Client { pub fn get_seat(&self, name: &str) -> Seat { let res = self.send_with_response(&ClientMessage::GetSeat { name }); - get_response!(res, Seat(0), GetSeat, seat); + get_response!(res, Seat(0), GetSeat { seat }); seat } pub fn get_input_devices(&self, seat: Option) -> Vec { let res = self.send_with_response(&ClientMessage::GetInputDevices { seat }); - get_response!(res, vec!(), GetInputDevices, devices); + get_response!(res, vec!(), GetInputDevices { devices }); devices } @@ -406,43 +422,43 @@ impl Client { pub fn device_connectors(&self, device: DrmDevice) -> Vec { let res = self.send_with_response(&ClientMessage::GetDeviceConnectors { device }); - get_response!(res, vec![], GetDeviceConnectors, connectors); + get_response!(res, vec![], GetDeviceConnectors { connectors }); connectors } pub fn drm_device_syspath(&self, device: DrmDevice) -> String { let res = self.send_with_response(&ClientMessage::GetDrmDeviceSyspath { device }); - get_response!(res, String::new(), GetDrmDeviceSyspath, syspath); + get_response!(res, String::new(), GetDrmDeviceSyspath { syspath }); syspath } pub fn drm_device_vendor(&self, device: DrmDevice) -> String { let res = self.send_with_response(&ClientMessage::GetDrmDeviceVendor { device }); - get_response!(res, String::new(), GetDrmDeviceVendor, vendor); + get_response!(res, String::new(), GetDrmDeviceVendor { vendor }); vendor } pub fn drm_device_model(&self, device: DrmDevice) -> String { let res = self.send_with_response(&ClientMessage::GetDrmDeviceModel { device }); - get_response!(res, String::new(), GetDrmDeviceModel, model); + get_response!(res, String::new(), GetDrmDeviceModel { model }); model } pub fn drm_device_pci_id(&self, device: DrmDevice) -> PciId { let res = self.send_with_response(&ClientMessage::GetDrmDevicePciId { device }); - get_response!(res, Default::default(), GetDrmDevicePciId, pci_id); + get_response!(res, Default::default(), GetDrmDevicePciId { pci_id }); pci_id } pub fn connector_connected(&self, connector: Connector) -> bool { let res = self.send_with_response(&ClientMessage::ConnectorConnected { connector }); - get_response!(res, false, ConnectorConnected, connected); + get_response!(res, false, ConnectorConnected { connected }); connected } pub fn connector_type(&self, connector: Connector) -> ConnectorType { let res = self.send_with_response(&ClientMessage::ConnectorType { connector }); - get_response!(res, CON_UNKNOWN, ConnectorType, ty); + get_response!(res, CON_UNKNOWN, ConnectorType { ty }); ty } @@ -451,10 +467,11 @@ impl Client { get_response!( res, Mode::zeroed(), - ConnectorMode, - width, - height, - refresh_millihz + ConnectorMode { + width, + height, + refresh_millihz + } ); Mode { width, @@ -465,7 +482,7 @@ impl Client { pub fn drm_devices(&self) -> Vec { let res = self.send_with_response(&ClientMessage::GetDrmDevices); - get_response!(res, vec![], GetDrmDevices, devices); + get_response!(res, vec![], GetDrmDevices { devices }); devices } @@ -514,13 +531,13 @@ impl Client { pub fn device_name(&self, device: InputDevice) -> String { let res = self.send_with_response(&ClientMessage::GetDeviceName { device }); - get_response!(res, String::new(), GetDeviceName, name); + get_response!(res, String::new(), GetDeviceName { name }); name } pub fn has_capability(&self, device: InputDevice, cap: Capability) -> bool { let res = self.send_with_response(&ClientMessage::HasCapability { device, cap }); - get_response!(res, false, HasCapability, has); + get_response!(res, false, HasCapability { has }); has } @@ -534,13 +551,13 @@ impl Client { pub fn seat_get_repeat_rate(&self, seat: Seat) -> (i32, i32) { let res = self.send_with_response(&ClientMessage::SeatGetRepeatRate { seat }); - get_response!(res, (25, 250), GetRepeatRate, rate, delay); + get_response!(res, (25, 250), GetRepeatRate { rate, delay }); (rate, delay) } pub fn parse_keymap(&self, keymap: &str) -> Keymap { let res = self.send_with_response(&ClientMessage::ParseKeymap { keymap }); - get_response!(res, Keymap(0), ParseKeymap, keymap); + get_response!(res, Keymap(0), ParseKeymap { keymap }); keymap } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 643eaeae..0dba3258 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -3,7 +3,7 @@ use { drm::{connector_type::ConnectorType, Connector, DrmDevice}, input::{acceleration::AccelProfile, capability::Capability, InputDevice, Seat}, keyboard::{keymap::Keymap, mods::Modifiers, syms::KeySym}, - theme::Color, + theme::{colors::Colorable, sized::Resizable, Color}, Axis, Direction, LogLevel, PciId, Timer, Workspace, }, bincode::{BorrowDecode, Decode, Encode}, @@ -56,6 +56,11 @@ pub enum ServerMessage { #[derive(Encode, BorrowDecode, Debug)] pub enum ClientMessage<'a> { + Reload, + Quit, + SwitchTo { + vtnr: u32, + }, Log { level: LogLevel, msg: &'a str, @@ -65,10 +70,6 @@ pub enum ClientMessage<'a> { GetSeat { name: &'a str, }, - Quit, - SwitchTo { - vtnr: u32, - }, SetSeat { device: InputDevice, seat: Seat, @@ -139,24 +140,20 @@ pub enum ClientMessage<'a> { kb: InputDevice, grab: bool, }, - GetTitleHeight, - GetBorderWidth, - SetTitleHeight { - height: i32, + ResetSizes, + GetSize { + sized: Resizable, }, - SetBorderWidth { - width: i32, + SetSize { + sized: Resizable, + size: i32, }, - SetTitleColor { - color: Color, + ResetColors, + GetColor { + colorable: Colorable, }, - SetTitleUnderlineColor { - color: Color, - }, - SetBorderColor { - color: Color, - }, - SetBackgroundColor { + SetColor { + colorable: Colorable, color: Color, }, CreateSplit { @@ -169,9 +166,13 @@ pub enum ClientMessage<'a> { FocusParent { seat: Seat, }, - ToggleFloating { + GetFloating { seat: Seat, }, + SetFloating { + seat: Seat, + floating: bool, + }, HasCapability { device: InputDevice, cap: Capability, @@ -246,7 +247,6 @@ pub enum ClientMessage<'a> { GetFullscreen { seat: Seat, }, - Reload, GetDeviceConnectors { device: DrmDevice, }, @@ -263,6 +263,11 @@ pub enum ClientMessage<'a> { GetDrmDevicePciId { device: DrmDevice, }, + ResetFont, + GetFont, + SetFont { + font: &'a str, + }, } #[derive(Encode, Decode, Debug)] @@ -290,11 +295,8 @@ pub enum Response { GetInputDevices { devices: Vec, }, - GetTitleHeight { - height: i32, - }, - GetBorderWidth { - width: i32, + GetSize { + size: i32, }, HasCapability { has: bool, @@ -343,6 +345,15 @@ pub enum Response { GetDrmDevicePciId { pci_id: PciId, }, + GetFloating { + floating: bool, + }, + GetColor { + color: Color, + }, + GetFont { + font: String, + }, } #[derive(Encode, Decode, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 61261930..25d2f74e 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -123,6 +123,14 @@ impl Seat { get!().close(self); } + pub fn get_floating(self) -> bool { + get!().get_floating(self) + } + + pub fn set_floating(self, floating: bool) { + get!().set_floating(self, floating); + } + pub fn toggle_floating(self) { get!().toggle_floating(self); } diff --git a/jay-config/src/theme.rs b/jay-config/src/theme.rs index cb5020ba..7fe4509c 100644 --- a/jay-config/src/theme.rs +++ b/jay-config/src/theme.rs @@ -1,45 +1,290 @@ -use bincode::{BorrowDecode, Encode}; +//! Knobs for changing the look of the compositor. -#[derive(Encode, BorrowDecode, Debug)] +use bincode::{Decode, Encode}; + +/// A color. +/// +/// When specifying RGBA values of a color, the RGB values can either be specified +/// *straight* or *premultiplied*. Premultiplied means that the RGB values have already +/// been multiplied by the alpha value. +/// +/// Given a color, to reduce its opacity by half, +/// +/// - if you're working with premultiplied values, you would multiply each component by `0.5`; +/// - if you're working with straight values, you would multiply only the alpha component by `0.5`. +/// +/// When using hexadecimal notation, `#RRGGBBAA`, the RGB values are usually straight. +// values are stored premultiplied +#[derive(Encode, Decode, Debug)] pub struct Color { - pub r: u8, - pub g: u8, - pub b: u8, - pub a: u8, + r: f32, + g: f32, + b: f32, + a: f32, } -pub fn set_title_color(color: Color) { - get!().set_title_color(color) +fn to_f32(c: u8) -> f32 { + c as f32 / 255f32 } -pub fn set_title_underline_color(color: Color) { - get!().set_title_underline_color(color) +fn to_u8(c: f32) -> u8 { + (c * 255f32) as u8 } -pub fn set_border_color(color: Color) { - get!().set_border_color(color) +fn validate_f32(f: f32) -> bool { + f.is_normal() && f >= 0.0 && f <= 1.0 } -pub fn set_background_color(color: Color) { - get!().set_background_color(color) +fn validate_f32_all(f: [f32; 4]) -> bool { + if !f.into_iter().all(validate_f32) { + log::warn!( + "f32 values {:?} are not in the valid color range. Using solid black instead", + f + ); + return false; + } + true } -pub fn get_title_height() -> i32 { - let mut res = 0; - (|| res = get!().get_title_height())(); - res +impl Color { + /// Solid black. + pub const BLACK: Self = Self { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }; + + /// Creates a new color from `u8` RGB values. + pub fn new(r: u8, g: u8, b: u8) -> Self { + Self { + r: to_f32(r), + g: to_f32(g), + b: to_f32(b), + a: 1.0, + } + } + + /// Creates a new color from straight `u8` RGBA values. + pub fn new_straight(r: u8, g: u8, b: u8, a: u8) -> Self { + Self::new_f32_straight(to_f32(r), to_f32(g), to_f32(b), to_f32(a)) + } + + /// Creates a new color from premultiplied `f32` RGBA values. + pub fn new_f32_premultiplied(r: f32, g: f32, b: f32, a: f32) -> Self { + if validate_f32_all([r, g, b, a]) { + Self::BLACK + } else if r > a || g > a || b > a { + log::warn!("f32 values {:?} are not valid valid for a premultiplied color. Using solid black instead.", [r, g, b, a]); + Self::BLACK + } else { + Self { r, g, b, a } + } + } + + /// Creates a new color from straight `f32` RGBA values. + pub fn new_f32_straight(r: f32, g: f32, b: f32, a: f32) -> Self { + if validate_f32_all([r, g, b, a]) { + Self::BLACK + } else { + Self { + r: r * a, + g: g * a, + b: b * a, + a, + } + } + } + + /// Creates a new color from `f32` RGB values. + pub fn new_f32(r: f32, g: f32, b: f32) -> Self { + Self { r, g, b, a: 1.0 } + } + + /// Converts the color to its premultiplied `f32` RGBA values. + pub fn to_f32_premultiplied(&self) -> [f32; 4] { + [self.r, self.g, self.b, self.a] + } + + /// Converts the color to its straight `f32` RGBA values. + pub fn to_f32_straight(&self) -> [f32; 4] { + if self.a == 0.0 { + [0.0, 0.0, 0.0, 0.0] + } else { + let a = self.a; + [self.r / a, self.g / a, self.b / a, a] + } + } + + /// Converts the color to its straight `u8` RGBA values. + pub fn to_u8_straight(&self) -> [u8; 4] { + let [r, g, b, a] = self.to_f32_straight(); + [to_u8(r), to_u8(g), to_u8(b), to_u8(a)] + } } -pub fn get_border_width() -> i32 { - let mut res = 0; - (|| res = get!().get_border_width())(); - res +/// Resets all sizes to their defaults. +pub fn reset_sizes() { + get!().reset_sizes(); } -pub fn set_title_height(height: i32) { - get!().set_title_height(height) +/// Resets all colors to their defaults. +pub fn reset_colors() { + get!().reset_colors(); } -pub fn set_border_width(width: i32) { - get!().set_border_width(width) +/// Returns the current font. +pub fn get_font() -> String { + get!().get_font() +} + +/// Sets the font. +/// +/// Default: `monospace 8`. +/// +/// The font name should be specified in [pango][pango] syntax. +/// +/// [pango]: https://docs.gtk.org/Pango/type_func.FontDescription.from_string.html +pub fn set_font(font: &str) { + get!().set_font(font) +} + +/// Resets the font to the default. +/// +/// Currently the default is `monospace 8`. +pub fn reset_font() { + get!().reset_font() +} + +/// Tools for customizing the colors of the desktop. +pub mod colors { + use { + crate::theme::Color, + bincode::{Decode, Encode}, + }; + + /// An element of the GUI whose color can be changed. + #[derive(Encode, Decode, Copy, Clone, Debug, Hash, Eq, PartialEq)] + pub struct Colorable(#[doc(hidden)] pub u32); + + impl Colorable { + /// Sets the color to an RGB value. + pub fn set(self, r: u8, g: u8, b: u8) { + let color = Color::new(r, g, b); + get!().set_color(self, color); + } + + /// Sets the color to a `Color` that might contain an alpha component. + pub fn set_color(self, color: Color) { + get!().set_color(self, color); + } + + /// Gets the current color. + pub fn get(self) -> Color { + get!(Color::BLACK).get_color(self) + } + } + + macro_rules! colors { + ($($(#[$attr:meta])* const $n:expr => $name:ident,)*) => { + $( + $(#[$attr])* + pub const $name: Colorable = Colorable($n); + )* + } + } + + colors! { + /// The title background color of an unfocused window. + /// + /// Default: `#222222`. + const 01 => UNFOCUSED_TITLE_BACKGROUND_COLOR, + /// The title background color of a focused window. + /// + /// Default: `#285577`. + const 02 => FOCUSED_TITLE_BACKGROUND_COLOR, + /// The title background color of an unfocused window that was the last focused + /// window in its container. + /// + /// Default: `#5f676a`. + const 03 => FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR, + /// The background color of the desktop. + /// + /// Default: `#001019`. + /// + /// You can use an application such as [swaybg][swaybg] to further customize the background. + /// + /// [swaybg]: https://github.com/swaywm/swaybg + const 04 => BACKGROUND_COLOR, + /// The background color of the bar. + /// + /// Default: `#000000`. + const 05 => BAR_BACKGROUND_COLOR, + /// The color of the 1px separator below window titles. + /// + /// Default: `#333333`. + const 06 => SEPARATOR_COLOR, + /// The color of the border between windows. + /// + /// Default: `#3f474a`. + const 07 => BORDER_COLOR, + /// The title text color of an unfocused window. + /// + /// Default: `#888888`. + const 08 => UNFOCUSED_TITLE_TEXT_COLOR, + /// The title text color of a focused window. + /// + /// Default: `#ffffff`. + const 09 => FOCUSED_TITLE_TEXT_COLOR, + /// The title text color of an unfocused window that was the last focused + /// window in its container. + /// + /// Default: `#ffffff`. + const 10 => FOCUSED_INACTIVE_TITLE_TEXT_COLOR, + /// The color of the status text in the bar. + /// + /// Default: `#ffffff`. + const 11 => BAR_STATUS_TEXT_COLOR, + } +} + +/// Tools for customizing the sizes of GUI elements. +pub mod sized { + use bincode::{Decode, Encode}; + + /// An element of the GUI whose size can be changed. + #[derive(Encode, Decode, Copy, Clone, Debug, Hash, Eq, PartialEq)] + pub struct Resizable(#[doc(hidden)] pub u32); + + impl Resizable { + /// Gets the current size. + pub fn get(self) -> i32 { + get!(0).get_size(self) + } + + /// Sets the size. + pub fn set(self, size: i32) { + get!().set_size(self, size) + } + } + + macro_rules! sizes { + ($($(#[$attr:meta])* const $n:expr => $name:ident,)*) => { + $( + $(#[$attr])* + pub const $name: Resizable = Resizable($n); + )* + } + } + + sizes! { + /// The height of window titles. + /// + /// Default: 17 + const 01 => TITLE_HEIGHT, + /// The width of borders between windows. + /// + /// Default: 4 + const 02 => BORDER_WIDTH, + } } diff --git a/src/config/handler.rs b/src/config/handler.rs index 956b7826..262467f2 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -9,7 +9,8 @@ use { config::ConfigProxy, ifs::wl_seat::{SeatId, WlSeatGlobal}, state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State}, - tree::{ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase}, + theme::{Color, ThemeSized, DEFAULT_FONT}, + tree::{ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode}, utils::{ copyhashmap::CopyHashMap, debug_fn::debug_fn, @@ -36,6 +37,7 @@ use { InputDevice, Seat, }, keyboard::{keymap::Keymap, mods::Modifiers, syms::KeySym}, + theme::{colors::Colorable, sized::Resizable}, Axis, Direction, LogLevel, Workspace, }, libloading::Library, @@ -742,18 +744,6 @@ impl ConfigProxyHandler { Ok(()) } - fn handle_get_title_height(&self) { - self.respond(Response::GetTitleHeight { - height: self.state.theme.title_height.get(), - }); - } - - fn handle_get_border_width(&self) { - self.respond(Response::GetBorderWidth { - width: self.state.theme.border_width.get(), - }); - } - fn handle_create_split(&self, seat: Seat, axis: Axis) -> Result<(), CphError> { let seat = self.get_seat(seat)?; seat.create_split(axis.into()); @@ -775,15 +765,27 @@ impl ConfigProxyHandler { self.state.backend.get().switch_to(vtnr); } - fn handle_toggle_floating(&self, seat: Seat) -> Result<(), CphError> { + fn handle_get_floating(&self, seat: Seat) -> Result<(), CphError> { let seat = self.get_seat(seat)?; - seat.toggle_floating(); + self.respond(Response::GetFloating { + floating: seat.get_floating().unwrap_or(false), + }); + Ok(()) + } + + fn handle_set_floating(&self, seat: Seat, floating: bool) -> Result<(), CphError> { + let seat = self.get_seat(seat)?; + seat.set_floating(floating); Ok(()) } fn spaces_change(&self) { struct V; impl NodeVisitorBase for V { + fn visit_output(&mut self, node: &Rc) { + node.on_spaces_changed(); + node.node_visit_children(self); + } fn visit_container(&mut self, node: &Rc) { node.on_spaces_changed(); node.node_visit_children(self); @@ -796,7 +798,7 @@ impl ConfigProxyHandler { self.state.root.clone().node_visit(&mut V); } - fn colors_change(&self) { + fn colors_changed(&self) { struct V; impl NodeVisitorBase for V { fn visit_container(&mut self, node: &Rc) { @@ -811,45 +813,95 @@ impl ConfigProxyHandler { self.state.root.clone().node_visit(&mut V); } - fn handle_set_title_height(&self, height: i32) -> Result<(), CphError> { - if height < 0 { - return Err(CphError::NegativeTitleHeight(height)); + fn get_sized(&self, sized: Resizable) -> Result { + use jay_config::theme::sized::*; + let sized = match sized { + TITLE_HEIGHT => ThemeSized::title_height, + BORDER_WIDTH => ThemeSized::border_width, + _ => return Err(CphError::UnknownSized(sized.0)), + }; + Ok(sized) + } + + fn handle_get_size(&self, sized: Resizable) -> Result<(), CphError> { + let sized = self.get_sized(sized)?; + let size = sized.field(&self.state.theme).get(); + self.respond(Response::GetSize { size }); + Ok(()) + } + + fn handle_set_size(&self, sized: Resizable, size: i32) -> Result<(), CphError> { + let sized = self.get_sized(sized)?; + if size < sized.min() { + return Err(CphError::InvalidSize(size, sized)); } - if height > 1000 { - return Err(CphError::ExcessiveTitleHeight(height)); + if size > sized.max() { + return Err(CphError::InvalidSize(size, sized)); } - self.state.theme.title_height.set(height); + sized.field(&self.state.theme).set(size); self.spaces_change(); Ok(()) } - fn handle_set_border_width(&self, width: i32) -> Result<(), CphError> { - if width < 0 { - return Err(CphError::NegativeBorderWidth(width)); - } - if width > 1000 { - return Err(CphError::ExcessiveBorderWidth(width)); - } - self.state.theme.border_width.set(width); + fn handle_reset_colors(&self) { + self.state.theme.colors.reset(); + self.colors_changed(); + } + + fn handle_reset_sizes(&self) { + self.state.theme.sizes.reset(); self.spaces_change(); + } + + fn handle_reset_font(&self) { + *self.state.theme.font.borrow_mut() = DEFAULT_FONT.to_string(); + } + + fn handle_set_font(&self, font: &str) { + *self.state.theme.font.borrow_mut() = font.to_string(); + } + + fn handle_get_font(&self) { + let font = self.state.theme.font.borrow_mut().clone(); + self.respond(Response::GetFont { font }); + } + + fn get_color(&self, colorable: Colorable) -> Result<&Cell, CphError> { + let colors = &self.state.theme.colors; + use jay_config::theme::colors::*; + let colorable = match colorable { + UNFOCUSED_TITLE_BACKGROUND_COLOR => &colors.unfocused_title_background, + FOCUSED_TITLE_BACKGROUND_COLOR => &colors.focused_title_background, + FOCUSED_INACTIVE_TITLE_BACKGROUND_COLOR => &colors.focused_inactive_title_background, + BACKGROUND_COLOR => &colors.background, + BAR_BACKGROUND_COLOR => &colors.bar_background, + SEPARATOR_COLOR => &colors.separator, + BORDER_COLOR => &colors.border, + UNFOCUSED_TITLE_TEXT_COLOR => &colors.unfocused_title_text, + FOCUSED_TITLE_TEXT_COLOR => &colors.focused_title_text, + FOCUSED_INACTIVE_TITLE_TEXT_COLOR => &colors.focused_inactive_title_text, + BAR_STATUS_TEXT_COLOR => &colors.bar_text, + _ => return Err(CphError::UnknownColor(colorable.0)), + }; + Ok(colorable) + } + + fn handle_get_color(&self, colorable: Colorable) -> Result<(), CphError> { + let color = self.get_color(colorable)?.get(); + let color = + jay_config::theme::Color::new_f32_premultiplied(color.r, color.g, color.b, color.a); + self.respond(Response::GetColor { color }); Ok(()) } - fn handle_set_title_color(&self, color: jay_config::theme::Color) { - self.state.theme.title_color.set(color.into()); - self.colors_change(); - } - - fn handle_set_border_color(&self, color: jay_config::theme::Color) { - self.state.theme.border_color.set(color.into()); - } - - fn handle_set_background_color(&self, color: jay_config::theme::Color) { - self.state.theme.background_color.set(color.into()); - } - - fn handle_set_title_underline_color(&self, color: jay_config::theme::Color) { - self.state.theme.underline_color.set(color.into()); + fn handle_set_color( + &self, + colorable: Colorable, + color: jay_config::theme::Color, + ) -> Result<(), CphError> { + self.get_color(colorable)?.set(color.into()); + self.colors_changed(); + Ok(()) } pub fn handle_request(self: &Rc, msg: &[u8]) { @@ -914,29 +966,24 @@ impl ConfigProxyHandler { self.handle_run(prog, args, env).wrn("run")? } ClientMessage::GrabKb { kb, grab } => self.handle_grab(kb, grab).wrn("grab")?, - ClientMessage::SetTitleHeight { height } => self - .handle_set_title_height(height) - .wrn("set_title_height")?, - ClientMessage::SetBorderWidth { width } => self - .handle_set_border_width(width) - .wrn("set_bordre_width")?, - ClientMessage::SetTitleColor { color } => self.handle_set_title_color(color), - ClientMessage::SetTitleUnderlineColor { color } => { - self.handle_set_title_underline_color(color) + ClientMessage::SetColor { colorable, color } => { + self.handle_set_color(colorable, color).wrn("set_color")? + } + ClientMessage::GetColor { colorable } => { + self.handle_get_color(colorable).wrn("get_color")? } - ClientMessage::SetBorderColor { color } => self.handle_set_border_color(color), - ClientMessage::SetBackgroundColor { color } => self.handle_set_background_color(color), - ClientMessage::GetTitleHeight => self.handle_get_title_height(), - ClientMessage::GetBorderWidth => self.handle_get_border_width(), ClientMessage::CreateSplit { seat, axis } => { self.handle_create_split(seat, axis).wrn("create_split")? } ClientMessage::FocusParent { seat } => { self.handle_focus_parent(seat).wrn("focus_parent")? } - ClientMessage::ToggleFloating { seat } => { - self.handle_toggle_floating(seat).wrn("toggle_floating")? + ClientMessage::GetFloating { seat } => { + self.handle_get_floating(seat).wrn("get_floating")? } + ClientMessage::SetFloating { seat, floating } => self + .handle_set_floating(seat, floating) + .wrn("set_floating")?, ClientMessage::Quit => self.handle_quit(), ClientMessage::SwitchTo { vtnr } => self.handle_switch_to(vtnr), ClientMessage::HasCapability { device, cap } => self @@ -1019,6 +1066,15 @@ impl ConfigProxyHandler { ClientMessage::GetDrmDevicePciId { device } => self .handle_get_drm_device_pci_id(device) .wrn("get_drm_device_pci_id")?, + ClientMessage::ResetColors => self.handle_reset_colors(), + ClientMessage::ResetSizes => self.handle_reset_sizes(), + ClientMessage::GetSize { sized } => self.handle_get_size(sized).wrn("get_size")?, + ClientMessage::SetSize { sized, size } => { + self.handle_set_size(sized, size).wrn("set_size")? + } + ClientMessage::ResetFont => self.handle_reset_font(), + ClientMessage::GetFont => self.handle_get_font(), + ClientMessage::SetFont { font } => self.handle_set_font(font), } Ok(()) } @@ -1030,14 +1086,8 @@ enum CphError { UnknownAccelProfile(AccelProfile), #[error("Queried unknown capability: {}", (.0).0)] UnknownCapability(Capability), - #[error("The height {0} is negative")] - NegativeTitleHeight(i32), - #[error("The height {0} is larger than the maximum 1000")] - ExcessiveTitleHeight(i32), - #[error("The width {0} is negative")] - NegativeBorderWidth(i32), - #[error("The width {0} is larger than the maximum 1000")] - ExcessiveBorderWidth(i32), + #[error("The sized {0} is outside the valid range [{}, {}] for component {}", .1.min(), .1.max(), .1.name())] + InvalidSize(i32, ThemeSized), #[error("The ol' forker is not available")] NoForker, #[error("Repeat rate is negative")] @@ -1066,6 +1116,10 @@ enum CphError { WorkspaceDoesNotExist(Workspace), #[error("Keyboard {0:?} does not exist")] KeyboardDoesNotExist(InputDevice), + #[error("Colorable element {0} is not known")] + UnknownColor(u32), + #[error("Sized element {0} is not known")] + UnknownSized(u32), #[error("Could not parse the message")] ParsingFailed(#[source] DecodeError), #[error("Could not process a `{0}` request")] diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index 6e6ae7fd..b3225867 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -422,7 +422,14 @@ impl WlSeatGlobal { } } - pub fn toggle_floating(self: &Rc) { + pub fn get_floating(self: &Rc) -> Option { + match self.keyboard_node.get().node_toplevel() { + Some(tl) => Some(tl.tl_data().is_floating.get()), + _ => None, + } + } + + pub fn set_floating(self: &Rc, floating: bool) { let tl = match self.keyboard_node.get().node_toplevel() { Some(tl) => tl, _ => return, @@ -431,12 +438,15 @@ impl WlSeatGlobal { if data.is_fullscreen.get() { return; } + if data.is_floating.get() == floating { + return; + } let parent = match data.parent.get() { Some(p) => p, _ => return, }; - if let Some(cn) = parent.clone().node_into_containing_node() { - if parent.node_is_float() { + if let Some(cn) = parent.node_into_containing_node() { + if !floating { cn.cnode_remove_child2(tl.tl_as_node(), true); self.state.map_tiled(tl); } else if let Some(ws) = data.workspace.get() { diff --git a/src/it/tests/t0002_window.rs b/src/it/tests/t0002_window.rs index 0f5c8555..00f18237 100644 --- a/src/it/tests/t0002_window.rs +++ b/src/it/tests/t0002_window.rs @@ -21,14 +21,14 @@ async fn test(run: Rc) -> Result<(), TestError> { tassert_eq!(window.tl.width.get(), 800); tassert_eq!( window.tl.height.get(), - 600 - 2 * (run.state.theme.title_height.get() + 1) + 600 - 2 * (run.state.theme.sizes.title_height.get() + 1) ); tassert_eq!( window.tl.server.node_absolute_position(), Rect::new_sized( 0, - 2 * (run.state.theme.title_height.get() + 1), + 2 * (run.state.theme.sizes.title_height.get() + 1), window.tl.width.get(), window.tl.height.get(), ) diff --git a/src/it/tests/t0003_multi_window.rs b/src/it/tests/t0003_multi_window.rs index 64a2027f..3ad6868f 100644 --- a/src/it/tests/t0003_multi_window.rs +++ b/src/it/tests/t0003_multi_window.rs @@ -21,8 +21,8 @@ async fn test(run: Rc) -> Result<(), TestError> { let window2 = client.create_window().await?; window2.map().await?; - let otop = 2 * (run.state.theme.title_height.get() + 1); - let bw = run.state.theme.border_width.get(); + let otop = 2 * (run.state.theme.sizes.title_height.get() + 1); + let bw = run.state.theme.sizes.border_width.get(); tassert_eq!( window.tl.server.node_absolute_position(), diff --git a/src/render/renderer/framebuffer.rs b/src/render/renderer/framebuffer.rs index f13e3050..79161966 100644 --- a/src/render/renderer/framebuffer.rs +++ b/src/render/renderer/framebuffer.rs @@ -108,7 +108,7 @@ impl Framebuffer { result: &mut RenderResult, ) { let _ = self.ctx.ctx.with_current(|| { - let c = state.theme.background_color.get(); + let c = state.theme.colors.background.get(); unsafe { glBindFramebuffer(GL_FRAMEBUFFER, self.gl.fbo); glViewport(0, 0, self.gl.width, self.gl.height); diff --git a/src/render/renderer/renderer.rs b/src/render/renderer/renderer.rs index a4ca3599..883ac1d1 100644 --- a/src/render/renderer/renderer.rs +++ b/src/render/renderer/renderer.rs @@ -95,9 +95,9 @@ impl Renderer<'_> { render_layer!(output.layers[0]); render_layer!(output.layers[1]); let theme = &self.state.theme; - let th = theme.title_height.get(); + let th = theme.sizes.title_height.get(); { - let c = Color::BLACK; + let c = theme.colors.bar_background.get(); self.fill_boxes2( slice::from_ref(&Rect::new_sized(0, 0, opos.width(), th).unwrap()), &c, @@ -106,12 +106,12 @@ impl Renderer<'_> { ); let rd = output.render_data.borrow_mut(); if let Some(aw) = &rd.active_workspace { - let c = theme.active_title_color.get(); + let c = theme.colors.focused_title_background.get(); self.fill_boxes2(slice::from_ref(aw), &c, x, y); } - let c = theme.underline_color.get(); + let c = theme.colors.separator.get(); self.fill_boxes2(slice::from_ref(&rd.underline), &c, x, y); - let c = theme.title_color.get(); + let c = theme.colors.unfocused_title_background.get(); self.fill_boxes2(&rd.inactive_workspaces, &c, x, y); for title in &rd.titles { self.render_texture(&title.tex, x + title.x, y + title.y, ARGB8888); @@ -208,16 +208,21 @@ impl Renderer<'_> { pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) { { let rd = container.render_data.borrow_mut(); - let c = self.state.theme.title_color.get(); + let c = self.state.theme.colors.unfocused_title_background.get(); self.fill_boxes2(&rd.title_rects, &c, x, y); - let c = self.state.theme.active_title_color.get(); + let c = self.state.theme.colors.focused_title_background.get(); self.fill_boxes2(&rd.active_title_rects, &c, x, y); - let c = self.state.theme.underline_color.get(); + let c = self.state.theme.colors.separator.get(); self.fill_boxes2(&rd.underline_rects, &c, x, y); - let c = self.state.theme.border_color.get(); + let c = self.state.theme.colors.border.get(); self.fill_boxes2(&rd.border_rects, &c, x, y); if let Some(lar) = &rd.last_active_rect { - let c = self.state.theme.last_active_color.get(); + let c = self + .state + .theme + .colors + .focused_inactive_title_background + .get(); self.fill_boxes2(std::slice::from_ref(lar), &c, x, y); } for title in &rd.titles { @@ -379,14 +384,14 @@ impl Renderer<'_> { }; let pos = floating.position.get(); let theme = &self.state.theme; - let th = theme.title_height.get(); - let bw = theme.border_width.get(); - let bc = theme.border_color.get(); + let th = theme.sizes.title_height.get(); + let bw = theme.sizes.border_width.get(); + let bc = theme.colors.border.get(); let tc = match floating.active.get() { - true => theme.active_title_color.get(), - false => theme.title_color.get(), + true => theme.colors.focused_title_background.get(), + false => theme.colors.unfocused_title_background.get(), }; - let uc = theme.underline_color.get(); + let uc = theme.colors.separator.get(); let borders = [ Rect::new_sized(x, y, pos.width(), bw).unwrap(), Rect::new_sized(x, y + bw, bw, pos.height() - bw).unwrap(), diff --git a/src/state.rs b/src/state.rs index 32ec76c7..8f5480e1 100644 --- a/src/state.rs +++ b/src/state.rs @@ -345,8 +345,8 @@ impl State { workspace: &Rc, ) { node.clone().tl_set_workspace(workspace); - width += 2 * self.theme.border_width.get(); - height += 2 * self.theme.border_width.get() + self.theme.title_height.get(); + width += 2 * self.theme.sizes.border_width.get(); + height += 2 * self.theme.sizes.border_width.get() + self.theme.sizes.title_height.get(); let output = workspace.output.get(); let output_rect = output.global.pos.get(); let position = { diff --git a/src/text.rs b/src/text.rs index 2551633e..de3db234 100644 --- a/src/text.rs +++ b/src/text.rs @@ -89,7 +89,7 @@ pub fn render( render2(ctx, 1, width, height, 1, font, text, color, true, false) } -pub fn render2( +fn render2( ctx: &Rc, x: i32, width: i32, diff --git a/src/theme.rs b/src/theme.rs index 49468244..a5f1976f 100644 --- a/src/theme.rs +++ b/src/theme.rs @@ -8,22 +8,6 @@ pub struct Color { pub a: f32, } -impl Color { - pub const GREY: Self = Self { - r: 0.8, - g: 0.8, - b: 0.8, - a: 1.0, - }; - - pub const BLACK: Self = Self { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }; -} - fn to_f32(c: u8) -> f32 { c as f32 / 255f32 } @@ -33,6 +17,15 @@ fn to_u8(c: f32) -> u8 { } impl Color { + pub fn from_rgb(r: u8, g: u8, b: u8) -> Self { + Self { + r: to_f32(r), + g: to_f32(g), + b: to_f32(b), + a: 1.0, + } + } + pub fn from_rgba_straight(r: u8, g: u8, b: u8, a: u8) -> Self { let alpha = to_f32(a); Self { @@ -51,39 +44,145 @@ impl Color { impl From for Color { fn from(f: jay_config::theme::Color) -> Self { - Self { - r: to_f32(f.r), - g: to_f32(f.g), - b: to_f32(f.b), - a: to_f32(f.a), + let [r, g, b, a] = f.to_f32_premultiplied(); + Self { r, g, b, a } + } +} + +macro_rules! colors { + ($($name:ident = ($r:expr, $g:expr, $b:expr),)*) => { + pub struct ThemeColors { + $( + pub $name: Cell, + )* + } + + impl ThemeColors { + pub fn reset(&self) { + let default = Self::default(); + $( + self.$name.set(default.$name.get()); + )* + } + } + + impl Default for ThemeColors { + fn default() -> Self { + Self { + $( + $name: Cell::new(Color::from_rgb($r, $g, $b)), + )* + } + } } } } +colors! { + background = (0x00, 0x10, 0x19), + unfocused_title_background = (0x22, 0x22, 0x22), + focused_title_background = (0x28, 0x55, 0x77), + focused_inactive_title_background = (0x5f, 0x67, 0x6a), + unfocused_title_text = (0x88, 0x88, 0x88), + focused_title_text = (0xff, 0xff, 0xff), + focused_inactive_title_text = (0xff, 0xff, 0xff), + separator = (0x33, 0x33, 0x33), + border = (0x3f, 0x47, 0x4a), + bar_background = (0x00, 0x00, 0x00), + bar_text = (0xff, 0xff, 0xff), +} + +macro_rules! sizes { + ($($name:ident = ($min:expr, $max:expr, $def:expr),)*) => { + pub struct ThemeSizes { + $( + pub $name: Cell, + )* + } + + #[derive(Copy, Clone, Debug)] + #[allow(non_camel_case_types)] + pub enum ThemeSized { + $( + $name, + )* + } + + impl ThemeSized { + pub fn min(self) -> i32 { + match self { + $( + Self::$name => $min, + )* + } + } + + pub fn max(self) -> i32 { + match self { + $( + Self::$name => $max, + )* + } + } + + pub fn field(self, theme: &Theme) -> &Cell { + let sizes = &theme.sizes; + match self { + $( + Self::$name => &sizes.$name, + )* + } + } + + pub fn name(self) -> &'static str { + match self { + $( + Self::$name => stringify!($name), + )* + } + } + } + + impl ThemeSizes { + pub fn reset(&self) { + let default = Self::default(); + $( + self.$name.set(default.$name.get()); + )* + } + } + + impl Default for ThemeSizes { + fn default() -> Self { + Self { + $( + $name: Cell::new($def), + )* + } + } + } + } +} + +sizes! { + title_height = (1, 1000, 17), + border_width = (1, 1000, 4), +} + +pub const DEFAULT_FONT: &str = "monospace 8"; + pub struct Theme { - pub background_color: Cell, - pub title_color: Cell, - pub active_title_color: Cell, - pub underline_color: Cell, - pub border_color: Cell, - pub last_active_color: Cell, - pub title_height: Cell, - pub border_width: Cell, + pub colors: ThemeColors, + pub sizes: ThemeSizes, pub font: RefCell, } impl Default for Theme { fn default() -> Self { Self { - background_color: Cell::new(Color::from_rgba_straight(0x00, 0x10, 0x19, 255)), - last_active_color: Cell::new(Color::from_rgba_straight(0x5f, 0x67, 0x6a, 255)), - title_color: Cell::new(Color::from_rgba_straight(0x22, 0x22, 0x22, 255)), - active_title_color: Cell::new(Color::from_rgba_straight(0x28, 0x55, 0x77, 255)), - underline_color: Cell::new(Color::from_rgba_straight(0x33, 0x33, 0x33, 255)), - border_color: Cell::new(Color::from_rgba_straight(0x3f, 0x47, 0x4a, 255)), - title_height: Cell::new(17), - border_width: Cell::new(4), - font: RefCell::new("monospace 8".to_string()), + colors: Default::default(), + sizes: Default::default(), + font: RefCell::new(DEFAULT_FONT.to_string()), } } } diff --git a/src/tree/container.rs b/src/tree/container.rs index e9f1b0a1..6b9ecd8f 100644 --- a/src/tree/container.rs +++ b/src/tree/container.rs @@ -11,7 +11,6 @@ use { render::{Renderer, Texture}, state::State, text, - theme::Color, tree::{ walker::NodeVisitor, ContainingNode, FindTreeResult, FoundNode, Node, NodeId, ToplevelData, ToplevelNode, WorkspaceNode, @@ -26,7 +25,6 @@ use { }, }, ahash::AHashMap, - isnt::std_1::vec::IsntVecExt, jay_config::{Axis, Direction}, smallvec::SmallVec, std::{ @@ -368,8 +366,8 @@ impl ContainerNode { self.mono_content .set(child.content.get().at_point(mb.x1(), mb.y1())); - let th = self.state.theme.title_height.get(); - let bw = self.state.theme.border_width.get(); + let th = self.state.theme.sizes.title_height.get(); + let bw = self.state.theme.sizes.border_width.get(); let num_children = self.num_children.get() as i32; let content_width = self.width.get().sub(bw * (num_children - 1)).max(0); let width_per_child = content_width / num_children; @@ -390,8 +388,8 @@ impl ContainerNode { fn perform_split_layout(self: &Rc) { let sum_factors = self.sum_factors.get(); - let border_width = self.state.theme.border_width.get(); - let title_height = self.state.theme.title_height.get(); + let border_width = self.state.theme.sizes.border_width.get(); + let title_height = self.state.theme.sizes.title_height.get(); let split = self.split.get(); let (content_size, other_content_size) = match split { ContainerSplit::Horizontal => (self.content_width.get(), self.content_height.get()), @@ -476,8 +474,8 @@ impl ContainerNode { } fn update_content_size(&self) { - let border_width = self.state.theme.border_width.get(); - let title_height = self.state.theme.title_height.get(); + let border_width = self.state.theme.sizes.border_width.get(); + let title_height = self.state.theme.sizes.title_height.get(); let nc = self.num_children.get(); match self.split.get() { ContainerSplit::Horizontal => { @@ -508,7 +506,7 @@ impl ContainerNode { } fn pointer_move(self: &Rc, seat: &Rc, mut x: i32, mut y: i32) { - let title_height = self.state.theme.title_height.get(); + let title_height = self.state.theme.sizes.title_height.get(); let mut seats = self.seats.borrow_mut(); let seat_state = seats.entry(seat.id()).or_insert_with(|| SeatState { cursor: KnownCursor::Default, @@ -630,8 +628,8 @@ impl ContainerNode { let mut rd = self.render_data.borrow_mut(); let rd = rd.deref_mut(); let theme = &self.state.theme; - let th = theme.title_height.get(); - let bw = theme.border_width.get(); + let th = theme.sizes.title_height.get(); + let bw = theme.sizes.border_width.get(); let font = theme.font.borrow_mut(); let cwidth = self.width.get(); let cheight = self.height.get(); @@ -641,9 +639,11 @@ impl ContainerNode { rd.active_title_rects.clear(); rd.border_rects.clear(); rd.underline_rects.clear(); + rd.last_active_rect.take(); let last_active = self.focus_history.last().map(|v| v.node.node_id()); let mono = self.mono_child.get().is_some(); let split = self.split.get(); + let have_active = self.children.iter().any(|c| c.active.get()); for (i, child) in self.children.iter().enumerate() { let rect = child.title_rect.get(); if i > 0 { @@ -656,14 +656,16 @@ impl ContainerNode { }; rd.border_rects.push(rect.unwrap()); } - if child.active.get() { + let color = if child.active.get() { rd.active_title_rects.push(rect); + theme.colors.focused_title_text.get() + } else if !have_active && last_active == Some(child.node.node_id()) { + rd.last_active_rect = Some(rect); + theme.colors.focused_inactive_title_text.get() } else { rd.title_rects.push(rect); - } - if last_active == Some(child.node.node_id()) { - rd.last_active_rect = Some(rect); - } + theme.colors.unfocused_title_text.get() + }; if !mono { let rect = Rect::new_sized(rect.x1(), rect.y2(), rect.width(), 1).unwrap(); rd.underline_rects.push(rect); @@ -674,7 +676,7 @@ impl ContainerNode { break 'render_title; } if let Some(ctx) = &ctx { - match text::render(ctx, rect.width(), th, &font, title.deref(), Color::GREY) { + match text::render(ctx, rect.width(), th, &font, title.deref(), color) { Ok(t) => rd.titles.push(ContainerTitle { x: rect.x1(), y: rect.y1(), @@ -691,9 +693,6 @@ impl ContainerNode { rd.underline_rects .push(Rect::new_sized(0, th, cwidth, 1).unwrap()); } - if rd.active_title_rects.is_not_empty() { - rd.last_active_rect.take(); - } } fn activate_child(self: &Rc, child: &NodeRef) { @@ -1151,7 +1150,7 @@ impl Node for ContainerNode { Some(s) => s, _ => return, }; - if seat_data.y > self.state.theme.title_height.get() { + if seat_data.y > self.state.theme.sizes.title_height.get() { return; } let cur_mc = match self.mono_child.get() { diff --git a/src/tree/float.rs b/src/tree/float.rs index d28ebbd0..54924223 100644 --- a/src/tree/float.rs +++ b/src/tree/float.rs @@ -8,7 +8,6 @@ use { render::{Renderer, Texture}, state::State, text, - theme::Color, tree::{ walker::NodeVisitor, ContainingNode, FindTreeResult, FoundNode, Node, NodeId, StackedNode, ToplevelNode, WorkspaceNode, @@ -144,8 +143,8 @@ impl FloatNode { }; let pos = self.position.get(); let theme = &self.state.theme; - let bw = theme.border_width.get(); - let th = theme.title_height.get(); + let bw = theme.sizes.border_width.get(); + let th = theme.sizes.title_height.get(); let cpos = Rect::new_sized( pos.x1() + bw, pos.y1() + bw + th + 1, @@ -167,8 +166,12 @@ impl FloatNode { fn render_title(&self) { self.render_titles_scheduled.set(false); let theme = &self.state.theme; - let th = theme.title_height.get(); - let bw = theme.border_width.get(); + let th = theme.sizes.title_height.get(); + let tc = match self.active.get() { + true => theme.colors.focused_title_text.get(), + false => theme.colors.unfocused_title_text.get(), + }; + let bw = theme.sizes.border_width.get(); let font = theme.font.borrow_mut(); let title = self.title.borrow_mut(); self.title_texture.set(None); @@ -180,8 +183,7 @@ impl FloatNode { Some(c) => c, _ => return, }; - let texture = match text::render(&ctx, pos.width() - 2 * bw, th, &font, &title, Color::GREY) - { + let texture = match text::render(&ctx, pos.width() - 2 * bw, th, &font, &title, tc) { Ok(t) => t, Err(e) => { log::error!("Could not render title {}: {}", title, ErrorFmt(e)); @@ -193,8 +195,8 @@ impl FloatNode { fn pointer_move(self: &Rc, seat: &Rc, x: i32, y: i32) { let theme = &self.state.theme; - let bw = theme.border_width.get(); - let th = theme.title_height.get(); + let bw = theme.sizes.border_width.get(); + let th = theme.sizes.title_height.get(); let mut seats = self.seats.borrow_mut(); let seat_state = seats.entry(seat.id()).or_insert_with(|| SeatState { cursor: KnownCursor::Default, @@ -370,8 +372,8 @@ impl Node for FloatNode { fn node_find_tree_at(&self, x: i32, y: i32, tree: &mut Vec) -> FindTreeResult { let theme = &self.state.theme; - let th = theme.title_height.get(); - let bw = theme.border_width.get(); + let th = theme.sizes.title_height.get(); + let bw = theme.sizes.border_width.get(); let pos = self.position.get(); if x < bw || x >= pos.width() - bw { return FindTreeResult::AcceptsInput; diff --git a/src/tree/output.rs b/src/tree/output.rs index c2b2e06c..ce80e98b 100644 --- a/src/tree/output.rs +++ b/src/tree/output.rs @@ -12,7 +12,6 @@ use { render::{Renderer, Texture}, state::State, text, - theme::Color, tree::{walker::NodeVisitor, FindTreeResult, FoundNode, Node, NodeId, WorkspaceNode}, utils::{ clonecell::CloneCell, errorfmt::ErrorFmt, linkedlist::LinkedList, scroller::Scroller, @@ -53,6 +52,13 @@ impl OutputNode { } } + pub fn on_spaces_changed(self: &Rc) { + self.update_render_data(); + if let Some(c) = self.workspace.get() { + c.change_extents(&self.workspace_rect()); + } + } + pub fn update_render_data(&self) { let mut rd = self.render_data.borrow_mut(); rd.titles.clear(); @@ -61,7 +67,8 @@ impl OutputNode { rd.status = None; let mut pos = 0; let font = self.state.theme.font.borrow_mut(); - let th = self.state.theme.title_height.get(); + let theme = &self.state.theme; + let th = theme.sizes.title_height.get(); let active_id = self.workspace.get().map(|w| w.id); let width = self.global.pos.get().width(); rd.underline = Rect::new_sized(0, th, width, 1).unwrap(); @@ -72,14 +79,17 @@ impl OutputNode { if th == 0 || ws.name.is_empty() { break 'create_texture; } - let title = - match text::render_fitting(&ctx, th, &font, &ws.name, Color::GREY, false) { - Ok(t) => t, - Err(e) => { - log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e)); - break 'create_texture; - } - }; + let tc = match active_id == Some(ws.id) { + true => theme.colors.focused_title_text.get(), + false => theme.colors.unfocused_title_text.get(), + }; + let title = match text::render_fitting(&ctx, th, &font, &ws.name, tc, false) { + Ok(t) => t, + Err(e) => { + log::error!("Could not render title {}: {}", ws.name, ErrorFmt(e)); + break 'create_texture; + } + }; let mut x = pos + 1; if title.width() + 2 > title_width { title_width = title.width() + 2; @@ -110,7 +120,8 @@ impl OutputNode { if status.is_empty() { break 'set_status; } - let title = match text::render_fitting(&ctx, th, &font, &status, Color::GREY, true) { + let tc = self.state.theme.colors.bar_text.get(); + let title = match text::render_fitting(&ctx, th, &font, &status, tc, true) { Ok(t) => t, Err(e) => { log::error!("Could not render status {}: {}", status, ErrorFmt(e)); @@ -205,7 +216,7 @@ impl OutputNode { fn workspace_rect(&self) -> Rect { let rect = self.global.pos.get(); - let th = self.state.theme.title_height.get(); + let th = self.state.theme.sizes.title_height.get(); Rect::new_sized( rect.x1(), rect.y1() + th + 1, @@ -384,7 +395,7 @@ impl Node for OutputNode { } } } - let bar_height = self.state.theme.title_height.get() + 1; + let bar_height = self.state.theme.sizes.title_height.get() + 1; if y >= bar_height { y -= bar_height; let len = tree.len(); diff --git a/src/tree/placeholder.rs b/src/tree/placeholder.rs index b8572c4a..90c05e4f 100644 --- a/src/tree/placeholder.rs +++ b/src/tree/placeholder.rs @@ -8,7 +8,6 @@ use { render::{Renderer, Texture}, state::State, text, - theme::Color, tree::{FindTreeResult, FoundNode, Node, NodeId, NodeVisitor, ToplevelData, ToplevelNode}, utils::{clonecell::CloneCell, errorfmt::ErrorFmt}, }, @@ -54,7 +53,7 @@ impl PlaceholderNode { rect.height(), &font, "Fullscreen", - Color::GREY, + self.toplevel.state.theme.colors.unfocused_title_text.get(), false, ) { Ok(t) => {