authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 02:39:48-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-10-02 03:16:54-07:00
log3dade15eef573afab71c86c6ae88e452c7da8176
treefec73d566ff894d0a4e799983463c79bbcbc41e6
parentba16a18feec9b6b0d5b03464b1e8cda9ded4a81e
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: Linux window controls match libadwaita on GNOME and follow the button layout

On GNOME the title bar row drew a thin stroked X on a 22 px disc of the app text colour over sctk-adwaita's GTK 3 header grey. It now draws libadwaita's window controls: 34 px buttons 3 px apart, a 24 px disc (20 px before 1.8) at 10/15/30 % of the header bar ink at rest, hovered and pressed, Adwaita's window-*-symbolic glyphs, the whole set at half opacity in the background, over libadwaita's header colours. The libadwaita release follows the running GNOME Shell (1.N with GNOME 41+N). The buttons sit at the start or end of the row as button-layout places them, re-read when the settings portal reports a change. Desktops other than GNOME that leave decorations to the client get the toolbar's own buttons rather than an imitation of Adwaita. Assisted-by: claude-opus-5.5

10 files changed, 201 insertions(+), 80 deletions(-)

arc/platforms.md+12-8
...@@ -63,16 +63,20 @@ keyboard, the toolbar and the macOS menu bar all run commands from it....@@ -63,16 +63,20 @@ keyboard, the toolbar and the macOS menu bar all run commands from it.
63- X11 and Wayland. Title bars come from the window manager, or on Wayland from63- X11 and Wayland. Title bars come from the window manager, or on Wayland from
64 the compositor where it offers server-side decorations, and the toolbar's64 the compositor where it offers server-side decorations, and the toolbar's
65 row lies beneath them, as in Dolphin and Kate. Elsewhere on Wayland, as on65 row lies beneath them, as in Dolphin and Kate. Elsewhere on Wayland, as on
66 GNOME, the row is the title bar, as a GTK 4 header bar holding tools: winit's66 GNOME, the row is the title bar, as a GTK 4 header bar holding tools:
67 Adwaita frame keeps its shadow, corners and resize edges but has no header,67 winit's Adwaita frame keeps its shadow, corners and resize edges but has no
68 and the row ends in the window's buttons in the order GNOME's `button-layout`68 header, and the window's buttons sit at the row's ends as GNOME's
69 lists them. Under that frame on GNOME the window erases its bottom69 `button-layout` places them, kept current through the settings portal. On
70 corners to transparent pixels and the frame draws libadwaita's window edge70 GNOME they are libadwaita's, sized and coloured as the release that came
71 round it: radius, shadows and outline. KWin rounds Breeze's corners itself.71 with the running GNOME Shell draws them; elsewhere they are the toolbar's
72 own buttons, not an imitation of a theme Snowbound can't read. Under that
73 frame on GNOME the window erases its bottom corners to transparent pixels
74 and the frame draws libadwaita's window edge round it: radius, shadows and
75 outline. KWin rounds Breeze's corners itself.
72- The toolbar and the rest of the chrome continue the title bar's fill,76- The toolbar and the rest of the chrome continue the title bar's fill,
73 focused and not: on KDE the colour scheme's header colours from77 focused and not: on KDE the colour scheme's header colours from
74 `kdeglobals`, as KWin paints its title bars, and on GNOME winit's Adwaita78 `kdeglobals`, as KWin paints its title bars, and on GNOME libadwaita's
75 frame's. A settings portal signal re-reads them when the scheme changes.79 header bar's. A settings portal signal re-reads them when the scheme changes.
76- Menus and other popups take the desktop's look and motion: libadwaita's80- Menus and other popups take the desktop's look and motion: libadwaita's
77 popover menus on GNOME, shown and hidden at once as GTK 4 does, and Breeze's81 popover menus on GNOME, shown and hidden at once as GTK 4 does, and Breeze's
78 on KDE, faded as KWin fades popups and scaled by Plasma's animation speed.82 on KDE, faded as KWin fades popups and scaled by Plasma's animation speed.
crates/snowbound/assets/adwaita/window-close.svg created+3
...@@ -0,0 +1,3 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2 <path d="M4 4H5L8 6.6 11 4H12V5L9.4 8 12 11V12H11L8 9.4 5 12H4V11L6.6 8 4 5Z" fill="currentColor"/>
3</svg>
crates/snowbound/assets/adwaita/window-maximize.svg created+3
...@@ -0,0 +1,3 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2 <path d="M4 4H12V12H4ZM6 6V10H10V6Z" fill="currentColor" fill-rule="evenodd"/>
3</svg>
crates/snowbound/assets/adwaita/window-minimize.svg created+3
...@@ -0,0 +1,3 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2 <path d="M4 10H12V12H4Z" fill="currentColor"/>
3</svg>
crates/snowbound/assets/adwaita/window-restore.svg created+3
...@@ -0,0 +1,3 @@
1<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
2 <path d="M5 5H11V11H5ZM7 7V9H9V7Z" fill="currentColor" fill-rule="evenodd"/>
3</svg>
crates/snowbound/src/linux.rs+167-68
...@@ -14,7 +14,7 @@ use std::{...@@ -14,7 +14,7 @@ use std::{
14 path::PathBuf,14 path::PathBuf,
15 process::Command,15 process::Command,
16 sync::{16 sync::{
17 OnceLock,17 Mutex, OnceLock,
18 atomic::{AtomicBool, AtomicU32, Ordering},18 atomic::{AtomicBool, AtomicU32, Ordering},
19 },19 },
20 time::Duration,20 time::Duration,
...@@ -30,10 +30,9 @@ use winit::{...@@ -30,10 +30,9 @@ use winit::{
30};30};
31use zbus::zvariant::{OwnedObjectPath, OwnedValue, Value};31use zbus::zvariant::{OwnedObjectPath, OwnedValue, Value};
3232
33/// The title bar's leading margin; the window controls sit at its trailing end.33/// The title bar's margins at its leading and trailing ends, libadwaita's header bar's.
34pub const LEADING: f32 = 8.0;34pub const LEADING: f32 = 7.0;
35/// The margin past the window controls.35pub const TRAILING: f32 = 7.0;
36pub const TRAILING: f32 = 8.0;
37/// How far inside the window's edges a press resizes it, and how far along them a corner36/// How far inside the window's edges a press resizes it, and how far along them a corner
38/// reaches, in logical pixels.37/// reaches, in logical pixels.
39const EDGE: f32 = 5.0;38const EDGE: f32 = 5.0;
...@@ -41,6 +40,14 @@ const CORNER: f32 = 16.0;...@@ -41,6 +40,14 @@ const CORNER: f32 = 16.0;
41const MINIMIZE: &[&str] = &[include_str!("../assets/icons/window-minimize.svg")];40const MINIMIZE: &[&str] = &[include_str!("../assets/icons/window-minimize.svg")];
42const MAXIMIZE: &[&str] = &[include_str!("../assets/icons/window-maximize.svg")];41const MAXIMIZE: &[&str] = &[include_str!("../assets/icons/window-maximize.svg")];
43const RESTORE: &[&str] = &[include_str!("../assets/icons/window-restore.svg")];42const RESTORE: &[&str] = &[include_str!("../assets/icons/window-restore.svg")];
43/// Adwaita's `window-*-symbolic` glyphs, which GTK's window controls show.
44const ADWAITA_MINIMIZE: &[&str] = &[include_str!("../assets/adwaita/window-minimize.svg")];
45const ADWAITA_MAXIMIZE: &[&str] = &[include_str!("../assets/adwaita/window-maximize.svg")];
46const ADWAITA_RESTORE: &[&str] = &[include_str!("../assets/adwaita/window-restore.svg")];
47const ADWAITA_CLOSE: &[&str] = &[include_str!("../assets/adwaita/window-close.svg")];
48/// GNOME's `button-layout`, as GTK reads it: the window's buttons at the title bar's start,
49/// a colon, then those at its end. Read once, then kept by the settings portal's signal.
50static BUTTON_LAYOUT: Mutex<Option<String>> = Mutex::new(None);
4451
45/// The event loop's, for signals and Snowbound's own dialogs.52/// The event loop's, for signals and Snowbound's own dialogs.
46pub(crate) static PROXY: OnceLock<EventLoopProxy<crate::UserEvent>> = OnceLock::new();53pub(crate) static PROXY: OnceLock<EventLoopProxy<crate::UserEvent>> = OnceLock::new();
...@@ -577,76 +584,152 @@ fn kde_color(value: &str) -> Option<[u8; 3]> {...@@ -577,76 +584,152 @@ fn kde_color(value: &str) -> Option<[u8; 3]> {
577 Some([channels.next()??, channels.next()??, channels.next()??])584 Some([channels.next()??, channels.next()??, channels.next()??])
578}585}
579586
580/// The header bar of winit's Adwaita frame, focused and not.587/// libadwaita's header bar, focused and not: neutral greys before 1.7, bluish ones since.
581fn adwaita_titlebar(appearance: Theme) -> [[u8; 3]; 2] {588fn adwaita_titlebar(appearance: Theme) -> [[u8; 3]; 2] {
582 let theme = match appearance {589 match (appearance, adwaita_minor() >= 7) {
583 Theme::Dark => sctk_adwaita::theme::ColorTheme::dark(),590 (Theme::Light, true) => [[0xff; 3], [0xfa, 0xfa, 0xfb]],
584 Theme::Light => sctk_adwaita::theme::ColorTheme::light(),591 (Theme::Light, false) => [[0xff; 3], [0xfa; 3]],
585 };592 (Theme::Dark, true) => [[0x2e, 0x2e, 0x32], [0x22, 0x22, 0x26]],
586 [theme.active, theme.inactive].map(|colors| {593 (Theme::Dark, false) => [[0x30; 3], [0x24; 3]],
587 let color = colors.headerbar.to_color_u8();594 }
588 [color.red(), color.green(), color.blue()]595}
596
597/// The minor version of the libadwaita GNOME apps use, as the session's GNOME Shell implies:
598/// 1.N came with GNOME 41 + N. The newest where the shell doesn't answer.
599fn adwaita_minor() -> u32 {
600 static MINOR: OnceLock<u32> = OnceLock::new();
601 *MINOR.get_or_init(|| {
602 let shell = || -> Option<String> {
603 let connection = zbus::blocking::connection::Builder::session()
604 .ok()?
605 .method_timeout(Duration::from_millis(500))
606 .build()
607 .ok()?;
608 zbus::blocking::Proxy::new(
609 &connection,
610 "org.gnome.Shell",
611 "/org/gnome/Shell",
612 "org.gnome.Shell",
613 )
614 .ok()?
615 .get_property("ShellVersion")
616 .ok()
617 };
618 shell()
619 .and_then(|version| version.split('.').next()?.parse::<u32>().ok())
620 .map_or(u32::MAX, |major| major.saturating_sub(41))
589 })621 })
590}622}
591623
592/// The window's buttons at the title bar's trailing end, in the order GNOME's624/// The window's buttons at the title bar's start, or its `end`, as the desktop's button
593/// `button-layout` lists them, which is close alone by default; elsewhere minimize,625/// layout places them. On GNOME they are libadwaita's: discs in the header bar's ink, dimmed
594/// maximize and close.626/// with it while the window is in the background. Elsewhere, where the compositor offered no
595pub fn window_controls(ui: &mut Ui, window: &Window) {627/// decorations of its own, they are the toolbar's own buttons.
596 static CONTROLS: OnceLock<Vec<&'static str>> = OnceLock::new();628pub fn window_controls(ui: &mut Ui, window: &Window, end: bool) {
597 let controls = CONTROLS.get_or_init(|| {629 let layout = BUTTON_LAYOUT
598 let layout = portal_setting("org.gnome.desktop.wm.preferences", "button-layout")630 .lock()
599 .and_then(|value| String::try_from(value).ok());631 .unwrap()
600 match layout {632 .get_or_insert_with(|| {
601 Some(layout) => layout633 portal_setting("org.gnome.desktop.wm.preferences", "button-layout")
602 .split([':', ','])634 .and_then(|value| String::try_from(value).ok())
603 .filter_map(|name| {635 .unwrap_or_else(|| {
604 ["minimize", "maximize", "close"]636 if desktop() == Desktop::Gnome {
605 .into_iter()637 "appmenu:close"
606 .find(|known| *known == name)638 } else {
639 ":minimize,maximize,close"
640 }
641 .into()
607 })642 })
608 .collect(),643 })
609 None if desktop() == Desktop::Gnome => vec!["close"],644 .clone();
610 None => vec!["minimize", "maximize", "close"],645 let (start, finish) = layout.split_once(':').unwrap_or((&layout, ""));
611 }646 let controls: Vec<&str> = if end { finish } else { start }
647 .split(',')
648 .map(str::trim)
649 .filter(|name| matches!(*name, "minimize" | "maximize" | "close"))
650 .collect();
651 if controls.is_empty() {
652 return;
653 }
654 let adwaita = desktop() == Desktop::Gnome;
655 // libadwaita's are 34 px buttons 3 px apart.
656 let (button, gap) = if adwaita {
657 (34.0, 3.0)
658 } else {
659 (ui::shell::TOOL, 6.0)
660 };
661 ui.open(
662 ("controls", end),
663 Spec {
664 size: [ui::children(), px(button)],
665 offset: [0.0, (ui::shell::TOOL - button) / 2.0],
666 gap,
667 ..Spec::default()
668 },
669 );
670 // `--headerbar-fg-color` at 10 %, 15 % and 30 % for the disc at rest, hovered and
671 // pressed, and whole for the glyph, over the header bar; at half opacity in the
672 // background.
673 let colors = adwaita.then(|| {
674 // Light text is the dark appearance's.
675 let appearance = if ui.theme.text[0] > 0.5 {
676 Theme::Dark
677 } else {
678 Theme::Light
679 };
680 let header = adwaita_titlebar(appearance)[usize::from(!ui.window_focused)];
681 let (ink, strength) = match appearance {
682 Theme::Dark => ([0xff; 3], 1.0),
683 Theme::Light if adwaita_minor() >= 7 => ([0, 0, 6], 0.8),
684 Theme::Light => ([0; 3], 0.8),
685 };
686 let opacity = if ui.window_focused { 1.0 } else { 0.5 };
687 let disc = |alpha: f32| over(ink, strength * alpha, header);
688 let rest = disc(0.1);
689 [rest, disc(0.15), disc(0.3), over(ink, strength, rest)]
690 .map(|color| linear(over(color, opacity, header)))
612 });691 });
613 let text = ui.theme.text;
614 let disc = |alpha| [text[0], text[1], text[2], alpha];
615 for control in controls {692 for control in controls {
616 let icon = match *control {693 let (name, icons) = match control {
617 "minimize" => MINIMIZE,694 "minimize" => ("Minimize", [MINIMIZE, ADWAITA_MINIMIZE]),
618 "maximize" if window.is_maximized() => RESTORE,695 "maximize" if window.is_maximized() => ("Restore", [RESTORE, ADWAITA_RESTORE]),
619 "maximize" => MAXIMIZE,696 "maximize" => ("Maximize", [MAXIMIZE, ADWAITA_MAXIMIZE]),
620 _ => crate::art::CLOSE,697 _ => ("Close", [crate::art::CLOSE, ADWAITA_CLOSE]),
621 };698 };
622 // Round, on a faint disc, as libadwaita's are.699 let clicked = if let Some([rest, hover, pressed, glyph]) = colors {
623 let clicked = ui700 // The 16 px glyph's disc pads it by 2 px, or by 4 px from 1.8.
624 .leaf(701 let disc = if adwaita_minor() >= 8 { 24.0 } else { 20.0 };
702 let id = ui.id(control);
703 let signal = ui.signal(id);
704 let press = ui.animate(
705 id.child("pressed"),
706 f32::from(u8::from(signal.dragging && signal.hovered)),
707 );
708 ui.leaf(
625 control,709 control,
626 Spec {710 Spec {
627 flags: Flags::CLICKABLE,711 flags: Flags::CLICKABLE,
628 size: [px(ui::shell::TOOL); 2],712 size: [px(button); 2],
629 icon: Some(icon),713 inset: [(button - disc) / 2.0; 4],
630 color: Some(text),714 icon: Some(icons[1]),
631 fill: Some(disc(0.1)),715 color: Some(glyph),
632 hover_fill: Some(disc(0.18)),716 fill: Some(ui::mix(rest, pressed, press)),
633 radius: ui::shell::TOOL / 2.0,717 hover_fill: Some(ui::mix(hover, pressed, press)),
718 radius: disc / 2.0,
634 center: true,719 center: true,
635 role: Some(accesskit::Role::Button),720 role: Some(accesskit::Role::Button),
636 ..Spec::default()721 ..Spec::default()
637 },722 },
638 )723 );
639 .clicked;724 signal.clicked
640 let name = match *control {725 } else {
641 "minimize" => "Minimize",726 let text = ui.theme.text;
642 "maximize" if window.is_maximized() => "Restore",727 ui::shell::tool_button(ui, control, icons[0], text, None).clicked
643 "maximize" => "Maximize",
644 _ => "Close",
645 };728 };
646 if let Some(node) = ui.access(ui.id(control)) {729 if let Some(node) = ui.access(ui.id(control)) {
647 node.set_label(name);730 node.set_label(name);
648 }731 }
649 match *control {732 match control {
650 _ if !clicked => {}733 _ if !clicked => {}
651 "minimize" => window.set_minimized(true),734 "minimize" => window.set_minimized(true),
652 "maximize" => zoom(window),735 "maximize" => zoom(window),
...@@ -657,6 +740,7 @@ pub fn window_controls(ui: &mut Ui, window: &Window) {...@@ -657,6 +740,7 @@ pub fn window_controls(ui: &mut Ui, window: &Window) {
657 }740 }
658 }741 }
659 }742 }
743 ui.close();
660}744}
661745
662/// The edge or corner a press at `pointer`, in logical pixels, resizes the window from, when746/// The edge or corner a press at `pointer`, in logical pixels, resizes the window from, when
...@@ -1055,7 +1139,7 @@ pub fn appearance(_: &Window) -> Theme {...@@ -1055,7 +1139,7 @@ pub fn appearance(_: &Window) -> Theme {
1055}1139}
10561140
1057/// Asks for the desktop's colours again whenever the settings portal reports a change to the1141/// Asks for the desktop's colours again whenever the settings portal reports a change to the
1058/// colour scheme, or to KDE's colours.1142/// colour scheme, or to KDE's colours, and keeps GNOME's button layout.
1059fn watch_settings(proxy: EventLoopProxy<crate::UserEvent>) {1143fn watch_settings(proxy: EventLoopProxy<crate::UserEvent>) {
1060 std::thread::spawn(move || {1144 std::thread::spawn(move || {
1061 let Ok(connection) = zbus::blocking::Connection::session() else {1145 let Ok(connection) = zbus::blocking::Connection::session() else {
...@@ -1073,16 +1157,25 @@ fn watch_settings(proxy: EventLoopProxy<crate::UserEvent>) {...@@ -1073,16 +1157,25 @@ fn watch_settings(proxy: EventLoopProxy<crate::UserEvent>) {
1073 return;1157 return;
1074 };1158 };
1075 for change in changes {1159 for change in changes {
1076 let Ok((namespace, _, _)) = change1160 let Ok((namespace, key, value)) =
1077 .body()1161 change
1078 .deserialize::<(String, String, zbus::zvariant::OwnedValue)>()1162 .body()
1163 .deserialize::<(String, String, zbus::zvariant::OwnedValue)>()
1079 else {1164 else {
1080 continue;1165 continue;
1081 };1166 };
1082 if (namespace == "org.freedesktop.appearance"1167 let event = if namespace == "org.freedesktop.appearance"
1083 || namespace.starts_with("org.kde.kdeglobals"))1168 || namespace.starts_with("org.kde.kdeglobals")
1084 && proxy.send_event(crate::UserEvent::Appearance).is_err()
1085 {1169 {
1170 crate::UserEvent::Appearance
1171 } else if namespace == "org.gnome.desktop.wm.preferences" && key == "button-layout" {
1172 *BUTTON_LAYOUT.lock().unwrap() =
1173 innermost(value).and_then(|value| String::try_from(value).ok());
1174 crate::UserEvent::Redraw
1175 } else {
1176 continue;
1177 };
1178 if proxy.send_event(event).is_err() {
1086 return;1179 return;
1087 }1180 }
1088 }1181 }
...@@ -1115,11 +1208,17 @@ fn portal_setting(namespace: &str, key: &str) -> Option<OwnedValue> {...@@ -1115,11 +1208,17 @@ fn portal_setting(namespace: &str, key: &str) -> Option<OwnedValue> {
1115 .ok()?;1208 .ok()?;
1116 let key = (namespace, key);1209 let key = (namespace, key);
1117 // Portals before ReadOne answer Read, with the value in a second variant.1210 // Portals before ReadOne answer Read, with the value in a second variant.
1118 let reply: OwnedValue = proxy1211 innermost(
1119 .call("ReadOne", &key)1212 proxy
1120 .or_else(|_| proxy.call("Read", &key))1213 .call("ReadOne", &key)
1121 .ok()?;1214 .or_else(|_| proxy.call("Read", &key))
1122 let mut value = Value::from(reply);1215 .ok()?,
1216 )
1217}
1218
1219/// `value` out of the variants a setting comes wrapped in.
1220fn innermost(value: OwnedValue) -> Option<OwnedValue> {
1221 let mut value = Value::from(value);
1123 while let Value::Value(inner) = value {1222 while let Value::Value(inner) = value {
1124 value = *inner;1223 value = *inner;
1125 }1224 }
crates/snowbound/src/macos.rs+1-1
...@@ -368,7 +368,7 @@ unsafe fn ns_data_bytes(data: &AnyObject) -> Vec<u8> {...@@ -368,7 +368,7 @@ unsafe fn ns_data_bytes(data: &AnyObject) -> Vec<u8> {
368}368}
369369
370/// AppKit draws the traffic lights.370/// AppKit draws the traffic lights.
371pub fn window_controls(_: &mut ui::Ui, _: &Window) {}371pub fn window_controls(_: &mut ui::Ui, _: &Window, _: bool) {}
372372
373/// Whether AppKit draws the title bar: before 10.10 content can't extend under it, and373/// Whether AppKit draws the title bar: before 10.10 content can't extend under it, and
374/// otherwise the app draws it around the traffic lights.374/// otherwise the app draws it around the traffic lights.
crates/snowbound/src/main.rs+4-1
...@@ -2017,6 +2017,9 @@ impl State {...@@ -2017,6 +2017,9 @@ impl State {
2017 ..Spec::default()2017 ..Spec::default()
2018 },2018 },
2019 );2019 );
2020 if title {
2021 platform::window_controls(&mut self.ui, &self.window, false);
2022 }
2020 if tools {2023 if tools {
2021 // The groups fold in the room the window's controls leave, and whatever still2024 // The groups fold in the room the window's controls leave, and whatever still
2022 // overflows is cut there, so the controls always show.2025 // overflows is cut there, so the controls always show.
...@@ -2050,7 +2053,7 @@ impl State {...@@ -2050,7 +2053,7 @@ impl State {
2050 }2053 }
2051 self.ui.close();2054 self.ui.close();
2052 if title {2055 if title {
2053 platform::window_controls(&mut self.ui, &self.window);2056 platform::window_controls(&mut self.ui, &self.window, true);
2054 }2057 }
2055 self.ui.leaf(2058 self.ui.leaf(
2056 "trail",2059 "trail",
crates/snowbound/src/web.rs+1-1
...@@ -532,7 +532,7 @@ pub fn cuts_corners() -> bool {...@@ -532,7 +532,7 @@ pub fn cuts_corners() -> bool {
532 false532 false
533}533}
534534
535pub fn window_controls(_: &mut Ui, _: &Window) {}535pub fn window_controls(_: &mut Ui, _: &Window, _: bool) {}
536536
537pub fn resize_direction(_: &Window, _: [f32; 2]) -> Option<ResizeDirection> {537pub fn resize_direction(_: &Window, _: [f32; 2]) -> Option<ResizeDirection> {
538 None538 None
crates/snowbound/src/windows.rs+4-1
...@@ -784,7 +784,10 @@ const CLOSE: &[&str] = &[include_str!("../assets/windows/caption-close.svg")];...@@ -784,7 +784,10 @@ const CLOSE: &[&str] = &[include_str!("../assets/windows/caption-close.svg")];
784/// 7 draws them over the glass and the row leaves them room, and 11 doesn't draw them over784/// 7 draws them over the glass and the row leaves them room, and 11 doesn't draw them over
785/// the DirectComposition surface, so the row draws them as 11 does, lit as the system785/// the DirectComposition surface, so the row draws them as 11 does, lit as the system
786/// reports the pointer. On 8 and 10 the row draws and runs them as 10 does.786/// reports the pointer. On 8 and 10 the row draws and runs them as 10 does.
787pub fn window_controls(ui: &mut Ui, window: &Window) {787pub fn window_controls(ui: &mut Ui, window: &Window, end: bool) {
788 if !end {
789 return;
790 }
788 let scale = window.scale_factor() as f32;791 let scale = window.scale_factor() as f32;
789 SCALE.store(scale.to_bits(), Ordering::Relaxed);792 SCALE.store(scale.to_bits(), Ordering::Relaxed);
790 let system = caption() == Caption::Glass;793 let system = caption() == Caption::Glass;