1
0
Fork 0
forked from wry/wry

add window gaps

This commit is contained in:
atagen 2026-04-05 20:04:13 +10:00
parent 769d12a525
commit 750bf06ce9
9 changed files with 144 additions and 13 deletions

View file

@ -363,5 +363,12 @@ pub mod sized {
/// ///
/// Default: 1 /// Default: 1
const 04 => BAR_SEPARATOR_WIDTH, const 04 => BAR_SEPARATOR_WIDTH,
/// The gap between tiled windows in pixels.
///
/// When set to a value greater than 0, windows are separated by this
/// gap rather than by the border width.
///
/// Default: 0
const 05 => GAP,
} }
} }

View file

@ -2440,6 +2440,7 @@ impl ConfigProxyHandler {
BORDER_WIDTH => ThemeSized::border_width, BORDER_WIDTH => ThemeSized::border_width,
BAR_HEIGHT => ThemeSized::bar_height, BAR_HEIGHT => ThemeSized::bar_height,
BAR_SEPARATOR_WIDTH => ThemeSized::bar_separator_width, BAR_SEPARATOR_WIDTH => ThemeSized::bar_separator_width,
GAP => ThemeSized::gap,
_ => return Err(CphError::UnknownSized(sized.0)), _ => return Err(CphError::UnknownSized(sized.0)),
}; };
Ok(sized) Ok(sized)

View file

@ -334,18 +334,102 @@ impl Renderer<'_> {
} }
} }
if let Some(child) = container.mono_child.get() { if let Some(child) = container.mono_child.get() {
let body = container.mono_body.get().move_(x, y); let mb = container.mono_body.get();
if self.state.theme.sizes.gap.get() > 0 && !child.node.node_is_container() {
let srgb_srgb = self.state.color_manager.srgb_gamma22();
let bw = self.state.theme.sizes.border_width.get();
let border_color = self.state.theme.colors.border.get();
let focused_border_color =
self.state.theme.colors.focused_title_background.get();
let c = if child.active.get() {
&focused_border_color
} else {
&border_color
};
let full_h = mb.y2();
let full_w = mb.width();
let frame_rects = [
Rect::new_sized_saturating(mb.x1() - bw, 0, bw, full_h),
Rect::new_sized_saturating(mb.x2(), 0, bw, full_h),
Rect::new_sized_saturating(mb.x1() - bw, -bw, full_w + 2 * bw, bw),
Rect::new_sized_saturating(mb.x1() - bw, full_h, full_w + 2 * bw, bw),
];
self.base.fill_boxes2(
&frame_rects,
c,
&srgb_srgb.linear,
RenderIntent::Perceptual,
x,
y,
);
}
let body = mb.move_(x, y);
let body = self.base.scale_rect(body); let body = self.base.scale_rect(body);
let content = container.mono_content.get(); let content = container.mono_content.get();
child child
.node .node
.node_render(self, x + content.x1(), y + content.y1(), Some(&body)); .node_render(self, x + content.x1(), y + content.y1(), Some(&body));
} else { } else {
let gap = self.state.theme.sizes.gap.get();
let (srgb_srgb, bw, border_color, focused_border_color, tpuh) = if gap > 0 {
let srgb_srgb = self.state.color_manager.srgb_gamma22();
let bw = self.state.theme.sizes.border_width.get();
let border_color = self.state.theme.colors.border.get();
let focused_border_color = self.state.theme.colors.focused_title_background.get();
let tpuh = self.state.theme.title_plus_underline_height();
(
Some(srgb_srgb),
bw,
border_color,
focused_border_color,
tpuh,
)
} else {
(None, 0, Color::SOLID_BLACK, Color::SOLID_BLACK, 0)
};
for child in container.children.iter() { for child in container.children.iter() {
let body = child.body.get(); let body = child.body.get();
if body.x1() >= container.width.get() || body.y1() >= container.height.get() { if body.x1() >= container.width.get() || body.y1() >= container.height.get() {
break; break;
} }
if let Some(srgb_srgb) = srgb_srgb {
if !child.node.node_is_container() {
let srgb = &srgb_srgb.linear;
let c = if child.border_color_is_focused.get() {
&focused_border_color
} else {
&border_color
};
let title_rect = child.title_rect.get();
let top_y = if tpuh > 0 { title_rect.y1() } else { body.y1() };
let full_h = body.y2() - top_y;
let full_w = body.width();
let frame_rects = [
Rect::new_sized_saturating(body.x1() - bw, top_y, bw, full_h),
Rect::new_sized_saturating(body.x2(), top_y, bw, full_h),
Rect::new_sized_saturating(
body.x1() - bw,
top_y - bw,
full_w + 2 * bw,
bw,
),
Rect::new_sized_saturating(
body.x1() - bw,
body.y2(),
full_w + 2 * bw,
bw,
),
];
self.base.fill_boxes2(
&frame_rects,
c,
srgb,
RenderIntent::Perceptual,
x,
y,
);
}
}
let body = body.move_(x, y); let body = body.move_(x, y);
let body = self.base.scale_rect(body); let body = self.base.scale_rect(body);
let content = child.content.get(); let content = child.content.get();

View file

@ -586,6 +586,7 @@ sizes! {
bar_height = (0, 1000, 17), bar_height = (0, 1000, 17),
border_width = (0, 1000, 4), border_width = (0, 1000, 4),
bar_separator_width = (0, 1000, 1), bar_separator_width = (0, 1000, 1),
gap = (0, 1000, 0),
} }
impl StaticText for ThemeSized { impl StaticText for ThemeSized {
@ -595,6 +596,7 @@ impl StaticText for ThemeSized {
ThemeSized::bar_height => "Bar Height", ThemeSized::bar_height => "Bar Height",
ThemeSized::border_width => "Border Width", ThemeSized::border_width => "Border Width",
ThemeSized::bar_separator_width => "Bar Separator Width", ThemeSized::bar_separator_width => "Bar Separator Width",
ThemeSized::gap => "Gap",
} }
} }
} }

View file

@ -158,6 +158,7 @@ pub struct ContainerChild {
pub body: Cell<Rect>, pub body: Cell<Rect>,
pub content: Cell<Rect>, pub content: Cell<Rect>,
factor: Cell<f64>, factor: Cell<f64>,
pub border_color_is_focused: Cell<bool>,
} }
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
@ -211,6 +212,7 @@ impl ContainerNode {
title_rect: Default::default(), title_rect: Default::default(),
focus_history: Default::default(), focus_history: Default::default(),
attention_requested: Cell::new(false), attention_requested: Cell::new(false),
border_color_is_focused: Default::default(),
}); });
let child_node_ref = child_node.clone(); let child_node_ref = child_node.clone();
let mut child_nodes = AHashMap::new(); let mut child_nodes = AHashMap::new();
@ -332,6 +334,7 @@ impl ContainerNode {
title_rect: Default::default(), title_rect: Default::default(),
focus_history: Default::default(), focus_history: Default::default(),
attention_requested: Default::default(), attention_requested: Default::default(),
border_color_is_focused: Default::default(),
}); });
let r = link.to_ref(); let r = link.to_ref();
links.insert(new.node_id(), link); links.insert(new.node_id(), link);
@ -382,11 +385,16 @@ impl ContainerNode {
} }
fn damage(&self) { fn damage(&self) {
let bw = if self.state.theme.sizes.gap.get() > 0 {
self.state.theme.sizes.border_width.get()
} else {
0
};
self.state.damage(Rect::new_sized_saturating( self.state.damage(Rect::new_sized_saturating(
self.abs_x1.get(), self.abs_x1.get() - bw,
self.abs_y1.get(), self.abs_y1.get() - bw,
self.width.get(), self.width.get() + 2 * bw,
self.height.get(), self.height.get() + 2 * bw,
)); ));
} }
@ -447,6 +455,7 @@ impl ContainerNode {
fn perform_split_layout(self: &Rc<Self>) { fn perform_split_layout(self: &Rc<Self>) {
let sum_factors = self.sum_factors.get(); let sum_factors = self.sum_factors.get();
let border_width = self.state.theme.sizes.border_width.get(); let border_width = self.state.theme.sizes.border_width.get();
let spacing = self.state.theme.sizes.gap.get().max(border_width);
let title_height_tmp = self.state.theme.title_height(); let title_height_tmp = self.state.theme.title_height();
let title_plus_underline_height = self.state.theme.title_plus_underline_height(); let title_plus_underline_height = self.state.theme.title_plus_underline_height();
let split = self.split.get(); let split = self.split.get();
@ -482,7 +491,7 @@ impl ContainerNode {
}; };
let body = Rect::new_sized_saturating(x1, y1, width, height); let body = Rect::new_sized_saturating(x1, y1, width, height);
child.body.set(body); child.body.set(body);
pos += body_size + border_width; pos += body_size + spacing;
if split == ContainerSplit::Vertical { if split == ContainerSplit::Vertical {
pos += title_plus_underline_height; pos += title_plus_underline_height;
} }
@ -522,7 +531,7 @@ impl ContainerNode {
}; };
body = Rect::new_sized_saturating(x1, y1, width, height); body = Rect::new_sized_saturating(x1, y1, width, height);
child.body.set(body); child.body.set(body);
pos += size + border_width; pos += size + spacing;
if split == ContainerSplit::Vertical { if split == ContainerSplit::Vertical {
pos += title_plus_underline_height; pos += title_plus_underline_height;
} }
@ -545,11 +554,12 @@ impl ContainerNode {
fn update_content_size(&self) { fn update_content_size(&self) {
let border_width = self.state.theme.sizes.border_width.get(); let border_width = self.state.theme.sizes.border_width.get();
let spacing = self.state.theme.sizes.gap.get().max(border_width);
let title_plus_underline_height = self.state.theme.title_plus_underline_height(); let title_plus_underline_height = self.state.theme.title_plus_underline_height();
let nc = self.num_children.get(); let nc = self.num_children.get();
match self.split.get() { match self.split.get() {
ContainerSplit::Horizontal => { ContainerSplit::Horizontal => {
let new_content_size = self.width.get().sub((nc - 1) as i32 * border_width).max(0); let new_content_size = self.width.get().sub((nc - 1) as i32 * spacing).max(0);
self.content_width.set(new_content_size); self.content_width.set(new_content_size);
self.content_height self.content_height
.set(self.height.get().sub(title_plus_underline_height).max(0)); .set(self.height.get().sub(title_plus_underline_height).max(0));
@ -560,7 +570,7 @@ impl ContainerNode {
.get() .get()
.sub( .sub(
title_plus_underline_height title_plus_underline_height
+ (nc - 1) as i32 * (border_width + title_plus_underline_height), + (nc - 1) as i32 * (spacing + title_plus_underline_height),
) )
.max(0); .max(0);
self.content_height.set(new_content_size); self.content_height.set(new_content_size);
@ -832,6 +842,7 @@ impl ContainerNode {
let have_active = self.children.iter().any(|c| c.active.get()); let have_active = self.children.iter().any(|c| c.active.get());
let abs_x = self.abs_x1.get(); let abs_x = self.abs_x1.get();
let abs_y = self.abs_y1.get(); let abs_y = self.abs_y1.get();
let gap = self.state.theme.sizes.gap.get();
for (i, child) in self.children.iter().enumerate() { for (i, child) in self.children.iter().enumerate() {
let rect = child.title_rect.get(); let rect = child.title_rect.get();
if self.toplevel_data.visible.get() && !mono && split != ContainerSplit::Horizontal { if self.toplevel_data.visible.get() && !mono && split != ContainerSplit::Horizontal {
@ -842,15 +853,17 @@ impl ContainerNode {
rect.height() + tuh, rect.height() + tuh,
)); ));
} }
if i > 0 { if gap > 0 && !mono && !child.node.node_is_container() {
let rect = if mono { child.border_color_is_focused.set(child.active.get());
} else if gap == 0 && i > 0 {
let sep = if mono {
Rect::new_sized_saturating(rect.x1() - bw, 0, bw, th) Rect::new_sized_saturating(rect.x1() - bw, 0, bw, th)
} else if split == ContainerSplit::Horizontal { } else if split == ContainerSplit::Horizontal {
Rect::new_sized_saturating(rect.x1() - bw, 0, bw, cheight) Rect::new_sized_saturating(rect.x1() - bw, 0, bw, cheight)
} else { } else {
Rect::new_sized_saturating(0, rect.y1() - bw, cwidth, bw) Rect::new_sized_saturating(0, rect.y1() - bw, cwidth, bw)
}; };
rd.border_rects.push(rect); rd.border_rects.push(sep);
} }
if child.active.get() { if child.active.get() {
rd.active_title_rects.push(rect); rd.active_title_rects.push(rect);
@ -1184,6 +1197,9 @@ impl ContainerNode {
// log::info!("node_child_active_changed"); // log::info!("node_child_active_changed");
self.schedule_render_titles(); self.schedule_render_titles();
self.schedule_compute_render_positions(); self.schedule_compute_render_positions();
if self.state.theme.sizes.gap.get() > 0 && self.toplevel_data.visible.get() {
self.damage();
}
if let Some(parent) = self.toplevel_data.parent.get() { if let Some(parent) = self.toplevel_data.parent.get() {
parent.node_child_active_changed(self.deref(), active, depth + 1); parent.node_child_active_changed(self.deref(), active, depth + 1);
} }
@ -1920,6 +1936,7 @@ impl ContainingNode for ContainerNode {
title_rect: Cell::new(node.title_rect.get()), title_rect: Cell::new(node.title_rect.get()),
focus_history: Cell::new(None), focus_history: Cell::new(None),
attention_requested: Cell::new(false), attention_requested: Cell::new(false),
border_color_is_focused: Default::default(),
}); });
if let Some(fh) = node.focus_history.take() { if let Some(fh) = node.focus_history.take() {
link.focus_history.set(Some(fh.append(link.to_ref()))); link.focus_history.set(Some(fh.append(link.to_ref())));
@ -1974,6 +1991,7 @@ impl ContainingNode for ContainerNode {
}; };
let num_children = self.num_children.fetch_sub(1) - 1; let num_children = self.num_children.fetch_sub(1) - 1;
if num_children == 0 { if num_children == 0 {
self.damage();
self.tl_destroy(); self.tl_destroy();
return; return;
} }

View file

@ -819,6 +819,21 @@ impl OutputNode {
.set(bar_rect_with_separator_rel); .set(bar_rect_with_separator_rel);
self.bar_separator_rect.set(bar_separator_rect); self.bar_separator_rect.set(bar_separator_rect);
self.bar_separator_rect_rel.set(bar_separator_rect_rel); self.bar_separator_rect_rel.set(bar_separator_rect_rel);
let gap = self.state.theme.sizes.gap.get();
if gap > 0 {
workspace_rect = Rect::new_sized_saturating(
workspace_rect.x1() + gap,
workspace_rect.y1() + gap,
(workspace_rect.width() - 2 * gap).max(0),
(workspace_rect.height() - 2 * gap).max(0),
);
workspace_rect_rel = Rect::new_sized_saturating(
workspace_rect_rel.x1() + gap,
workspace_rect_rel.y1() + gap,
(workspace_rect_rel.width() - 2 * gap).max(0),
(workspace_rect_rel.height() - 2 * gap).max(0),
);
}
self.workspace_rect.set(workspace_rect); self.workspace_rect.set(workspace_rect);
self.workspace_rect_rel.set(workspace_rect_rel); self.workspace_rect_rel.set(workspace_rect_rel);
self.update_tray_positions(); self.update_tray_positions();

View file

@ -223,6 +223,7 @@ pub struct Theme {
pub bar_font: Option<String>, pub bar_font: Option<String>,
pub bar_position: Option<BarPosition>, pub bar_position: Option<BarPosition>,
pub bar_separator_width: Option<i32>, pub bar_separator_width: Option<i32>,
pub gap: Option<i32>,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -63,7 +63,7 @@ impl Parser for ThemeParser<'_> {
font, font,
title_font, title_font,
), ),
(bar_font, bar_position_val, bar_separator_width), (bar_font, bar_position_val, bar_separator_width, gap),
) = ext.extract(( ) = ext.extract((
( (
opt(val("attention-requested-bg-color")), opt(val("attention-requested-bg-color")),
@ -93,6 +93,7 @@ impl Parser for ThemeParser<'_> {
recover(opt(str("bar-font"))), recover(opt(str("bar-font"))),
recover(opt(str("bar-position"))), recover(opt(str("bar-position"))),
recover(opt(s32("bar-separator-width"))), recover(opt(s32("bar-separator-width"))),
recover(opt(s32("gap"))),
), ),
))?; ))?;
macro_rules! color { macro_rules! color {
@ -146,6 +147,7 @@ impl Parser for ThemeParser<'_> {
bar_font: bar_font.map(|f| f.value.to_string()), bar_font: bar_font.map(|f| f.value.to_string()),
bar_position, bar_position,
bar_separator_width: bar_separator_width.despan(), bar_separator_width: bar_separator_width.despan(),
gap: gap.despan(),
}) })
} }
} }

View file

@ -1003,6 +1003,7 @@ impl State {
size!(TITLE_HEIGHT, title_height); size!(TITLE_HEIGHT, title_height);
size!(BAR_HEIGHT, bar_height); size!(BAR_HEIGHT, bar_height);
size!(BAR_SEPARATOR_WIDTH, bar_separator_width); size!(BAR_SEPARATOR_WIDTH, bar_separator_width);
size!(GAP, gap);
macro_rules! font { macro_rules! font {
($fun:ident, $field:ident) => { ($fun:ident, $field:ident) => {
if let Some(font) = &theme.$field { if let Some(font) = &theme.$field {