summary refs log tree commit diff
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2026-11-12 00:18:39 -0700
committerIrene Knapp <ireneista@irenes.space>2026-11-12 00:18:39 -0700
commit6480f84e8ee9839fb486916d708712daf34a0aff (patch)
treeb53dd2b3eeecad2d52d926f8b2977051551e5bcb
parentcea4c230b40d5ea2851ed3f97f73b2554849dc34 (diff)
rounded corners
fun

that was some nontrivial type magick

Force-Push: yes
Change-Id: I9b2848cc76f3dfab5b87d7e6b2f28bf2a136fca5
-rw-r--r--src/geometry.rs100
-rw-r--r--src/main.rs137
-rw-r--r--src/x.rs2
-rw-r--r--src/x/geometry.rs66
4 files changed, 264 insertions, 41 deletions
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<u8> + Ord + Sub<Output=Self> { }
+impl<T: Copy + From<u8> + Ord + Sub<Output=T>> Coordinate for T { }
+
+
+#[derive(Debug)]
+pub struct Point<T: Coordinate> {
+  pub x: T,
+  pub y: T,
+}
+
+#[derive(Debug)]
+pub struct Rectangle<T: Coordinate> {
+  pub left: T,
+  pub top: T,
+  pub width: T,
+  pub height: T,
+}
+
+impl<T: Coordinate> Rectangle<T> {
+  pub fn corner_point(&self, corner: Corner) -> Point<T> {
+    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<T>
+  {
+    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<T> {
+    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<T> {
-  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<u16>
   {
-    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<T: Coordinate> {
+  fn to_x_rectangle(&self) -> xcb::x::Rectangle
+    where T: TryInto<i16> + TryInto<u16> + Debug,
+          <T as TryInto<i16>>::Error: Debug,
+          <T as TryInto<u16>>::Error: Debug;
+  fn to_x_arc(&self, angle1: i16, angle2: i16) -> xcb::x::Arc
+    where T: TryInto<i16> + TryInto<u16> + Debug,
+          <T as TryInto<i16>>::Error: Debug,
+          <T as TryInto<u16>>::Error: Debug;
+}
+
+
+impl<T: Coordinate> RectangleExt<T> for Rectangle<T> {
+  fn to_x_rectangle(&self) -> xcb::x::Rectangle
+    where T: TryInto<i16> + TryInto<u16> + Debug,
+          <T as TryInto<i16>>::Error: Debug,
+          <T as TryInto<u16>>::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<i16> + TryInto<u16> + Debug,
+          <T as TryInto<i16>>::Error: Debug,
+          <T as TryInto<u16>>::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(),
+    }
+  }
+}
+