1
0
Fork 0
forked from wry/wry

renderer: add support for floating-titlebars (#4)

Reviewed-on: https://git.kosslan.dev/wry/jay/pulls/4
This commit is contained in:
kossLAN 2026-04-06 20:58:36 -04:00 committed by atagen
parent 4d803360dd
commit 6dba659978
13 changed files with 316 additions and 158 deletions

View file

@ -1421,6 +1421,16 @@ impl ConfigProxyHandler {
});
}
fn handle_set_floating_titles(&self, floating: bool) {
self.state.set_floating_titles(floating);
}
fn handle_get_floating_titles(&self) {
self.respond(Response::GetFloatingTitles {
floating: self.state.theme.floating_titles.get(),
});
}
fn handle_set_bar_position(&self, position: BarPosition) -> Result<(), CphError> {
let Ok(position) = position.try_into() else {
return Err(CphError::UnknownBarPosition(position));
@ -2441,6 +2451,7 @@ impl ConfigProxyHandler {
BAR_HEIGHT => ThemeSized::bar_height,
BAR_SEPARATOR_WIDTH => ThemeSized::bar_separator_width,
GAP => ThemeSized::gap,
TITLE_GAP => ThemeSized::title_gap,
_ => return Err(CphError::UnknownSized(sized.0)),
};
Ok(sized)
@ -3291,6 +3302,10 @@ impl ConfigProxyHandler {
ClientMessage::GetShowBar => self.handle_get_show_bar(),
ClientMessage::SetShowTitles { show } => self.handle_set_show_titles(show),
ClientMessage::GetShowTitles => self.handle_get_show_titles(),
ClientMessage::SetFloatingTitles { floating } => {
self.handle_set_floating_titles(floating)
}
ClientMessage::GetFloatingTitles => self.handle_get_floating_titles(),
ClientMessage::SetBarPosition { position } => self
.handle_set_bar_position(position)
.wrn("set_bar_position")?,

View file

@ -277,65 +277,70 @@ impl Renderer<'_> {
self.render_tl_aux(placeholder.tl_data(), bounds, true);
}
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
{
let srgb_srgb = self.state.color_manager.srgb_gamma22();
let srgb = &srgb_srgb.linear;
let perceptual = RenderIntent::Perceptual;
let rd = container.render_data.borrow_mut();
let c = self.state.theme.colors.unfocused_title_background.get();
fn render_container_decorations(&mut self, container: &ContainerNode, x: i32, y: i32) {
let srgb_srgb = self.state.color_manager.srgb_gamma22();
let srgb = &srgb_srgb.linear;
let perceptual = RenderIntent::Perceptual;
let rd = container.render_data.borrow_mut();
let c = self.state.theme.colors.unfocused_title_background.get();
self.base
.fill_boxes2(&rd.title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.focused_title_background.get();
self.base
.fill_boxes2(&rd.active_title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.attention_requested_background.get();
self.base
.fill_boxes2(&rd.attention_title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.separator.get();
self.base
.fill_boxes2(&rd.underline_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.border.get();
self.base
.fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y);
if let Some(lar) = &rd.last_active_rect {
let c = self
.state
.theme
.colors
.focused_inactive_title_background
.get();
self.base
.fill_boxes2(&rd.title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.focused_title_background.get();
self.base
.fill_boxes2(&rd.active_title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.attention_requested_background.get();
self.base
.fill_boxes2(&rd.attention_title_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.separator.get();
self.base
.fill_boxes2(&rd.underline_rects, &c, srgb, perceptual, x, y);
let c = self.state.theme.colors.border.get();
self.base
.fill_boxes2(&rd.border_rects, &c, srgb, perceptual, x, y);
if let Some(lar) = &rd.last_active_rect {
let c = self
.state
.theme
.colors
.focused_inactive_title_background
.get();
self.base
.fill_boxes2(std::slice::from_ref(lar), &c, srgb, perceptual, x, y);
}
if let Some(titles) = rd.titles.get(&self.base.scale) {
for title in titles {
let rect = title.rect.move_(x, y);
let bounds = self.base.scale_rect(rect);
let (x, y) = self.base.scale_point(rect.x1(), rect.y1());
self.base.render_texture(
&title.tex,
None,
x,
y,
None,
None,
self.base.scale,
Some(&bounds),
None,
AcquireSync::None,
ReleaseSync::None,
false,
srgb_srgb,
perceptual,
AlphaMode::PremultipliedElectrical,
);
}
.fill_boxes2(std::slice::from_ref(lar), &c, srgb, perceptual, x, y);
}
if let Some(titles) = rd.titles.get(&self.base.scale) {
for title in titles {
let rect = title.rect.move_(x, y);
let bounds = self.base.scale_rect(rect);
let (x, y) = self.base.scale_point(rect.x1(), rect.y1());
self.base.render_texture(
&title.tex,
None,
x,
y,
None,
None,
self.base.scale,
Some(&bounds),
None,
AcquireSync::None,
ReleaseSync::None,
false,
srgb_srgb,
perceptual,
AlphaMode::PremultipliedElectrical,
);
}
}
}
pub fn render_container(&mut self, container: &ContainerNode, x: i32, y: i32) {
let floating = self.state.theme.floating_titles.get();
self.render_container_decorations(container, x, y);
if let Some(child) = container.mono_child.get() {
let mb = container.mono_body.get();
if self.state.theme.sizes.gap.get() > 0 && !child.node.node_is_container() {
if self.state.theme.sizes.gap.get() != 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();
@ -346,22 +351,48 @@ impl Renderer<'_> {
} 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 srgb = &srgb_srgb.linear;
let perceptual = RenderIntent::Perceptual;
if floating {
if !child.node.node_is_container() {
let body_frame = [
Rect::new_sized_saturating(mb.x1() - bw, mb.y1(), bw, mb.y2() - mb.y1()),
Rect::new_sized_saturating(mb.x2(), mb.y1(), bw, mb.y2() - mb.y1()),
Rect::new_sized_saturating(mb.x1() - bw, mb.y1() - bw, full_w + 2 * bw, bw),
Rect::new_sized_saturating(mb.x1() - bw, mb.y2(), full_w + 2 * bw, bw),
];
self.base.fill_boxes2(&body_frame, c, srgb, perceptual, x, y);
}
let th = self.state.theme.title_height();
if th > 0 {
for tab in container.children.iter() {
let tr = tab.title_rect.get();
let tc = if tab.active.get() {
&focused_border_color
} else {
&border_color
};
let tw = tr.width();
let tab_frame = [
Rect::new_sized_saturating(tr.x1() - bw, tr.y1() - bw, tw + 2 * bw, bw),
Rect::new_sized_saturating(tr.x1() - bw, tr.y1(), bw, th),
Rect::new_sized_saturating(tr.x2(), tr.y1(), bw, th),
Rect::new_sized_saturating(tr.x1() - bw, tr.y1() + th, tw + 2 * bw, bw),
];
self.base.fill_boxes2(&tab_frame, tc, srgb, perceptual, x, y);
}
}
} else if !child.node.node_is_container() {
let full_h = mb.y2();
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, perceptual, x, y);
}
}
let body = mb.move_(x, y);
let body = self.base.scale_rect(body);
@ -371,7 +402,7 @@ impl Renderer<'_> {
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
} 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, 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();
@ -393,41 +424,46 @@ impl Renderer<'_> {
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 srgb = &srgb_srgb.linear;
let c = if floating {
if child.active.get() { &focused_border_color } else { &border_color }
} else if child.border_color_is_focused.get() {
&focused_border_color
} else {
&border_color
};
let title_rect = child.title_rect.get();
let full_w = body.width();
let perceptual = RenderIntent::Perceptual;
if floating && tpuh > 0 {
let tw = title_rect.width();
let title_h = title_rect.height();
let title_frame = [
Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1() - bw, tw + 2 * bw, bw),
Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1(), bw, title_h),
Rect::new_sized_saturating(title_rect.x2(), title_rect.y1(), bw, title_h),
Rect::new_sized_saturating(title_rect.x1() - bw, title_rect.y1() + title_h, tw + 2 * bw, bw),
];
self.base.fill_boxes2(&title_frame, c, srgb, perceptual, x, y);
if !child.node.node_is_container() && gap != 0 {
let body_frame = [
Rect::new_sized_saturating(body.x1() - bw, body.y1(), bw, body.y2() - body.y1()),
Rect::new_sized_saturating(body.x2(), body.y1(), bw, body.y2() - body.y1()),
Rect::new_sized_saturating(body.x1() - bw, body.y1() - bw, full_w + 2 * bw, bw),
Rect::new_sized_saturating(body.x1() - bw, body.y2(), full_w + 2 * bw, bw),
];
self.base.fill_boxes2(&body_frame, c, srgb, perceptual, x, y);
}
} else if !child.node.node_is_container() && gap != 0 {
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,
),
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,
);
self.base.fill_boxes2(&frame_rects, c, srgb, perceptual, x, y);
}
}
let body = body.move_(x, y);
@ -438,6 +474,7 @@ impl Renderer<'_> {
.node_render(self, x + content.x1(), y + content.y1(), Some(&body));
}
}
self.render_tl_aux(container.tl_data(), None, false);
}

View file

@ -1861,6 +1861,11 @@ impl State {
self.spaces_changed();
}
pub fn set_floating_titles(&self, floating: bool) {
self.theme.floating_titles.set(floating);
self.spaces_changed();
}
pub fn set_ui_drag_enabled(&self, enabled: bool) {
self.ui_drag_enabled.set(enabled);
}

View file

@ -587,6 +587,7 @@ sizes! {
border_width = (0, 1000, 4),
bar_separator_width = (0, 1000, 1),
gap = (0, 1000, 0),
title_gap = (0, 1000, 5),
}
impl StaticText for ThemeSized {
@ -597,6 +598,7 @@ impl StaticText for ThemeSized {
ThemeSized::border_width => "Border Width",
ThemeSized::bar_separator_width => "Bar Separator Width",
ThemeSized::gap => "Gap",
ThemeSized::title_gap => "Title Gap",
}
}
}
@ -649,6 +651,7 @@ pub struct Theme {
pub title_font: CloneCell<Option<Arc<String>>>,
pub default_font: Arc<String>,
pub show_titles: Cell<bool>,
pub floating_titles: Cell<bool>,
pub bar_position: Cell<BarPosition>,
}
@ -663,6 +666,7 @@ impl Default for Theme {
title_font: Default::default(),
default_font,
show_titles: Cell::new(true),
floating_titles: Cell::new(false),
bar_position: Default::default(),
}
}
@ -689,9 +693,22 @@ impl Theme {
if self.show_titles.get() { 1 } else { 0 }
}
pub fn floating_title_top_margin(&self) -> i32 {
if self.floating_titles.get() && self.sizes.gap.get() != 0 {
self.sizes.border_width.get()
} else {
0
}
}
pub fn title_plus_underline_height(&self) -> i32 {
if self.show_titles.get() {
self.sizes.title_height.get() + 1
if self.floating_titles.get() && self.sizes.gap.get() != 0 {
let bw = self.sizes.border_width.get();
3 * bw + self.sizes.title_height.get() + self.sizes.title_gap.get()
} else {
self.sizes.title_height.get() + 1
}
} else {
0
}

View file

@ -383,16 +383,26 @@ impl ContainerNode {
let mb = self.mono_body.get();
return (mb.width(), mb.height());
}
let spacing = self.state.theme.sizes.gap.get().max(
self.state.theme.sizes.border_width.get(),
);
let gap = self.state.theme.sizes.gap.get();
let bw = self.state.theme.sizes.border_width.get();
let floating = self.state.theme.floating_titles.get() && gap != 0;
let nc = self.num_children.get() as i32 + 1;
match self.split.get() {
ContainerSplit::Horizontal => {
let spacing = if floating {
self.state.theme.sizes.title_gap.get() + 2 * bw
} else {
gap.max(bw)
};
let content_w = self.width.get().sub((nc - 1) * spacing).max(0);
(content_w / nc, self.height.get().sub(tpuh).max(0))
}
ContainerSplit::Vertical => {
let spacing = if floating {
self.state.theme.sizes.title_gap.get() + bw
} else {
gap.max(bw)
};
let content_h = self
.height
.get()
@ -407,6 +417,7 @@ impl ContainerNode {
self.update_content_size();
// log::info!("on_spaces_changed");
self.schedule_layout();
self.schedule_compute_render_positions();
}
pub fn on_colors_changed(self: &Rc<Self>) {
@ -416,7 +427,7 @@ impl ContainerNode {
}
fn damage(&self) {
let bw = if self.state.theme.sizes.gap.get() > 0 {
let bw = if self.state.theme.sizes.gap.get() != 0 {
self.state.theme.sizes.border_width.get()
} else {
0
@ -485,8 +496,15 @@ impl ContainerNode {
let th = self.state.theme.title_height();
let bw = self.state.theme.sizes.border_width.get();
let bw_top = self.state.theme.floating_title_top_margin();
let floating = self.state.theme.floating_titles.get();
let spacing = if floating {
self.state.theme.sizes.title_gap.get() + 2 * bw
} else {
bw
};
let num_children = self.num_children.get() as i32;
let content_width = self.width.get().sub(bw * (num_children - 1)).max(0);
let content_width = self.width.get().sub(spacing * (num_children - 1)).max(0);
let width_per_child = content_width / num_children;
let mut rem = content_width % num_children;
let mut pos = 0;
@ -498,18 +516,28 @@ impl ContainerNode {
}
child
.title_rect
.set(Rect::new_sized_saturating(pos, 0, width, th));
pos += width + bw;
.set(Rect::new_sized_saturating(pos, bw_top, width, th));
pos += width + spacing;
}
}
fn perform_split_layout(self: &Rc<Self>) {
let sum_factors = self.sum_factors.get();
let gap = self.state.theme.sizes.gap.get();
let border_width = self.state.theme.sizes.border_width.get();
let spacing = self.state.theme.sizes.gap.get().max(border_width);
let floating = self.state.theme.floating_titles.get() && gap != 0;
let title_height_tmp = self.state.theme.title_height();
let title_plus_underline_height = self.state.theme.title_plus_underline_height();
let tpuh = self.state.theme.title_plus_underline_height();
let split = self.split.get();
let spacing = if floating {
let title_gap = self.state.theme.sizes.title_gap.get();
match split {
ContainerSplit::Horizontal => title_gap + 2 * border_width,
ContainerSplit::Vertical => title_gap + border_width,
}
} else {
gap.max(border_width)
};
let (content_size, other_content_size) = match split {
ContainerSplit::Horizontal => (self.content_width.get(), self.content_height.get()),
ContainerSplit::Vertical => (self.content_height.get(), self.content_width.get()),
@ -527,24 +555,14 @@ impl ContainerNode {
body_size = body_size.min(remaining_content_size);
remaining_content_size -= body_size;
let (x1, y1, width, height) = match split {
ContainerSplit::Horizontal => (
pos,
title_plus_underline_height,
body_size,
other_content_size,
),
_ => (
0,
pos + title_plus_underline_height,
other_content_size,
body_size,
),
ContainerSplit::Horizontal => (pos, tpuh, body_size, other_content_size),
_ => (0, pos + tpuh, other_content_size, body_size),
};
let body = Rect::new_sized_saturating(x1, y1, width, height);
child.body.set(body);
pos += body_size + spacing;
if split == ContainerSplit::Vertical {
pos += title_plus_underline_height;
pos += tpuh;
}
}
if remaining_content_size > 0 {
@ -561,39 +579,28 @@ impl ContainerNode {
let (x1, y1, width, height, size) = match split {
ContainerSplit::Horizontal => {
let width = body.width() + add;
(
pos,
title_plus_underline_height,
width,
other_content_size,
width,
)
(pos, tpuh, width, other_content_size, width)
}
_ => {
let height = body.height() + add;
(
0,
pos + title_plus_underline_height,
other_content_size,
height,
height,
)
(0, pos + tpuh, other_content_size, height, height)
}
};
body = Rect::new_sized_saturating(x1, y1, width, height);
child.body.set(body);
pos += size + spacing;
if split == ContainerSplit::Vertical {
pos += title_plus_underline_height;
pos += tpuh;
}
}
}
let bw_top = self.state.theme.floating_title_top_margin();
self.sum_factors.set(1.0);
for child in self.children.iter() {
let body = child.body.get();
child.title_rect.set(Rect::new_sized_saturating(
body.x1(),
body.y1() - title_plus_underline_height,
body.y1() - tpuh + bw_top,
body.width(),
title_height_tmp,
));
@ -604,25 +611,33 @@ impl ContainerNode {
}
fn update_content_size(&self) {
let gap = self.state.theme.sizes.gap.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 floating = self.state.theme.floating_titles.get() && gap != 0;
let title_gap = self.state.theme.sizes.title_gap.get();
let tpuh = self.state.theme.title_plus_underline_height();
let nc = self.num_children.get();
match self.split.get() {
ContainerSplit::Horizontal => {
let spacing = if floating {
title_gap + 2 * border_width
} else {
gap.max(border_width)
};
let new_content_size = self.width.get().sub((nc - 1) as i32 * spacing).max(0);
self.content_width.set(new_content_size);
self.content_height
.set(self.height.get().sub(title_plus_underline_height).max(0));
self.content_height.set(self.height.get().sub(tpuh).max(0));
}
ContainerSplit::Vertical => {
let spacing = if floating {
title_gap + border_width
} else {
gap.max(border_width)
};
let new_content_size = self
.height
.get()
.sub(
title_plus_underline_height
+ (nc - 1) as i32 * (spacing + title_plus_underline_height),
)
.sub(tpuh + (nc - 1) as i32 * (spacing + tpuh))
.max(0);
self.content_height.set(new_content_size);
self.content_width.set(self.width.get());
@ -630,9 +645,9 @@ impl ContainerNode {
}
self.mono_body.set(Rect::new_sized_saturating(
0,
title_plus_underline_height,
tpuh,
self.width.get(),
self.height.get() - title_plus_underline_height,
self.height.get() - tpuh,
));
}
@ -904,9 +919,9 @@ impl ContainerNode {
rect.height() + tuh,
));
}
if gap > 0 && !mono && !child.node.node_is_container() {
if gap != 0 && !mono && !child.node.node_is_container() {
child.border_color_is_focused.set(child.active.get());
} else if gap == 0 && i > 0 {
} else if i > 0 {
let sep = if mono {
Rect::new_sized_saturating(rect.x1() - bw, 0, bw, th)
} else if split == ContainerSplit::Horizontal {
@ -914,7 +929,10 @@ impl ContainerNode {
} else {
Rect::new_sized_saturating(0, rect.y1() - bw, cwidth, bw)
};
rd.border_rects.push(sep);
let floating = self.state.theme.floating_titles.get();
if gap == 0 || (mono && !floating) {
rd.border_rects.push(sep);
}
}
if child.active.get() {
rd.active_title_rects.push(rect);
@ -925,7 +943,7 @@ impl ContainerNode {
} else {
rd.title_rects.push(rect);
}
if !mono {
if !mono && (!self.state.theme.floating_titles.get() || gap == 0) {
let rect = Rect::new_sized_saturating(rect.x1(), rect.y2(), rect.width(), 1);
rd.underline_rects.push(rect);
}
@ -937,10 +955,14 @@ impl ContainerNode {
}
}
}
if mono {
if mono && (!self.state.theme.floating_titles.get() || gap == 0) {
rd.underline_rects
.push(Rect::new_sized_saturating(0, th, cwidth, tuh));
}
if gap == 0 && th > 0 {
rd.underline_rects
.push(Rect::new_sized_saturating(0, 0, cwidth, tuh));
}
if self.toplevel_data.visible.get() && (mono || split == ContainerSplit::Horizontal) {
self.state
.damage(Rect::new_sized_saturating(abs_x, abs_y, cwidth, tpuh));
@ -1248,7 +1270,7 @@ impl ContainerNode {
// log::info!("node_child_active_changed");
self.schedule_render_titles();
self.schedule_compute_render_positions();
if self.state.theme.sizes.gap.get() > 0 && self.toplevel_data.visible.get() {
if self.state.theme.sizes.gap.get() != 0 && self.toplevel_data.visible.get() {
self.damage();
}
if let Some(parent) = self.toplevel_data.parent.get() {