diff --git a/src/config/mod.rs b/src/config/mod.rs index 1691caa..9807961 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,7 +4,6 @@ //! 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}; @@ -68,12 +67,6 @@ 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 { @@ -116,10 +109,6 @@ 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 { @@ -143,7 +132,6 @@ impl Default for CosmicTk { stretch: iced::font::Stretch::Normal, style: iced::font::Style::Normal, }, - window_controls_position: WindowControlsPosition::default(), } } } diff --git a/src/malloc.rs b/src/malloc.rs index bc5e835..b99a66f 100644 --- a/src/malloc.rs +++ b/src/malloc.rs @@ -1,49 +1,21 @@ // Copyright 2025 System76 // SPDX-License-Identifier: MPL-2.0 -use std::cell::Cell; use std::os::raw::c_int; -use std::time::{Duration, Instant}; const M_MMAP_THRESHOLD: c_int = -3; -/// Minimum interval between two actual `malloc_trim` calls. -/// -/// `trim` is called at the end of every `update()` and `view()`, which can -/// reach 60-200 Hz during typical scrolling, resize, or animation. Each -/// `malloc_trim` walks the glibc heap (10 µs to several ms depending on -/// fragmentation), so calling it at render frequency can consume a -/// substantial fraction of the frame budget. Throttling to 1 Hz keeps RSS -/// bounded while removing the per-frame syscall from the hot path. -const TRIM_MIN_INTERVAL: Duration = Duration::from_millis(1000); - -thread_local! { - static LAST_TRIM: Cell> = const { Cell::new(None) }; -} - unsafe extern "C" { fn malloc_trim(pad: usize); fn mallopt(param: c_int, value: c_int) -> c_int; } -/// Throttled wrapper over `malloc_trim`. Safe to call at render frequency: -/// consecutive calls within `TRIM_MIN_INTERVAL` (per-thread) skip the syscall. #[inline] pub fn trim(pad: usize) { - LAST_TRIM.with(|last| { - let now = Instant::now(); - let should_trim = match last.get() { - None => true, - Some(prev) => now.duration_since(prev) >= TRIM_MIN_INTERVAL, - }; - if should_trim { - last.set(Some(now)); - unsafe { - malloc_trim(pad); - } - } - }); + unsafe { + malloc_trim(pad); + } } /// Prevents glibc from hoarding memory via memory fragmentation. diff --git a/src/widget/header_bar.rs b/src/widget/header_bar.rs index 0fcbc6c..a772f7d 100644 --- a/src/widget/header_bar.rs +++ b/src/widget/header_bar.rs @@ -27,31 +27,9 @@ pub fn header_bar<'a, Message>() -> HeaderBar<'a, Message> { 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 @@ -113,14 +91,6 @@ pub struct HeaderBar<'a, Message> { /// 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, } impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { @@ -402,20 +372,12 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } = theme::spacing(); // Take ownership of the regions to be packed. - let mut start = std::mem::take(&mut self.start); + let start = std::mem::take(&mut self.start); let center = std::mem::take(&mut self.center); let mut end = std::mem::take(&mut self.end); - // 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), - } + // Also packs the window controls at the very end. + end.push(self.window_controls(space_xxs)); let padding = if self.is_ssd { [2, 8, 2, 8] @@ -485,11 +447,7 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { } /// Creates the widget for window controls. - fn window_controls( - &mut self, - spacing: u16, - controls_position: WindowControlsPosition, - ) -> Element<'a, Message> { + fn window_controls(&mut self, spacing: u16) -> Element<'a, Message> { macro_rules! icon { ($name:expr, $size:expr, $on_press:expr) => {{ widget::icon::from_name($name) @@ -502,37 +460,25 @@ impl<'a, Message: Clone + 'static> HeaderBar<'a, Message> { }}; } - 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) + 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) .align_y(iced::Alignment::Center) .into() } diff --git a/src/widget/mod.rs b/src/widget/mod.rs index db9b2fc..f442b0d 100644 --- a/src/widget/mod.rs +++ b/src/widget/mod.rs @@ -221,7 +221,7 @@ pub use grid::{Grid, grid}; mod header_bar; #[doc(inline)] -pub use header_bar::{HeaderBar, WindowControlsPosition, header_bar}; +pub use header_bar::{HeaderBar, header_bar}; pub mod icon; #[doc(inline)] diff --git a/src/widget/segmented_button/model/mod.rs b/src/widget/segmented_button/model/mod.rs index fff1cf6..e0dd8c5 100644 --- a/src/widget/segmented_button/model/mod.rs +++ b/src/widget/segmented_button/model/mod.rs @@ -132,10 +132,7 @@ where /// ``` #[inline] pub fn clear(&mut self) { - // `remove()` mutates `self.order`, so transfer ownership first: - // the inner `self.order.remove(index)` then no-ops because - // `position()` can't find the entity in the empty VecDeque. - for entity in std::mem::take(&mut self.order) { + for entity in self.order.clone() { self.remove(entity); } } diff --git a/src/widget/segmented_button/widget.rs b/src/widget/segmented_button/widget.rs index 9ddd7d8..44ca857 100644 --- a/src/widget/segmented_button/widget.rs +++ b/src/widget/segmented_button/widget.rs @@ -173,10 +173,6 @@ where pub(super) on_context: Option Message + 'static>>, #[setters(skip)] pub(super) on_middle_press: Option Message + 'static>>, - /// Emits the ID of the item that was double-clicked with the left button. - /// Fires in addition to `on_activate` (which keeps firing on each click). - #[setters(skip)] - pub(super) on_double_click: Option Message + 'static>>, #[setters(skip)] pub(super) on_dnd_drop: Option, String, DndAction) -> Message + 'static>>, @@ -236,7 +232,6 @@ where on_close: None, on_context: None, on_middle_press: None, - on_double_click: None, on_dnd_drop: None, on_dnd_enter: None, on_dnd_leave: None, @@ -359,16 +354,6 @@ where self } - /// Emitted when a tab is double-clicked with the left mouse button. - /// Fires in addition to `on_activate`, which keeps firing on each click. - pub fn on_double_click(mut self, on_double_click: T) -> Self - where - T: Fn(Entity) -> Message + 'static, - { - self.on_double_click = Some(Box::new(on_double_click)); - self - } - /// Enable drag-and-drop support for tabs using the provided payload builder. pub fn enable_tab_drag(mut self, mime: String) -> Self { self.tab_drag = Some(TabDragSource::new(mime)); @@ -406,12 +391,11 @@ where { return None; } - // Always use positional swap (Konsole/Firefox/Chrome semantics): - // dropping onto any part of a different tab swaps it with the dragged - // tab. drop_hint.side-based Before/After is counter-intuitive: dropping - // A (pos 0) on the left half of B (pos 1) resolves to "Before B" which, - // after removing A, lands at pos 0 — so the tab appears not to move. - let position = self.default_insert_position(dragged, target); + let position = state + .drop_hint + .filter(|hint| hint.entity == target) + .map(|hint| InsertPosition::from(hint.side)) + .unwrap_or_else(|| self.default_insert_position(dragged, target)); Some(ReorderEvent { dragged, target, @@ -928,7 +912,6 @@ where hovered: Default::default(), known_length: Default::default(), middle_clicked: Default::default(), - last_click: None, internal_layout: Default::default(), context_cursor: Point::default(), show_context: Default::default(), @@ -1202,14 +1185,7 @@ where .dnd_state .drag_offer .as_ref() - .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)) - // Self-drop fallback: some compositors (cosmic-comp - // observed) do not emit OfferEvent::SelectedAction for - // internal drags, leaving selected_action empty. - // dragging_tab is only set by start_tab_drag on this - // same widget, so this covers the self-drop case - // safely; mime and on_reorder are checked below. - || state.dragging_tab.is_some(); + .is_some_and(|offer| offer.selected_action.contains(DndAction::Move)); let pending_reorder = if allow_reorder && self.on_reorder.is_some() && self.tab_drag.as_ref().is_some_and(|d| d.mime == *mime_type) @@ -1407,33 +1383,6 @@ where state.set_focused(); state.focused_item = Item::Tab(key); state.pressed_item = None; - - // Double-click detection on the same entity - // within 400 ms — fires after on_activate so - // the tab is already focused when the handler - // runs. - if let Some(on_double_click) = - self.on_double_click.as_ref() - { - let now = Instant::now(); - let is_double = match state.last_click { - Some((prev, t)) => { - prev == key - && now.duration_since(t) - < Duration::from_millis(400) - } - None => false, - }; - state.last_click = if is_double { - None - } else { - Some((key, now)) - }; - if is_double { - shell.publish(on_double_click(key)); - } - } - shell.capture_event(); return; } @@ -2438,9 +2387,6 @@ pub struct LocalState { hovered: Item, /// The ID of the button that was middle-clicked, but not yet released. middle_clicked: Option, - /// Entity and timestamp of the most recent left-click activation, used - /// to detect double-clicks on the same tab. - last_click: Option<(Entity, Instant)>, /// Last known length of the model. pub(super) known_length: usize, /// Dimensions of internal buttons when shrinking @@ -2586,7 +2532,6 @@ mod tests { hovered: Item::default(), known_length: 0, middle_clicked: None, - last_click: None, internal_layout: Vec::new(), context_cursor: Point::ORIGIN, show_context: None, diff --git a/src/widget/table/model/mod.rs b/src/widget/table/model/mod.rs index 749860f..d6250ea 100644 --- a/src/widget/table/model/mod.rs +++ b/src/widget/table/model/mod.rs @@ -100,10 +100,7 @@ where /// model.clear(); /// ``` pub fn clear(&mut self) { - // `remove()` mutates `self.order`, so transfer ownership first: - // the inner `self.order.remove(index)` then no-ops because - // `position()` can't find the entity in the empty VecDeque. - for entity in std::mem::take(&mut self.order) { + for entity in self.order.clone() { self.remove(entity); } }