sctk_adwaita/
lib.rs

1#![deny(
2    clippy::print_stderr,
3    clippy::print_stdout,
4    clippy::dbg_macro,
5    clippy::exit,
6    clippy::unwrap_used,
7    clippy::expect_used,
8    clippy::panic,
9    clippy::indexing_slicing,
10    clippy::string_slice
11)]
12
13use std::error::Error;
14use std::mem;
15use std::num::NonZeroU32;
16use std::sync::Arc;
17use std::time::Duration;
18
19use tiny_skia::{
20    Color, FillRule, Mask, Path, PathBuilder, Pixmap, PixmapMut, PixmapPaint, Point, Rect,
21    Transform,
22};
23
24use smithay_client_toolkit::reexports::client::backend::ObjectId;
25use smithay_client_toolkit::reexports::client::protocol::wl_shm;
26use smithay_client_toolkit::reexports::client::protocol::wl_subsurface::WlSubsurface;
27use smithay_client_toolkit::reexports::client::protocol::wl_surface::WlSurface;
28use smithay_client_toolkit::reexports::client::{Dispatch, Proxy, QueueHandle};
29use smithay_client_toolkit::reexports::csd_frame::{
30    CursorIcon, DecorationsFrame, FrameAction, FrameClick, WindowManagerCapabilities, WindowState,
31};
32
33use smithay_client_toolkit::compositor::{CompositorState, Region, SurfaceData};
34use smithay_client_toolkit::shell::WaylandSurface;
35use smithay_client_toolkit::shm::{slot::SlotPool, Shm};
36use smithay_client_toolkit::subcompositor::SubcompositorState;
37use smithay_client_toolkit::subcompositor::SubsurfaceData;
38
39mod buttons;
40mod config;
41mod parts;
42mod pointer;
43mod shadow;
44pub mod theme;
45mod title;
46mod wl_typed;
47
48use crate::theme::{
49    ColorMap, ColorTheme, BORDER_SIZE, CORNER_RADIUS, HEADER_SIZE, RESIZE_HANDLE_CORNER_SIZE,
50    VISIBLE_BORDER_SIZE,
51};
52
53use buttons::Buttons;
54use config::get_button_layout_config;
55use parts::DecorationParts;
56use pointer::{Location, MouseState};
57use shadow::Shadow;
58use title::TitleText;
59use wl_typed::WlTyped;
60
61/// XXX this is not result, so `must_use` when needed.
62type SkiaResult = Option<()>;
63
64/// A simple set of decorations
65#[derive(Debug)]
66pub struct AdwaitaFrame<State> {
67    /// The base surface used to create the window.
68    base_surface: WlTyped<WlSurface, SurfaceData>,
69
70    compositor: Arc<CompositorState>,
71
72    /// Subcompositor to create/drop subsurfaces ondemand.
73    subcompositor: Arc<SubcompositorState>,
74
75    /// Queue handle to perform object creation.
76    queue_handle: QueueHandle<State>,
77
78    /// The drawable decorations, `None` when hidden.
79    decorations: Option<DecorationParts>,
80
81    /// Memory pool to allocate the buffers for the decorations.
82    pool: SlotPool,
83
84    /// Whether the frame should be redrawn.
85    dirty: bool,
86
87    /// Whether the drawing should be synced with the main surface.
88    should_sync: bool,
89
90    /// Scale factor used for the surface.
91    scale_factor: u32,
92
93    /// Wether the frame is resizable.
94    resizable: bool,
95
96    buttons: Buttons,
97    state: WindowState,
98    wm_capabilities: WindowManagerCapabilities,
99    mouse: MouseState,
100    theme: ColorTheme,
101    title: Option<String>,
102    title_text: Option<TitleText>,
103    shadow: Shadow,
104
105    /// Draw decorations but without the titlebar
106    hide_titlebar: bool,
107
108    width: NonZeroU32,
109    height: NonZeroU32,
110}
111
112impl<State> AdwaitaFrame<State>
113where
114    State: Dispatch<WlSurface, SurfaceData> + Dispatch<WlSubsurface, SubsurfaceData> + 'static,
115{
116    pub fn new(
117        base_surface: &impl WaylandSurface,
118        shm: &Shm,
119        compositor: Arc<CompositorState>,
120        subcompositor: Arc<SubcompositorState>,
121        queue_handle: QueueHandle<State>,
122        frame_config: FrameConfig,
123    ) -> Result<Self, Box<dyn Error>> {
124        let base_surface = WlTyped::wrap::<State>(base_surface.wl_surface().clone());
125
126        let pool = SlotPool::new(1, shm)?;
127
128        let decorations = Some(DecorationParts::new(
129            &base_surface,
130            &subcompositor,
131            &queue_handle,
132            frame_config.hide_titlebar,
133        ));
134
135        let theme = frame_config.theme;
136
137        Ok(AdwaitaFrame {
138            base_surface,
139            decorations,
140            pool,
141            compositor,
142            subcompositor,
143            queue_handle,
144            dirty: true,
145            scale_factor: 1,
146            should_sync: true,
147            title: None,
148            title_text: TitleText::new(theme.active.font_color),
149            theme,
150            buttons: Buttons::new(get_button_layout_config()),
151            mouse: Default::default(),
152            state: WindowState::empty(),
153            wm_capabilities: WindowManagerCapabilities::all(),
154            resizable: true,
155            shadow: Shadow::default(),
156            hide_titlebar: frame_config.hide_titlebar,
157            width: NonZeroU32::MIN,
158            height: NonZeroU32::MIN,
159        })
160    }
161
162    /// Update the current frame config.
163    pub fn set_config(&mut self, config: FrameConfig) {
164        self.theme = config.theme;
165        self.dirty = true;
166
167        if self.hide_titlebar != config.hide_titlebar {
168            self.hide_titlebar = config.hide_titlebar;
169            let mut decorations = DecorationParts::new(
170                &self.base_surface,
171                &self.subcompositor,
172                &self.queue_handle,
173                self.hide_titlebar,
174            );
175            decorations.resize(self.width.get(), self.height.get());
176            self.decorations = Some(decorations);
177        }
178    }
179
180    fn precise_location(
181        &self,
182        location: Location,
183        decoration: &DecorationParts,
184        x: f64,
185        y: f64,
186    ) -> Location {
187        let header_width = decoration.header().surface_rect.width;
188        let side_height = decoration.side_height();
189
190        let left_corner_x = BORDER_SIZE + RESIZE_HANDLE_CORNER_SIZE;
191        let right_corner_x = (header_width + BORDER_SIZE).saturating_sub(RESIZE_HANDLE_CORNER_SIZE);
192        let top_corner_y = RESIZE_HANDLE_CORNER_SIZE;
193        let bottom_corner_y = side_height.saturating_sub(RESIZE_HANDLE_CORNER_SIZE);
194        match location {
195            Location::Head | Location::Button(_) => self.buttons.find_button(x, y),
196            Location::Top | Location::TopLeft | Location::TopRight => {
197                if x <= f64::from(left_corner_x) {
198                    Location::TopLeft
199                } else if x >= f64::from(right_corner_x) {
200                    Location::TopRight
201                } else {
202                    Location::Top
203                }
204            }
205            Location::Bottom | Location::BottomLeft | Location::BottomRight => {
206                if x <= f64::from(left_corner_x) {
207                    Location::BottomLeft
208                } else if x >= f64::from(right_corner_x) {
209                    Location::BottomRight
210                } else {
211                    Location::Bottom
212                }
213            }
214            Location::Left => {
215                if y <= f64::from(top_corner_y) {
216                    Location::TopLeft
217                } else if y >= f64::from(bottom_corner_y) {
218                    Location::BottomLeft
219                } else {
220                    Location::Left
221                }
222            }
223            Location::Right => {
224                if y <= f64::from(top_corner_y) {
225                    Location::TopRight
226                } else if y >= f64::from(bottom_corner_y) {
227                    Location::BottomRight
228                } else {
229                    Location::Right
230                }
231            }
232            other => other,
233        }
234    }
235
236    fn redraw_inner(&mut self) -> Option<bool> {
237        let decorations = self.decorations.as_mut()?;
238
239        // Reset the dirty bit.
240        self.dirty = false;
241        let should_sync = mem::take(&mut self.should_sync);
242
243        // Don't draw borders if the frame explicitly hidden or fullscreened.
244        if self.state.contains(WindowState::FULLSCREEN) {
245            decorations.hide();
246            return Some(true);
247        } else {
248            decorations.show();
249        }
250
251        if self.hide_titlebar {
252            decorations.hide_titlebar();
253        }
254
255        let colors = if self.state.contains(WindowState::ACTIVATED) {
256            &self.theme.active
257        } else {
258            &self.theme.inactive
259        };
260
261        let draw_borders = if self.state.contains(WindowState::MAXIMIZED) {
262            // Don't draw the borders.
263            decorations.hide_borders();
264            false
265        } else {
266            true
267        };
268        let border_paint = colors.border_paint();
269
270        // Draw the borders.
271        for (idx, part) in decorations.parts().filter(|(_, part)| !part.hide) {
272            let scale = self.scale_factor;
273
274            let mut rect = part.surface_rect;
275            // XXX to perfectly align the visible borders we draw them with
276            // the header, otherwise rounded corners won't look 'smooth' at the
277            // start. To achieve that, we enlargen the width of the header by
278            // 2 * `VISIBLE_BORDER_SIZE`, and move `x` by `VISIBLE_BORDER_SIZE`
279            // to the left.
280            if idx == DecorationParts::HEADER && draw_borders {
281                rect.width += 2 * VISIBLE_BORDER_SIZE;
282                rect.x -= VISIBLE_BORDER_SIZE as i32;
283            }
284
285            rect.width *= scale;
286            rect.height *= scale;
287
288            let (buffer, canvas) = match self.pool.create_buffer(
289                rect.width as i32,
290                rect.height as i32,
291                rect.width as i32 * 4,
292                wl_shm::Format::Argb8888,
293            ) {
294                Ok((buffer, canvas)) => (buffer, canvas),
295                Err(_) => continue,
296            };
297
298            // Create the pixmap and fill with transparent color.
299            let mut pixmap = PixmapMut::from_bytes(canvas, rect.width, rect.height)?;
300
301            // Fill everything with transparent background, since we draw rounded corners and
302            // do invisible borders to enlarge the input zone.
303            pixmap.fill(Color::TRANSPARENT);
304
305            if !self.state.intersects(WindowState::TILED) {
306                self.shadow.draw(
307                    &mut pixmap,
308                    scale,
309                    self.state.contains(WindowState::ACTIVATED),
310                    idx,
311                );
312            }
313
314            match idx {
315                DecorationParts::HEADER => {
316                    if let Some(title_text) = self.title_text.as_mut() {
317                        title_text.update_scale(scale);
318                        title_text.update_color(colors.font_color);
319                    }
320
321                    draw_headerbar(
322                        &mut pixmap,
323                        self.title_text.as_ref().map(|t| t.pixmap()).unwrap_or(None),
324                        scale as f32,
325                        self.resizable,
326                        &self.state,
327                        &self.theme,
328                        &self.buttons,
329                        self.mouse.location,
330                    );
331                }
332                border => {
333                    // The visible border is one pt.
334                    let visible_border_size = VISIBLE_BORDER_SIZE * scale;
335
336                    // XXX we do all the match using integral types and then convert to f32 in the
337                    // end to ensure that result is finite.
338                    let border_rect = match border {
339                        DecorationParts::LEFT => {
340                            let x = (rect.x.unsigned_abs() * scale) - visible_border_size;
341                            let y = rect.y.unsigned_abs() * scale;
342                            Rect::from_xywh(
343                                x as f32,
344                                y as f32,
345                                visible_border_size as f32,
346                                (rect.height - y) as f32,
347                            )
348                        }
349                        DecorationParts::RIGHT => {
350                            let y = rect.y.unsigned_abs() * scale;
351                            Rect::from_xywh(
352                                0.,
353                                y as f32,
354                                visible_border_size as f32,
355                                (rect.height - y) as f32,
356                            )
357                        }
358                        // We draw small visible border only bellow the window surface, no need to
359                        // handle `TOP`.
360                        DecorationParts::BOTTOM => {
361                            let x = (rect.x.unsigned_abs() * scale) - visible_border_size;
362                            Rect::from_xywh(
363                                x as f32,
364                                0.,
365                                (rect.width - 2 * x) as f32,
366                                visible_border_size as f32,
367                            )
368                        }
369                        // Unless titlebar is disabled
370                        DecorationParts::TOP if self.hide_titlebar => {
371                            let x = rect.x.unsigned_abs() * scale;
372                            let x = x.saturating_sub(visible_border_size);
373
374                            let y = rect.y.unsigned_abs() * scale;
375                            let y = y.saturating_sub(visible_border_size);
376
377                            Rect::from_xywh(
378                                x as f32,
379                                y as f32,
380                                (rect.width - 2 * x) as f32,
381                                visible_border_size as f32,
382                            )
383                        }
384                        _ => None,
385                    };
386
387                    // Fill the visible border, if present.
388                    if let Some(border_rect) = border_rect {
389                        pixmap.fill_rect(border_rect, &border_paint, Transform::identity(), None);
390                    }
391                }
392            };
393
394            if should_sync {
395                part.subsurface.set_sync();
396            } else {
397                part.subsurface.set_desync();
398            }
399
400            part.surface.set_buffer_scale(scale as i32);
401
402            part.subsurface.set_position(rect.x, rect.y);
403            buffer.attach_to(&part.surface).ok()?;
404
405            if part.surface.version() >= 4 {
406                part.surface.damage_buffer(0, 0, i32::MAX, i32::MAX);
407            } else {
408                part.surface.damage(0, 0, i32::MAX, i32::MAX);
409            }
410
411            if let Some(input_rect) = part.input_rect {
412                let input_region = Region::new(&*self.compositor).ok()?;
413                input_region.add(
414                    input_rect.x,
415                    input_rect.y,
416                    input_rect.width as i32,
417                    input_rect.height as i32,
418                );
419
420                part.surface
421                    .set_input_region(Some(input_region.wl_region()));
422            }
423
424            part.surface.commit();
425        }
426
427        Some(should_sync)
428    }
429}
430
431impl<State> DecorationsFrame for AdwaitaFrame<State>
432where
433    State: Dispatch<WlSurface, SurfaceData> + Dispatch<WlSubsurface, SubsurfaceData> + 'static,
434{
435    fn update_state(&mut self, state: WindowState) {
436        let difference = self.state.symmetric_difference(state);
437        self.state = state;
438        self.dirty |= difference.intersects(
439            WindowState::ACTIVATED
440                | WindowState::FULLSCREEN
441                | WindowState::MAXIMIZED
442                | WindowState::TILED,
443        );
444    }
445
446    fn update_wm_capabilities(&mut self, wm_capabilities: WindowManagerCapabilities) {
447        self.dirty |= self.wm_capabilities != wm_capabilities;
448        self.wm_capabilities = wm_capabilities;
449        self.buttons.update_wm_capabilities(wm_capabilities);
450    }
451
452    fn set_hidden(&mut self, hidden: bool) {
453        if hidden {
454            self.dirty = false;
455            let _ = self.pool.resize(1);
456            self.decorations = None;
457        } else if self.decorations.is_none() {
458            self.decorations = Some(DecorationParts::new(
459                &self.base_surface,
460                &self.subcompositor,
461                &self.queue_handle,
462                self.hide_titlebar,
463            ));
464            self.dirty = true;
465            self.should_sync = true;
466        }
467    }
468
469    fn set_resizable(&mut self, resizable: bool) {
470        self.dirty |= self.resizable != resizable;
471        self.resizable = resizable;
472    }
473
474    fn resize(&mut self, width: NonZeroU32, height: NonZeroU32) {
475        self.width = width;
476        self.height = height;
477
478        let Some(decorations) = self.decorations.as_mut() else {
479            log::error!("trying to resize the hidden frame.");
480            return;
481        };
482
483        decorations.resize(width.get(), height.get());
484        self.buttons
485            .arrange(width.get(), get_margin_h_lp(&self.state));
486        self.dirty = true;
487        self.should_sync = true;
488    }
489
490    fn draw(&mut self) -> bool {
491        self.redraw_inner().unwrap_or(true)
492    }
493
494    fn subtract_borders(
495        &self,
496        width: NonZeroU32,
497        height: NonZeroU32,
498    ) -> (Option<NonZeroU32>, Option<NonZeroU32>) {
499        if self.decorations.is_none()
500            || self.state.contains(WindowState::FULLSCREEN)
501            || self.hide_titlebar
502        {
503            (Some(width), Some(height))
504        } else {
505            (
506                Some(width),
507                NonZeroU32::new(height.get().saturating_sub(HEADER_SIZE)),
508            )
509        }
510    }
511
512    fn add_borders(&self, width: u32, height: u32) -> (u32, u32) {
513        if self.decorations.is_none()
514            || self.state.contains(WindowState::FULLSCREEN)
515            || self.hide_titlebar
516        {
517            (width, height)
518        } else {
519            (width, height + HEADER_SIZE)
520        }
521    }
522
523    fn location(&self) -> (i32, i32) {
524        if self.decorations.is_none()
525            || self.state.contains(WindowState::FULLSCREEN)
526            || self.hide_titlebar
527        {
528            (0, 0)
529        } else {
530            (0, -(HEADER_SIZE as i32))
531        }
532    }
533
534    fn set_title(&mut self, title: impl Into<String>) {
535        let new_title = title.into();
536        if let Some(title_text) = self.title_text.as_mut() {
537            title_text.update_title(new_title.clone());
538        }
539
540        self.title = Some(new_title);
541        self.dirty = true;
542    }
543
544    fn on_click(
545        &mut self,
546        timestamp: Duration,
547        click: FrameClick,
548        pressed: bool,
549    ) -> Option<FrameAction> {
550        match click {
551            FrameClick::Normal => self.mouse.click(
552                timestamp,
553                pressed,
554                self.resizable,
555                &self.state,
556                &self.wm_capabilities,
557            ),
558            FrameClick::Alternate => self.mouse.alternate_click(pressed, &self.wm_capabilities),
559            _ => None,
560        }
561    }
562
563    fn set_scaling_factor(&mut self, scale_factor: f64) {
564        // NOTE: Clamp it just in case to some ok-ish range.
565        self.scale_factor = scale_factor.clamp(0.1, 64.).ceil() as u32;
566        self.dirty = true;
567        self.should_sync = true;
568    }
569
570    fn click_point_moved(
571        &mut self,
572        _timestamp: Duration,
573        surface: &ObjectId,
574        x: f64,
575        y: f64,
576    ) -> Option<CursorIcon> {
577        let decorations = self.decorations.as_ref()?;
578        let location = decorations.find_surface(surface);
579        if location == Location::None {
580            return None;
581        }
582
583        let old_location = self.mouse.location;
584
585        let location = self.precise_location(location, decorations, x, y);
586        let new_cursor = self.mouse.moved(location, x, y, self.resizable);
587
588        // Set dirty if we moved the cursor between the buttons.
589        self.dirty |= (matches!(old_location, Location::Button(_))
590            || matches!(self.mouse.location, Location::Button(_)))
591            && old_location != self.mouse.location;
592
593        Some(new_cursor)
594    }
595
596    fn click_point_left(&mut self) {
597        self.mouse.left()
598    }
599
600    fn is_dirty(&self) -> bool {
601        self.dirty
602    }
603
604    fn is_hidden(&self) -> bool {
605        self.decorations.is_none()
606    }
607}
608
609/// The configuration for the [`AdwaitaFrame`] frame.
610#[derive(Debug, Clone)]
611pub struct FrameConfig {
612    pub theme: ColorTheme,
613    /// Draw decorations but without the titlebar
614    pub hide_titlebar: bool,
615}
616
617impl FrameConfig {
618    /// Create the new configuration with the given `theme`.
619    pub fn new(theme: ColorTheme) -> Self {
620        Self {
621            theme,
622            hide_titlebar: false,
623        }
624    }
625
626    /// This is equivalent of calling `FrameConfig::new(ColorTheme::auto())`.
627    ///
628    /// For details see [`ColorTheme::auto`].
629    pub fn auto() -> Self {
630        Self::new(ColorTheme::auto())
631    }
632
633    /// This is equivalent of calling `FrameConfig::new(ColorTheme::light())`.
634    ///
635    /// For details see [`ColorTheme::light`].
636    pub fn light() -> Self {
637        Self::new(ColorTheme::light())
638    }
639
640    /// This is equivalent of calling `FrameConfig::new(ColorTheme::dark())`.
641    ///
642    /// For details see [`ColorTheme::dark`].
643    pub fn dark() -> Self {
644        Self::new(ColorTheme::dark())
645    }
646
647    /// Draw decorations but without the titlebar
648    pub fn hide_titlebar(mut self, hide: bool) -> Self {
649        self.hide_titlebar = hide;
650        self
651    }
652}
653
654#[allow(clippy::too_many_arguments)]
655fn draw_headerbar(
656    pixmap: &mut PixmapMut,
657    text_pixmap: Option<&Pixmap>,
658    scale: f32,
659    resizable: bool,
660    state: &WindowState,
661    theme: &ColorTheme,
662    buttons: &Buttons,
663    mouse: Location,
664) {
665    let colors = theme.for_state(state.contains(WindowState::ACTIVATED));
666
667    let _ = draw_headerbar_bg(pixmap, scale, colors, state);
668
669    // Horizontal margin.
670    let margin_h = get_margin_h_lp(state) * 2.0;
671
672    let canvas_w = pixmap.width() as f32;
673    let canvas_h = pixmap.height() as f32;
674
675    let header_w = canvas_w - margin_h * 2.0;
676    let header_h = canvas_h;
677
678    if let Some(text_pixmap) = text_pixmap {
679        const TEXT_OFFSET: f32 = 10.;
680        let offset_x = TEXT_OFFSET * scale;
681
682        let text_w = text_pixmap.width() as f32;
683        let text_h = text_pixmap.height() as f32;
684
685        let x = margin_h + header_w / 2. - text_w / 2.;
686        let y = header_h / 2. - text_h / 2.;
687
688        let left_buttons_end_x = buttons.left_buttons_end_x().unwrap_or(0.0) * scale;
689        let right_buttons_start_x =
690            buttons.right_buttons_start_x().unwrap_or(header_w / scale) * scale;
691
692        {
693            // We have enough space to center text
694            let (x, y, text_canvas_start_x) = if (x + text_w < right_buttons_start_x - offset_x)
695                && (x > left_buttons_end_x + offset_x)
696            {
697                let text_canvas_start_x = x;
698
699                (x, y, text_canvas_start_x)
700            } else {
701                let x = left_buttons_end_x + offset_x;
702                let text_canvas_start_x = left_buttons_end_x + offset_x;
703
704                (x, y, text_canvas_start_x)
705            };
706
707            let text_canvas_end_x = right_buttons_start_x - x - offset_x;
708            // Ensure that text start within the bounds.
709            let x = x.max(margin_h + offset_x);
710
711            if let Some(clip) =
712                Rect::from_xywh(text_canvas_start_x, 0., text_canvas_end_x, canvas_h)
713            {
714                if let Some(mut mask) = Mask::new(canvas_w as u32, canvas_h as u32) {
715                    mask.fill_path(
716                        &PathBuilder::from_rect(clip),
717                        FillRule::Winding,
718                        false,
719                        Transform::identity(),
720                    );
721                    pixmap.draw_pixmap(
722                        x.round() as i32,
723                        y as i32,
724                        text_pixmap.as_ref(),
725                        &PixmapPaint::default(),
726                        Transform::identity(),
727                        Some(&mask),
728                    );
729                } else {
730                    log::error!(
731                        "Invalid mask width and height: w: {}, h: {}",
732                        canvas_w as u32,
733                        canvas_h as u32
734                    );
735                }
736            }
737        }
738    }
739
740    // Draw the buttons.
741    buttons.draw(
742        margin_h, header_w, scale, colors, mouse, pixmap, resizable, state,
743    );
744}
745
746#[must_use]
747fn draw_headerbar_bg(
748    pixmap: &mut PixmapMut,
749    scale: f32,
750    colors: &ColorMap,
751    state: &WindowState,
752) -> SkiaResult {
753    let w = pixmap.width() as f32;
754    let h = pixmap.height() as f32;
755
756    let radius = if state.intersects(WindowState::MAXIMIZED | WindowState::TILED) {
757        0.
758    } else {
759        CORNER_RADIUS as f32 * scale
760    };
761
762    let bg = rounded_headerbar_shape(0., 0., w, h, radius)?;
763
764    pixmap.fill_path(
765        &bg,
766        &colors.headerbar_paint(),
767        FillRule::Winding,
768        Transform::identity(),
769        None,
770    );
771
772    pixmap.fill_rect(
773        Rect::from_xywh(0., h - 1., w, h)?,
774        &colors.border_paint(),
775        Transform::identity(),
776        None,
777    );
778
779    Some(())
780}
781
782fn rounded_headerbar_shape(x: f32, y: f32, width: f32, height: f32, radius: f32) -> Option<Path> {
783    // https://stackoverflow.com/a/27863181
784    let cubic_bezier_circle = 0.552_284_8 * radius;
785
786    let mut pb = PathBuilder::new();
787    let mut cursor = Point::from_xy(x, y);
788
789    // !!!
790    // This code is heavily "inspired" by https://gitlab.com/snakedye/snui/
791    // So technically it should be licensed under MPL-2.0, sorry about that 🥺 👉👈
792    // !!!
793
794    // Positioning the cursor
795    cursor.y += radius;
796    pb.move_to(cursor.x, cursor.y);
797
798    // Drawing the outline
799    let next = Point::from_xy(cursor.x + radius, cursor.y - radius);
800    pb.cubic_to(
801        cursor.x,
802        cursor.y - cubic_bezier_circle,
803        next.x - cubic_bezier_circle,
804        next.y,
805        next.x,
806        next.y,
807    );
808    cursor = next;
809    pb.line_to(
810        {
811            cursor.x = x + width - radius;
812            cursor.x
813        },
814        cursor.y,
815    );
816    let next = Point::from_xy(cursor.x + radius, cursor.y + radius);
817    pb.cubic_to(
818        cursor.x + cubic_bezier_circle,
819        cursor.y,
820        next.x,
821        next.y - cubic_bezier_circle,
822        next.x,
823        next.y,
824    );
825    cursor = next;
826    pb.line_to(cursor.x, {
827        cursor.y = y + height;
828        cursor.y
829    });
830    pb.line_to(
831        {
832            cursor.x = x;
833            cursor.x
834        },
835        cursor.y,
836    );
837
838    pb.close();
839
840    pb.finish()
841}
842
843// returns horizontal margin, logical points
844fn get_margin_h_lp(state: &WindowState) -> f32 {
845    if state.intersects(WindowState::MAXIMIZED | WindowState::TILED) {
846        0.
847    } else {
848        VISIBLE_BORDER_SIZE as f32
849    }
850}