header_bar: add WindowControlsPosition (macOS-style left controls)
Adds a new public enum `WindowControlsPosition { Start, End }` and a
matching field on `HeaderBar`, allowing window controls (close / minimize
/ maximize) to be packed on the start side of the headerbar (macOS
style, icon order close → minimize → maximize) instead of the default
end side (Linux / GNOME style, minimize → maximize → close).
Wiring:
- `crate::widget::WindowControlsPosition` re-exported alongside
`HeaderBar`.
- `HeaderBar::controls_position(Option<WindowControlsPosition>)` setter;
when left unset, falls back to `crate::config::window_controls_position()`
(reads `CosmicTk.window_controls_position`), mirroring how `density`
falls back to `header_size()`.
- New `CosmicTk.window_controls_position` field with default `End` for
backwards compatibility; serde-friendly enum so existing configs keep
working via `#[serde(default)]` semantics.
Tested with cosmic-yoterm, cosmic-settings, cosmic-edit, cosmic-files
rebuilt against this libcosmic via a local `[patch]` override. Config
changes picked up live through the existing cosmic-config subscription.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
87510782ae
commit
10422b8f4a
3 changed files with 94 additions and 24 deletions
|
|
@ -4,6 +4,7 @@
|
|||
//! Configurations available to libcosmic applications.
|
||||
|
||||
use crate::cosmic_theme::Density;
|
||||
use crate::widget::WindowControlsPosition;
|
||||
use cosmic_config::cosmic_config_derive::CosmicConfigEntry;
|
||||
use cosmic_config::{Config, CosmicConfigEntry};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -67,6 +68,12 @@ pub fn header_size() -> Density {
|
|||
COSMIC_TK.read().unwrap().header_size
|
||||
}
|
||||
|
||||
/// Position of the window control buttons (close / minimize / maximize).
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn window_controls_position() -> WindowControlsPosition {
|
||||
COSMIC_TK.read().unwrap().window_controls_position
|
||||
}
|
||||
|
||||
/// Interface density.
|
||||
#[allow(clippy::missing_panics_doc)]
|
||||
pub fn interface_density() -> Density {
|
||||
|
|
@ -109,6 +116,10 @@ pub struct CosmicTk {
|
|||
|
||||
/// Mono font family
|
||||
pub monospace_font: FontConfig,
|
||||
|
||||
/// Side on which window control buttons (close / minimize / maximize)
|
||||
/// are placed. `End` = right (Linux / GNOME), `Start` = left (macOS).
|
||||
pub window_controls_position: WindowControlsPosition,
|
||||
}
|
||||
|
||||
impl Default for CosmicTk {
|
||||
|
|
@ -132,6 +143,7 @@ impl Default for CosmicTk {
|
|||
stretch: iced::font::Stretch::Normal,
|
||||
style: iced::font::Style::Normal,
|
||||
},
|
||||
window_controls_position: WindowControlsPosition::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,9 +27,32 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> {
|
|||
sharp_corners: false,
|
||||
is_ssd: false,
|
||||
on_double_click: None,
|
||||
transparent: false,
|
||||
controls_position: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Position of the window control buttons (close/min/max) within the headerbar.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Default,
|
||||
PartialEq,
|
||||
Eq,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
)]
|
||||
pub enum WindowControlsPosition {
|
||||
/// Controls packed at the start (left on LTR) — macOS style.
|
||||
/// Internal icon order becomes close → minimize → maximize.
|
||||
Start,
|
||||
/// Controls packed at the end (right on LTR) — Linux / GNOME style.
|
||||
/// Internal icon order is minimize → maximize → close.
|
||||
#[default]
|
||||
End,
|
||||
}
|
||||
|
||||
#[derive(Setters)]
|
||||
pub struct HeaderBar<'a, Message> {
|
||||
/// Defines the title of the window
|
||||
|
|
@ -88,6 +111,17 @@ pub struct HeaderBar<'a, Message> {
|
|||
|
||||
/// HeaderBar used for server-side decorations
|
||||
is_ssd: bool,
|
||||
|
||||
/// Whether the headerbar should be transparent
|
||||
transparent: bool,
|
||||
|
||||
/// Side on which the window control buttons (close / minimize / maximize)
|
||||
/// are rendered. `None` falls back to the user's CosmicTk config
|
||||
/// (`crate::config::window_controls_position()`). `Some` overrides it.
|
||||
/// `End` matches Linux / GNOME conventions; `Start` provides macOS-style
|
||||
/// left-side controls.
|
||||
#[setters(strip_option)]
|
||||
controls_position: Option<WindowControlsPosition>,
|
||||
}
|
||||
|
||||
impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
||||
|
|
@ -370,12 +404,20 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
let is_ssd = self.is_ssd;
|
||||
|
||||
// Take ownership of the regions to be packed.
|
||||
let start = std::mem::take(&mut self.start);
|
||||
let mut start = std::mem::take(&mut self.start);
|
||||
let center = std::mem::take(&mut self.center);
|
||||
let mut end = std::mem::take(&mut self.end);
|
||||
|
||||
// Also packs the window controls at the very end.
|
||||
end.push(self.window_controls(space_xxs));
|
||||
// Pack window controls on the configured side (reads CosmicTk
|
||||
// config when the builder did not set an explicit override).
|
||||
let controls_position = self
|
||||
.controls_position
|
||||
.unwrap_or_else(crate::config::window_controls_position);
|
||||
let controls = self.window_controls(space_xxs, controls_position);
|
||||
match controls_position {
|
||||
WindowControlsPosition::End => end.push(controls),
|
||||
WindowControlsPosition::Start => start.insert(0, controls),
|
||||
}
|
||||
|
||||
let padding = if is_ssd {
|
||||
[2, 8, 2, 8]
|
||||
|
|
@ -445,7 +487,11 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
}
|
||||
|
||||
/// Creates the widget for window controls.
|
||||
fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> {
|
||||
fn window_controls(
|
||||
&mut self,
|
||||
spacing: u16,
|
||||
controls_position: WindowControlsPosition,
|
||||
) -> Element<'a, Message> {
|
||||
macro_rules! icon {
|
||||
($name:expr, $size:expr, $on_press:expr) => {{
|
||||
widget::icon::from_name($name)
|
||||
|
|
@ -458,25 +504,37 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> {
|
|||
}};
|
||||
}
|
||||
|
||||
widget::row::with_capacity(3)
|
||||
.push_maybe(
|
||||
self.on_minimize
|
||||
.take()
|
||||
.map(|m| icon!("window-minimize-symbolic", 16, m)),
|
||||
)
|
||||
.push_maybe(self.on_maximize.take().map(|m| {
|
||||
if self.maximized {
|
||||
icon!("window-restore-symbolic", 16, m)
|
||||
} else {
|
||||
icon!("window-maximize-symbolic", 16, m)
|
||||
}
|
||||
}))
|
||||
.push_maybe(
|
||||
self.on_close
|
||||
.take()
|
||||
.map(|m| icon!("window-close-symbolic", 16, m)),
|
||||
)
|
||||
.spacing(spacing)
|
||||
let minimize = self
|
||||
.on_minimize
|
||||
.take()
|
||||
.map(|m| icon!("window-minimize-symbolic", 16, m));
|
||||
let maximize = self.on_maximize.take().map(|m| {
|
||||
if self.maximized {
|
||||
icon!("window-restore-symbolic", 16, m)
|
||||
} else {
|
||||
icon!("window-maximize-symbolic", 16, m)
|
||||
}
|
||||
});
|
||||
let close = self
|
||||
.on_close
|
||||
.take()
|
||||
.map(|m| icon!("window-close-symbolic", 16, m));
|
||||
|
||||
// Icon order follows the OS convention for the chosen side:
|
||||
// End → minimize, maximize, close (Linux / GNOME)
|
||||
// Start → close, minimize, maximize (macOS)
|
||||
let row = widget::row::with_capacity(3);
|
||||
let row = match controls_position {
|
||||
WindowControlsPosition::End => row
|
||||
.push_maybe(minimize)
|
||||
.push_maybe(maximize)
|
||||
.push_maybe(close),
|
||||
WindowControlsPosition::Start => row
|
||||
.push_maybe(close)
|
||||
.push_maybe(minimize)
|
||||
.push_maybe(maximize),
|
||||
};
|
||||
row.spacing(spacing)
|
||||
.align_y(iced::Alignment::Center)
|
||||
.into()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,7 +225,7 @@ pub use grid::{Grid, grid};
|
|||
|
||||
mod header_bar;
|
||||
#[doc(inline)]
|
||||
pub use header_bar::{HeaderBar, header_bar};
|
||||
pub use header_bar::{HeaderBar, WindowControlsPosition, header_bar};
|
||||
|
||||
pub mod icon;
|
||||
#[doc(inline)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue