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:
parent
d6e11de1f1
commit
a74b6e3a9b
5 changed files with 119 additions and 46 deletions
47
src/hooks.rs
Normal file
47
src/hooks.rs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
use crate::shell::element::stack::{
|
||||
CosmicStackInternal, DefaultDecorations as DefaultStackDecorations, Message as StackMessage,
|
||||
};
|
||||
use crate::shell::element::window::{
|
||||
CosmicWindowInternal, DefaultDecorations as DefaultWindowDecorations, Message as WindowMessage,
|
||||
};
|
||||
use std::sync::{Arc, OnceLock};
|
||||
|
||||
/// An _unstable_ interface to customize cosmic-comp at compile-time by providing
|
||||
/// hooks to be run in specific code paths.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct Hooks {
|
||||
pub window_decorations:
|
||||
Option<Arc<dyn Decorations<CosmicWindowInternal, WindowMessage> + Send + Sync>>,
|
||||
pub stack_decorations:
|
||||
Option<Arc<dyn Decorations<CosmicStackInternal, StackMessage> + Send + Sync>>,
|
||||
}
|
||||
|
||||
pub static HOOKS: OnceLock<Hooks> = OnceLock::new();
|
||||
|
||||
pub trait Decorations<Internal, Message>: std::fmt::Debug {
|
||||
fn view(&self, state: &Internal) -> cosmic::Element<'_, Message>;
|
||||
}
|
||||
|
||||
impl Decorations<CosmicWindowInternal, WindowMessage>
|
||||
for Option<Arc<dyn Decorations<CosmicWindowInternal, WindowMessage> + Send + Sync>>
|
||||
{
|
||||
fn view(&self, window: &CosmicWindowInternal) -> cosmic::Element<'_, WindowMessage> {
|
||||
match self {
|
||||
None => DefaultWindowDecorations.view(window),
|
||||
Some(deco) => deco.view(window),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Decorations<CosmicStackInternal, StackMessage>
|
||||
for Option<Arc<dyn Decorations<CosmicStackInternal, StackMessage> + Send + Sync>>
|
||||
{
|
||||
fn view(&self, window: &CosmicStackInternal) -> cosmic::Element<'_, StackMessage> {
|
||||
match self {
|
||||
None => DefaultStackDecorations.view(window),
|
||||
Some(deco) => deco.view(window),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,6 +33,7 @@ pub mod config;
|
|||
pub mod dbus;
|
||||
#[cfg(feature = "debug")]
|
||||
pub mod debug;
|
||||
pub mod hooks;
|
||||
pub mod input;
|
||||
mod logger;
|
||||
pub mod session;
|
||||
|
|
@ -103,7 +104,7 @@ impl State {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn run() -> Result<(), Box<dyn Error>> {
|
||||
pub fn run(hooks: crate::hooks::Hooks) -> Result<(), Box<dyn Error>> {
|
||||
let raw_args = RawArgs::from_args();
|
||||
let mut cursor = raw_args.cursor();
|
||||
let git_hash = option_env!("GIT_HASH").unwrap_or("unknown");
|
||||
|
|
@ -137,6 +138,10 @@ pub fn run() -> Result<(), Box<dyn Error>> {
|
|||
|
||||
utils::rlimit::increase_nofile_limit();
|
||||
|
||||
// init hook globals
|
||||
hooks::HOOKS.set(hooks)
|
||||
.expect("Hooks global has already been initialized. Running multiple instances of COSMIC in one process is not supported.");
|
||||
|
||||
// init event loop
|
||||
let mut event_loop = EventLoop::try_new().with_context(|| "Failed to initialize event loop")?;
|
||||
// init wayland
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
fn main() {
|
||||
if let Err(err) = cosmic_comp::run() {
|
||||
if let Err(err) = cosmic_comp::run(Default::default()) {
|
||||
tracing::error!("Error occured in main(): {}", err);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue