Add hooks for custom window decorations

This is a first use of the new hooks system, which allows customizing
cosmic-comp at compile-time.
In this case, the view() function of CosmicWindow / CosmicStack is
hooked and the hook can change what is rendered as the header bar.

Signed-off-by: Yureka <yuka@yuka.dev>
This commit is contained in:
Yureka 2025-09-29 17:34:30 +02:00 committed by Victoria Brekenfeld
parent d6e11de1f1
commit a74b6e3a9b
5 changed files with 119 additions and 46 deletions

View file

@ -4,6 +4,7 @@ use super::{
};
use crate::{
backend::render::cursor::CursorState,
hooks::{Decorations, HOOKS},
shell::{
focus::target::PointerFocusTarget,
grabs::{ReleaseMode, ResizeEdge},
@ -1007,12 +1008,57 @@ impl Program for CosmicStackInternal {
}
fn view(&self) -> CosmicElement<'_, Self::Message> {
let windows = self.windows.lock().unwrap();
if self.geometry.lock().unwrap().is_none() {
HOOKS.get().unwrap().stack_decorations.view(self)
}
fn foreground(
&self,
pixels: &mut tiny_skia::PixmapMut<'_>,
damage: &[Rectangle<i32, Buffer>],
scale: f32,
theme: &Theme,
) {
if self.group_focused.load(Ordering::SeqCst) {
let border = Rectangle::new(
(0, ((TAB_HEIGHT as f32 * scale) - scale).floor() as i32).into(),
(pixels.width() as i32, scale.ceil() as i32).into(),
);
let mut paint = tiny_skia::Paint::default();
let (b, g, r, a) = theme.cosmic().accent_color().into_components();
paint.set_color(tiny_skia::Color::from_rgba(r, g, b, a).unwrap());
for rect in damage {
if let Some(overlap) = rect.intersection(border) {
pixels.fill_rect(
tiny_skia::Rect::from_xywh(
overlap.loc.x as f32,
overlap.loc.y as f32,
overlap.size.w as f32,
overlap.size.h as f32,
)
.unwrap(),
&paint,
Default::default(),
None,
)
}
}
}
}
}
#[derive(Debug)]
pub struct DefaultDecorations;
impl Decorations<CosmicStackInternal, Message> for DefaultDecorations {
fn view(&self, stack: &CosmicStackInternal) -> cosmic::Element<'_, Message> {
let windows = stack.windows.lock().unwrap();
if stack.geometry.lock().unwrap().is_none() {
return iced_widget::row(Vec::new()).into();
};
let active = self.active.load(Ordering::SeqCst);
let group_focused = self.group_focused.load(Ordering::SeqCst);
let active = stack.active.load(Ordering::SeqCst);
let group_focused = stack.group_focused.load(Ordering::SeqCst);
let elements = vec![
cosmic_widget::icon::from_name("window-stack-symbolic")
@ -1057,7 +1103,8 @@ impl Program for CosmicStackInternal {
)
.id(SCROLLABLE_ID.clone())
.force_visible(
self.scroll_to_focus
stack
.scroll_to_focus
.load(Ordering::SeqCst)
.then_some(active),
)
@ -1079,7 +1126,7 @@ impl Program for CosmicStackInternal {
} else {
Radius::from([8.0, 8.0, 0.0, 0.0])
};
let group_focused = self.group_focused.load(Ordering::SeqCst);
let group_focused = stack.group_focused.load(Ordering::SeqCst);
iced_widget::row(elements)
.height(TAB_HEIGHT as u16)
@ -1109,42 +1156,6 @@ impl Program for CosmicStackInternal {
}))
.into()
}
fn foreground(
&self,
pixels: &mut tiny_skia::PixmapMut<'_>,
damage: &[Rectangle<i32, Buffer>],
scale: f32,
theme: &Theme,
) {
if self.group_focused.load(Ordering::SeqCst) {
let border = Rectangle::new(
(0, ((TAB_HEIGHT as f32 * scale) - scale).floor() as i32).into(),
(pixels.width() as i32, scale.ceil() as i32).into(),
);
let mut paint = tiny_skia::Paint::default();
let (b, g, r, a) = theme.cosmic().accent_color().into_components();
paint.set_color(tiny_skia::Color::from_rgba(r, g, b, a).unwrap());
for rect in damage {
if let Some(overlap) = rect.intersection(border) {
pixels.fill_rect(
tiny_skia::Rect::from_xywh(
overlap.loc.x as f32,
overlap.loc.y as f32,
overlap.size.w as f32,
overlap.size.h as f32,
)
.unwrap(),
&paint,
Default::default(),
None,
)
}
}
}
}
}
impl IsAlive for CosmicStack {

View file

@ -1,5 +1,6 @@
use crate::{
backend::render::cursor::CursorState,
hooks::{Decorations, HOOKS},
shell::{
focus::target::PointerFocusTarget,
grabs::{ReleaseMode, ResizeEdge},
@ -554,11 +555,20 @@ impl Program for CosmicWindowInternal {
}
fn view(&self) -> cosmic::Element<'_, Self::Message> {
HOOKS.get().unwrap().window_decorations.view(self)
}
}
#[derive(Debug)]
pub struct DefaultDecorations;
impl Decorations<CosmicWindowInternal, Message> for DefaultDecorations {
fn view(&self, win: &CosmicWindowInternal) -> cosmic::Element<'_, Message> {
let mut header = cosmic::widget::header_bar()
.title(self.last_title.lock().unwrap().clone())
.title(win.last_title.lock().unwrap().clone())
.on_drag(Message::DragStart)
.on_close(Message::Close)
.focused(self.window.is_activated(false))
.focused(win.window.is_activated(false))
.on_double_click(Message::Maximize)
.on_right_click(Message::Menu)
.is_ssd(true);