From 6480f84e8ee9839fb486916d708712daf34a0aff Mon Sep 17 00:00:00 2001 From: Irene Knapp Date: Thu, 12 Nov 2026 00:18:39 -0700 Subject: rounded corners fun that was some nontrivial type magick Force-Push: yes Change-Id: I9b2848cc76f3dfab5b87d7e6b2f28bf2a136fca5 --- src/geometry.rs | 100 +++++++++++++++++++++++++++++++++++++++ src/main.rs | 137 ++++++++++++++++++++++++++++++++++++++---------------- src/x.rs | 2 + src/x/geometry.rs | 66 ++++++++++++++++++++++++++ 4 files changed, 264 insertions(+), 41 deletions(-) create mode 100644 src/geometry.rs create mode 100644 src/x/geometry.rs diff --git a/src/geometry.rs b/src/geometry.rs new file mode 100644 index 0000000..3d25b5d --- /dev/null +++ b/src/geometry.rs @@ -0,0 +1,100 @@ +use std::ops::Sub; + + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Side { Top, Bottom, Left, Right } + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Corner { TopLeft, TopRight, BottomRight, BottomLeft } + +impl Side { + pub fn opposite(&self) -> Side { + match self { + Side::Top => Side::Bottom, + Side::Right => Side::Left, + Side::Bottom => Side::Top, + Side::Left => Side::Right, + } + } + + pub fn corners_on_side(&self) -> &[Corner] { + match self { + Side::Top => &[ Corner::TopLeft, Corner::TopRight ], + Side::Right => &[ Corner::TopRight, Corner::BottomRight ], + Side::Bottom => &[ Corner::BottomRight, Corner::BottomLeft ], + Side::Left => &[ Corner::BottomLeft, Corner::TopLeft ], + } + } +} + +impl Corner { + pub fn first_clockwise_side(&self) -> Side { + match self { + Corner::TopLeft => Side::Left, + Corner::TopRight => Side::Top, + Corner::BottomRight => Side::Right, + Corner::BottomLeft => Side::Bottom, + } + } +} + + +pub trait Coordinate: Copy + From + Ord + Sub { } +impl + Ord + Sub> Coordinate for T { } + + +#[derive(Debug)] +pub struct Point { + pub x: T, + pub y: T, +} + +#[derive(Debug)] +pub struct Rectangle { + pub left: T, + pub top: T, + pub width: T, + pub height: T, +} + +impl Rectangle { + pub fn corner_point(&self, corner: Corner) -> Point { + match corner { + Corner::TopLeft => Point { x: T::from(0), y: T::from(0) }, + Corner::TopRight => Point { x: self.width, y: T::from(0) }, + Corner::BottomRight => Point { x: self.width, y: self.height }, + Corner::BottomLeft => Point { x: T::from(0), y: self.height }, + } + } + + pub fn inset_from_corner_point(&self, corner: Corner, distance: T) + -> Point + { + match corner { + Corner::TopLeft => Point { x: distance, y: distance }, + Corner::TopRight => Point { x: self.width - distance, y: distance }, + Corner::BottomRight => Point { + x: self.width - distance, + y: self.height - distance + }, + Corner::BottomLeft => Point { x: distance, y: self.height - distance }, + } + } + + pub fn corner_square(&self, corner: Corner, size: T) -> Rectangle { + let corner_point = self.corner_point(corner); + let inset_point = self.inset_from_corner_point(corner, size); + + let left = corner_point.x.min(inset_point.x); + let top = corner_point.y.min(inset_point.y); + let right = corner_point.x.max(inset_point.x); + let bottom = corner_point.y.max(inset_point.y); + + Rectangle { + left, top, + width: right - left, + height: bottom - top, + } + } +} + diff --git a/src/main.rs b/src/main.rs index 5ad82a4..5b2f853 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,31 @@ +use crate::geometry::*; use crate::x::*; +use std::ops::Sub; use std::sync::Arc; use xcb::{ Xid }; use xcb::x::{ ClientMessageData, + Gc, ChangeGc, Cw, CreateWindow, Drawable, EventMask, - PolyFillRectangle, + PolyFillArc, PolyFillRectangle, Screen, WindowClass }; +mod geometry; mod x; -#[derive(Clone, Copy, PartialEq, Eq)] -enum ScreenSide { Top, Bottom, Left, Right } - struct DockConfiguration { thickness: u16, - side: ScreenSide, + side: Side, background_pixel: u32, + use_rounded_inside_corners: bool, + use_rounded_outside_corners: bool, } struct Dock { @@ -36,16 +39,6 @@ impl HasWindow for Dock { } -#[derive(Debug)] -struct Rectangle { - left: T, - top: T, - width: T, - height: T, -} - - - fn main() -> xcb::Result<()> { let display = Arc::new(Display::new()?); @@ -54,14 +47,16 @@ fn main() -> xcb::Result<()> { let dock = Dock::new(Arc::clone(&display), DockConfiguration { thickness: 50, - side: ScreenSide::Bottom, + side: Side::Bottom, background_pixel, + use_rounded_inside_corners: false, + use_rounded_outside_corners: true, })?; loop { match display.wait_for_event()? { xcb::Event::X(xcb::x::Event::Expose(_event)) => { - let _ = dock.redraw(); // TODO + let _ = dock.redraw(); // TODO } xcb::Event::X(xcb::x::Event::ButtonPress(event)) => { @@ -132,10 +127,10 @@ impl Dock { let thickness = self.configuration.thickness as u32; let strut_values = match self.configuration.side { - ScreenSide::Left => &[thickness, 0 as u32, 0 as u32, 0 as u32], - ScreenSide::Right => &[0 as u32, thickness, 0 as u32, 0 as u32], - ScreenSide::Top => &[0 as u32, 0 as u32, thickness, 0 as u32], - ScreenSide::Bottom => &[0 as u32, 0 as u32, 0 as u32, thickness], + Side::Left => &[thickness, 0 as u32, 0 as u32, 0 as u32], + Side::Right => &[0 as u32, thickness, 0 as u32, 0 as u32], + Side::Top => &[0 as u32, 0 as u32, thickness, 0 as u32], + Side::Bottom => &[0 as u32, 0 as u32, 0 as u32, thickness], }; self.set_cardinal_property(&atoms.wm_strut, strut_values)?; @@ -146,22 +141,22 @@ impl Dock { strut_values[3], 0 as u32, - if self.configuration.side == ScreenSide::Left { + if self.configuration.side == Side::Left { screen.height_in_pixels() as u32 } else { 0 as u32 }, 0 as u32, - if self.configuration.side == ScreenSide::Right { + if self.configuration.side == Side::Right { screen.height_in_pixels() as u32 } else { 0 as u32 }, 0 as u32, - if self.configuration.side == ScreenSide::Top { + if self.configuration.side == Side::Top { screen.width_in_pixels() as u32 } else { 0 as u32 }, 0 as u32, - if self.configuration.side == ScreenSide::Bottom { + if self.configuration.side == Side::Bottom { screen.width_in_pixels() as u32 } else { 0 as u32 }, ]; @@ -176,14 +171,14 @@ impl Dock { fn compute_bounds(screen: &Screen, configuration: &DockConfiguration) -> Rectangle { - if configuration.side == ScreenSide::Top - || configuration.side == ScreenSide::Bottom + if configuration.side == Side::Top + || configuration.side == Side::Bottom { let left = 0; let width = screen.width_in_pixels(); let height = configuration.thickness; - let top = if configuration.side == ScreenSide::Bottom { + let top = if configuration.side == Side::Bottom { screen.height_in_pixels() - height } else { 0 }; @@ -193,7 +188,7 @@ impl Dock { let height = screen.height_in_pixels(); let width = configuration.thickness; - let left = if configuration.side == ScreenSide::Right { + let left = if configuration.side == Side::Right { screen.width_in_pixels() - width } else { 0 }; @@ -203,25 +198,85 @@ impl Dock { fn redraw(&self) -> xcb::Result<()> { - println!("redraw"); - let drawable = Drawable::Window(self.window_id()); let context = GraphicContext::new( - Arc::clone(self.display()), drawable)?; + Arc::clone(self.display()), drawable)?; + + if self.configuration.use_rounded_inside_corners + || self.configuration.use_rounded_outside_corners + { + let black_pixel = self.screen().black_pixel(); + + let mut corner_squares = Vec::new(); + let mut corner_arcs = Vec::new(); + if self.configuration.use_rounded_outside_corners { + for corner in self.configuration.side.corners_on_side() { + let (corner_square, corner_arc) = self.compute_corner_regions(*corner); + + corner_squares.push(corner_square); + corner_arcs.push(corner_arc); + } + } + if self.configuration.use_rounded_inside_corners { + for corner in self.configuration.side.opposite().corners_on_side() { + let (corner_square, corner_arc) = self.compute_corner_regions(*corner); - let black = self.screen().black_pixel(); + corner_squares.push(corner_square); + corner_arcs.push(corner_arc); + } + } - let cookie = self.send_request_checked(&PolyFillRectangle { - drawable: drawable, - gc: context.graphic_context_id(), - rectangles: &[xcb::x::Rectangle { - x: 0, y: 0, width: 25, height: 25 - }], - }); - self.check_request(cookie)?; + let cookie = self.send_request_checked(&ChangeGc { + gc: context.graphic_context_id(), + value_list: &[ + Gc::Foreground(black_pixel), + ], + }); + self.check_request(cookie)?; + + let cookie = self.send_request_checked(&PolyFillRectangle { + drawable: drawable, + gc: context.graphic_context_id(), + rectangles: &corner_squares, + }); + self.check_request(cookie)?; + + let cookie = self.send_request_checked(&ChangeGc { + gc: context.graphic_context_id(), + value_list: &[ + Gc::Foreground(self.configuration.background_pixel), + ], + }); + self.check_request(cookie)?; + + let cookie = self.send_request_checked(&PolyFillArc { + drawable: drawable, + gc: context.graphic_context_id(), + arcs: &corner_arcs, + }); + self.check_request(cookie)?; + } Ok(()) } + + fn compute_corner_regions(&self, corner: Corner) + -> (xcb::x::Rectangle, xcb::x::Arc) + { + let radius = self.configuration.thickness / 2; + let diameter = self.configuration.thickness; + + let bounds = &self.bounds; + + let corner_square = bounds.corner_square(corner, radius) + .to_x_rectangle(); + + let angle1 = corner.first_clockwise_side().to_x_angle(); + let corner_arc = bounds.corner_square(corner, diameter) + .to_x_arc(angle1, X_CLOCKWISE_RIGHT_ANGLE); + + (corner_square, corner_arc) + } } impl HasDisplay for Dock { diff --git a/src/x.rs b/src/x.rs index d4eadfc..09297d8 100644 --- a/src/x.rs +++ b/src/x.rs @@ -2,6 +2,7 @@ pub use color::DisplayColorExt; pub use context::{ GraphicContext, HasGraphicContext, GraphicContextExt }; pub use connection::{ Connection, HasConnection, ConnectionExt }; pub use display::{ Display, HasDisplay, DisplayExt }; +pub use geometry::{ X_CLOCKWISE_RIGHT_ANGLE, SideExt, RectangleExt }; pub use window::{ Window, HasWindow, WindowExt }; pub mod atoms; @@ -9,5 +10,6 @@ pub mod color; pub mod connection; pub mod context; pub mod display; +pub mod geometry; pub mod window; diff --git a/src/x/geometry.rs b/src/x/geometry.rs new file mode 100644 index 0000000..fcbb15a --- /dev/null +++ b/src/x/geometry.rs @@ -0,0 +1,66 @@ +use crate::geometry::*; + +use std::fmt::Debug; + + +pub const X_CLOCKWISE_RIGHT_ANGLE: i16 = -90 * 64; + + +pub trait SideExt { + fn to_x_angle(&self) -> i16; +} + +impl SideExt for Side { + fn to_x_angle(&self) -> i16 { + match self { + Side::Top => 90 * 64, + Side::Right => 0, + Side::Bottom => 270 * 64, + Side::Left => 180 * 64, + } + } +} + + +pub trait RectangleExt { + fn to_x_rectangle(&self) -> xcb::x::Rectangle + where T: TryInto + TryInto + Debug, + >::Error: Debug, + >::Error: Debug; + fn to_x_arc(&self, angle1: i16, angle2: i16) -> xcb::x::Arc + where T: TryInto + TryInto + Debug, + >::Error: Debug, + >::Error: Debug; +} + + +impl RectangleExt for Rectangle { + fn to_x_rectangle(&self) -> xcb::x::Rectangle + where T: TryInto + TryInto + Debug, + >::Error: Debug, + >::Error: Debug + { + xcb::x::Rectangle { + x: self.left.try_into().unwrap(), + y: self.top.try_into().unwrap(), + width: self.width.try_into().unwrap(), + height: self.height.try_into().unwrap() + } + } + + fn to_x_arc(&self, angle1: i16, angle2: i16) -> xcb::x::Arc + where T: TryInto + TryInto + Debug, + >::Error: Debug, + >::Error: Debug + { + xcb::x::Arc { + x: self.left.try_into().unwrap(), + y: self.top.try_into().unwrap(), + width: self.width.try_into().unwrap(), + height: self.height.try_into().unwrap(), + angle1: angle1.try_into().unwrap(), + angle2: angle2.try_into().unwrap(), + } + } +} + -- cgit 1.4.1