atagen

monolith

public
Star0
Watch0

Commit eacaaa02eca5

tarball

atagen <boss@atagen.co>2026-10-22 05:38 UTC

4 files changed+41-46
scene: mark an extended window with its own border colour

The double ring doubled the loudest signal in the scene, which already encodes focus, and its 3b width overflowed an 8px half-gap onto neighbours. A colour costs no geometry. Focused extended windows get a separate extended-focused colour, since the extended window is usually the focused one and focus alone would hide the cue.
Modified file: crates/config/default.toml+3 additions
@@ -34,6 +34,9 @@ focused = "#88bfd1"
3434 unfocused = "#3b4252"
3535 # A window asking for attention that is not focused.
3636 urgent = "#ebcb8b"
37+# A window extended over others in its container.
38+extended = "#5e4b6e"
39+extended-focused = "#b48ead"
3740
3841 [output]
3942 # Variable refresh rate on outputs that support it, off, on, or fullscreen.
Modified file: crates/config/src/lib.rs+5 additions−1 deletions
@@ -191,7 +191,7 @@ pub enum Vrr {
191191 }
192192
193193 #[derive(Deserialize, Debug, Clone, PartialEq)]
194-#[serde(default, deny_unknown_fields)]
194+#[serde(default, deny_unknown_fields, rename_all = "kebab-case")]
195195 pub struct Decoration {
196196 /// Border width in logical pixels, drawn in the gap around each window.
197197 pub border: u32,
@@ -199,6 +199,8 @@ pub struct Decoration {
199199 pub focused: Color,
200200 pub unfocused: Color,
201201 pub urgent: Color,
202+ pub extended: Color,
203+ pub extended_focused: Color,
202204 }
203205
204206 /// Corner radii in logical pixels, top left, top right, bottom right and
@@ -234,6 +236,8 @@ impl Default for Decoration {
234236 focused: Color::rgb(0x88, 0xbf, 0xd1),
235237 unfocused: Color::rgb(0x3b, 0x42, 0x52),
236238 urgent: Color::rgb(0xeb, 0xcb, 0x8b),
239+ extended: Color::rgb(0x5e, 0x4b, 0x6e),
240+ extended_focused: Color::rgb(0xb4, 0x8e, 0xad),
237241 }
238242 }
239243 }
Modified file: crates/monolith/src/scene.rs+31 additions−43 deletions
@@ -185,32 +185,26 @@ pub fn build(
185185 clip_window(&mut out, first, clip);
186186 }
187187 if framed && deco.border > 0 {
188+ let extended = layout.grouped(id);
188189 let color = if focused == Some(id) {
189- deco.focused.0
190+ if extended {
191+ deco.extended_focused.0
192+ } else {
193+ deco.focused.0
194+ }
190195 } else if layout.window(id).is_some_and(|w| w.urgent) {
191196 deco.urgent.0
197+ } else if extended {
198+ deco.extended.0
192199 } else {
193200 deco.unfocused.0
194201 };
195202 let alpha = effect.map_or(1.0, |e| e.progress) * opacity;
196- let b = border_width(deco, scale);
197- let radius = deco.radius.0.into_iter().max().unwrap_or(0);
198- let radius = (f64::from(radius) * scale).round() as i32;
199- let rings = if layout.grouped(id) { 2 } else { 1 };
200- let Some((at, size)) = pixels(r, scale) else {
201- continue;
202- };
203- let window = Rectangle::new(at.into(), size.into());
204- for (k, borders) in state.borders.iter_mut().take(rings).enumerate() {
205- let by = 2 * b * k as i32;
206- let frame = outset_rect(window, by);
207- let reach = if radius > 0 { radius + by } else { 0 };
208- let reach = reach.min(frame.size.w / 2).min(frame.size.h / 2);
209- let first = out.len();
210- border_elements(&mut out, borders, frame, b, reach, color, alpha);
211- if let Some(clip) = clip {
212- ring_borders(&mut out, first, outset(clip, by), b);
213- }
203+ let borders = &mut state.borders;
204+ let first = out.len();
205+ border_elements(&mut out, borders, r, deco, color, alpha, scale);
206+ if let Some(clip) = clip {
207+ ring_borders(&mut out, first, clip, border_width(deco, scale));
214208 }
215209 }
216210 }
@@ -355,19 +349,24 @@ fn window_elements(
355349 );
356350 }
357351
358-/// Four strips `b` wide just outside `frame`, in the gap, so the client keeps
359-/// its size. With rounded corners they `reach` in by the largest radius, to
360-/// cover the band the ring curves through inside the frame's corners.
352+/// Four strips just outside `r`, in the gap, so the client keeps its size.
353+/// With rounded corners they reach in by the largest radius, to cover the
354+/// band the ring curves through inside the window's corners.
361355 fn border_elements(
362356 out: &mut Vec<Draw>,
363357 borders: &mut Option<[SolidColorBuffer; 4]>,
364- frame: Rectangle<i32, Physical>,
365- b: i32,
366- reach: i32,
358+ r: Rect,
359+ deco: &Decoration,
367360 color: [f32; 4],
368361 alpha: f32,
362+ scale: f64,
369363 ) {
370- let (x, y, w, h) = (frame.loc.x, frame.loc.y, frame.size.w, frame.size.h);
364+ let Some(((x, y), (w, h))) = pixels(r, scale) else {
365+ return;
366+ };
367+ let b = border_width(deco, scale);
368+ let reach = (f64::from(deco.radius.0.into_iter().max().unwrap_or(0)) * scale).round() as i32;
369+ let reach = reach.min(w / 2).min(h / 2);
371370 let strips = [
372371 ((x - b, y - b), (w + 2 * b, b + reach)),
373372 ((x - b, y + h - reach), (w + 2 * b, b + reach)),
@@ -670,31 +669,20 @@ fn clip_window(out: &mut [Draw], first: usize, clip: Clip) {
670669 /// Turns the border strips from `first` on into a ring around the window's
671670 /// rounded outline.
672671 fn ring_borders(out: &mut [Draw], first: usize, window: Clip, b: i32) {
672+ let grow = |r: f32| if r > 0.0 { r + b as f32 } else { 0.0 };
673673 let ring = Clip {
674+ rect: Rectangle::new(
675+ window.rect.loc - Point::from((b, b)),
676+ window.rect.size + Size::from((2 * b, 2 * b)),
677+ ),
678+ radii: window.radii.map(grow),
674679 ring: b as f32,
675- ..outset(window, b)
676680 };
677681 for draw in &mut out[first..] {
678682 wrap(draw, ring);
679683 }
680684 }
681685
682-fn outset(clip: Clip, by: i32) -> Clip {
683- let grow = |r: f32| if r > 0.0 { r + by as f32 } else { 0.0 };
684- Clip {
685- rect: outset_rect(clip.rect, by),
686- radii: clip.radii.map(grow),
687- ring: clip.ring,
688- }
689-}
690-
691-fn outset_rect(r: Rectangle<i32, Physical>, by: i32) -> Rectangle<i32, Physical> {
692- Rectangle::new(
693- r.loc - Point::from((by, by)),
694- r.size + Size::from((2 * by, 2 * by)),
695- )
696-}
697-
698686 fn wrap(draw: &mut Draw, clip: Clip) {
699687 let inner = std::mem::replace(draw, Draw::Clipped(ClipElement::empty()));
700688 *draw = Draw::Clipped(ClipElement::new(inner, clip));
Modified file: crates/monolith/src/wm.rs+2 additions−2 deletions
@@ -99,7 +99,7 @@ pub struct WindowState {
9999 /// A rule sent it somewhere, so activation tokens leave it alone.
100100 pub placed_by_rule: bool,
101101 pub list_handle: Option<ForeignToplevelHandle>,
102- pub borders: [Option<[SolidColorBuffer; 4]>; 2],
102+ pub borders: Option<[SolidColorBuffer; 4]>,
103103 pub app_id: String,
104104 pub title: String,
105105 /// The title it mapped with, which `initial_title` rules match.
@@ -172,7 +172,7 @@ impl Wm {
172172 reclassify: false,
173173 placed_by_rule,
174174 list_handle: None,
175- borders: [None, None],
175+ borders: None,
176176 app_id,
177177 initial_title: title.clone(),
178178 title,