From 07a8e50f47ec7b17f995d7dbc1d846a37ce07eac Mon Sep 17 00:00:00 2001 From: Samuel Onoja Date: Wed, 14 Oct 2026 01:01:21 +0100 Subject: [PATCH] Add window blur, organize Settings, and reduce launcher memory --- compositor/src/config/appearance.rs | 4 + compositor/src/focus_effect.rs | 1 + compositor/src/input.rs | 27 ++++- compositor/src/render/closing.rs | 6 + compositor/src/render/mod.rs | 1 + compositor/src/render/resources.rs | 4 + compositor/src/render/scene.rs | 6 + compositor/src/render/window_blur.rs | 101 +++++++++++++++ compositor/src/state.rs | 4 +- compositor/src/state/prediction.rs | 1 + docs/configuration/appearance.md | 14 ++- docs/configuration/session-protocols.md | 2 +- docs/panel-configuration.md | 2 +- shell/ferese-settings/src/panel_controls.rs | 14 +++ shell/ferese-settings/src/schema.rs | 128 ++++++++++---------- shell/ferese-shell/src/launcher/mod.rs | 18 ++- shell/ferese-shell/src/launcher/view.rs | 15 +-- 17 files changed, 260 insertions(+), 88 deletions(-) create mode 100644 compositor/src/render/window_blur.rs diff --git a/compositor/src/config/appearance.rs b/compositor/src/config/appearance.rs index bc6d095..6a0b019 100644 --- a/compositor/src/config/appearance.rs +++ b/compositor/src/config/appearance.rs @@ -5,6 +5,7 @@ impl Config { let effect = &self.appearance.focus_effect; Ok(FocusEffectSettings { enabled: effect.enabled, + blur: effect.blur, active_opacity: unit_theme_value(effect.active_opacity, "appearance.focus_effect.active_opacity")?, inactive_opacity: unit_theme_value(effect.inactive_opacity, "appearance.focus_effect.inactive_opacity")?, inactive_dim: unit_theme_value(effect.inactive_dim, "appearance.focus_effect.inactive_dim")?, @@ -254,6 +255,7 @@ pub(super) struct AppearanceConfig { #[derive(Clone, Copy, Debug, PartialEq)] pub struct FocusEffectSettings { pub enabled: bool, + pub blur: bool, pub active_opacity: f64, pub inactive_opacity: f64, pub inactive_dim: f64, @@ -264,6 +266,7 @@ pub struct FocusEffectSettings { #[serde(default, deny_unknown_fields)] pub(super) struct FocusEffectConfig { pub(super) enabled: bool, + pub(super) blur: bool, pub(super) active_opacity: f64, pub(super) inactive_opacity: f64, pub(super) inactive_dim: f64, @@ -274,6 +277,7 @@ impl Default for FocusEffectConfig { fn default() -> Self { Self { enabled: true, + blur: true, active_opacity: 1.0, inactive_opacity: 1.0, inactive_dim: 0.0, diff --git a/compositor/src/focus_effect.rs b/compositor/src/focus_effect.rs index 1c0f6b5..4afbbaf 100644 --- a/compositor/src/focus_effect.rs +++ b/compositor/src/focus_effect.rs @@ -100,6 +100,7 @@ mod tests { fn settings() -> FocusEffectSettings { FocusEffectSettings { enabled: true, + blur: true, active_opacity: 1.0, inactive_opacity: 0.8, inactive_dim: 0.2, diff --git a/compositor/src/input.rs b/compositor/src/input.rs index 468c4e7..170e3fa 100644 --- a/compositor/src/input.rs +++ b/compositor/src/input.rs @@ -593,7 +593,8 @@ impl Ferese { } return; } - if event.state() == ButtonState::Released && self.tab_buttons.remove(&event.button_code()) { + if event.state() == ButtonState::Released && self.consumed_pointer_buttons.remove(&event.button_code()) + { return; } let pointer = self.seat.get_pointer().expect("seat has a pointer"); @@ -626,11 +627,33 @@ impl Ferese { return; } + if event.state() == ButtonState::Pressed + && !pointer.is_grabbed() + && let Some(surface) = self.seat.get_keyboard().and_then(|keyboard| keyboard.current_focus()) + && self + .space + .layer_for_surface(&surface, smithay::desktop::WindowSurfaceType::ALL) + .is_some_and(|layer| layer.namespace() == "ferese-launcher") + && !self + .layer_under(pointer.current_location()) + .is_some_and(|(layer, _, _)| layer.wl_surface() == &surface) + && let Some(shell) = self + .shell_resources + .iter() + .filter_map(|shell| shell.upgrade().ok()) + .find(|shell| shell.version() >= 10) + { + // Consume both halves of the click before underlying windows or tabs see it. + self.consumed_pointer_buttons.insert(event.button_code()); + shell.toggle_launcher(String::new()); + return; + } + if event.state() == ButtonState::Pressed && !pointer.is_grabbed() && let Some(id) = self.tab_at(pointer.current_location()) { - self.tab_buttons.insert(event.button_code()); + self.consumed_pointer_buttons.insert(event.button_code()); if event.button_code() == BTN_LEFT { self.activate_managed_window(id); } diff --git a/compositor/src/render/closing.rs b/compositor/src/render/closing.rs index 8ffde14..da346f1 100644 --- a/compositor/src/render/closing.rs +++ b/compositor/src/render/closing.rs @@ -195,6 +195,12 @@ pub(super) fn grouped_elements( { elements.push(material); } + if window.material.is_none() + && let Some(blur) = + super::window_blur::window_blur_element(state, renderer, output, window.presentation, corners) + { + elements.push(blur); + } let theme = &state.theme_settings; let (offset_factor, blur_factor, opacity_factor) = window.presentation.shadow_factors(); if let Some(shadow) = window_shadow_element( diff --git a/compositor/src/render/mod.rs b/compositor/src/render/mod.rs index 7e0c4e2..c76e4b2 100644 --- a/compositor/src/render/mod.rs +++ b/compositor/src/render/mod.rs @@ -1,6 +1,7 @@ mod backdrop; mod capture; mod closing; +mod window_blur; mod window_content; pub(crate) use closing::ClosedWindow; #[cfg(test)] diff --git a/compositor/src/render/resources.rs b/compositor/src/render/resources.rs index af2a12d..39e601c 100644 --- a/compositor/src/render/resources.rs +++ b/compositor/src/render/resources.rs @@ -20,6 +20,7 @@ pub(super) struct WindowBuffers { pub borders: WindowBorderBuffers, pub dim: WindowBorderBuffers, pub resize_fill: WindowBorderBuffers, + pub blur: HashMap<(ErasedContextId, OutputId), super::window_blur::WindowBlur>, pub shadow: WindowShadowBuffers, corner_shape: Option, thumbnail_shapes: HashMap, @@ -48,6 +49,7 @@ impl RenderResources { buffers.dim.contexts.clear(); buffers.resize_fill.contexts.clear(); buffers.shadow.contexts.clear(); + buffers.blur.clear(); if let Some(snapshot) = &mut buffers.snapshot { snapshot.commit.increment(); @@ -101,6 +103,7 @@ impl RenderResources { for window in self.windows.values_mut() { window.thumbnail_shapes.remove(&id); + window.blur.retain(|(_, output), _| *output != id); } for surface in self.surfaces.values_mut() { @@ -167,6 +170,7 @@ impl RenderResources { window.dim.contexts.remove(context); window.resize_fill.contexts.remove(context); window.shadow.contexts.remove(context); + window.blur.retain(|(key, _), _| key != context); if window .snapshot diff --git a/compositor/src/render/scene.rs b/compositor/src/render/scene.rs index 37f9938..91ae6af 100644 --- a/compositor/src/render/scene.rs +++ b/compositor/src/render/scene.rs @@ -340,6 +340,12 @@ fn scene_elements( { elements.push(background); } + if material_surface.is_none() + && let Some(blur) = + super::window_blur::window_blur_element(state, renderer, output, sample.presentation, corners) + { + elements.push(blur); + } if let Some(shadow) = shadow { elements.push(shadow.into()); } diff --git a/compositor/src/render/window_blur.rs b/compositor/src/render/window_blur.rs new file mode 100644 index 0000000..7bb3c87 --- /dev/null +++ b/compositor/src/render/window_blur.rs @@ -0,0 +1,101 @@ +use super::*; + +#[derive(Debug)] +pub(super) struct WindowBlur { + parameters: MaterialParameters, + element: BlurRenderElement, +} + +pub(super) fn window_blur_element( + state: &mut Ferese, + renderer: &mut GlesRenderer, + output: &Output, + presentation: crate::presentation::WindowPresentation, + corners: RoundedRect, +) -> Option { + let radius = state.theme_settings.backdrop_blur; + if !state.focus_effect.enabled + || !state.focus_effect.blur + || state.theme_settings.reduce_transparency + || radius <= 0.0 + || presentation.focus_alpha <= 0.0 + || presentation.focus_alpha >= 1.0 + || presentation.alpha() <= 0.0 + { + if let Some(window) = state.render.windows.get_mut(&presentation.id) { + window.blur.clear(); + } + return None; + } + + let mode = output.current_mode()?; + let scale = output.current_scale().fractional_scale(); + let transform = output.current_transform().invert(); + let output_size = output + .current_transform() + .transform_size(mode.size) + .to_f64() + .to_logical(scale) + .to_i32_ceil(); + let geometry = corners.rect.to_f64().to_logical(scale).to_i32_up(); + let Some(visible) = geometry.intersection(Rectangle::from_size(output_size)) else { + if let Some(window) = state.render.windows.get_mut(&presentation.id) { + window.blur.clear(); + } + return None; + }; + let sample_geometry = expanded_blur_region(visible, radius.ceil() as i32, output_size); + let sample_physical = sample_geometry.to_physical_precise_round(scale); + let parameters = MaterialParameters { + panel: None, + corner_radii: [corners.radius; 4], + // Replace the sharp backdrop completely. Window content supplies the + // transparency; only the lifecycle animation fades this background. + presentation_alpha: presentation.opacity.current.clamp(0.0, 1.0) as f32, + background_opacity: 0.0, + blur: (radius * scale) as f32, + sample_geometry, + sample_physical, + sample_framebuffer: framebuffer_clip_rect(sample_physical, mode.size, transform), + shadow_rect: [0.0; 4], + shadow_values: [0.0; 2], + shadow_bounds: geometry, + geometry, + tint: [0.0; 4], + generation: 0, + opaque: false, + visible_framebuffer: framebuffer_clip_rect(corners.rect, mode.size, transform), + }; + let key = (renderer.context_id().erased(), state.output_id(output)?); + let program = blur_program(&mut state.render, renderer)?; + let buffers = &mut state.render.windows.entry(presentation.id).or_default().blur; + let capture_rect = framebuffer_capture_rect(parameters.sample_framebuffer); + let capture_size = Size::from((capture_rect[2], capture_rect[3])); + if buffers + .get(&key) + .is_none_or(|cached| cached.element.texture.size() != capture_size) + { + let texture = match Offscreen::::create_buffer(renderer, Fourcc::Abgr8888, capture_size) { + Ok(texture) => texture, + Err(error) => { + tracing::warn!(%error, "window blur allocation failed"); + buffers.remove(&key); + return None; + } + }; + let element = BlurRenderElement::new(texture, program.0, ¶meters, Arc::new(AtomicBool::new(true))); + buffers.insert( + key.clone(), + WindowBlur { + parameters: parameters.clone(), + element, + }, + ); + } + let cached = buffers.get_mut(&key)?; + if cached.parameters != parameters { + cached.element.update(¶meters); + cached.parameters = parameters; + } + Some(cached.element.clone().into()) +} diff --git a/compositor/src/state.rs b/compositor/src/state.rs index 3c36733..8a3dbf1 100644 --- a/compositor/src/state.rs +++ b/compositor/src/state.rs @@ -467,7 +467,7 @@ pub struct Ferese { pub(crate) tab_indicator: tab_indicator::TabIndicatorState, pub(crate) tab_indicator_settings: crate::config::TabIndicatorSettings, pub(crate) tab_bars: HashMap>, - pub(crate) tab_buttons: HashSet, + pub(crate) consumed_pointer_buttons: HashSet, pub(crate) focus_history: ferese_core::FocusHistory, pub(crate) focus_cycle: Option, column_width_presets: Vec, @@ -771,7 +771,7 @@ impl Ferese { tab_indicator: tab_indicator::TabIndicatorState::default(), tab_indicator_settings: config.tab_indicator, tab_bars: HashMap::new(), - tab_buttons: HashSet::new(), + consumed_pointer_buttons: HashSet::new(), focus_history: Default::default(), focus_cycle: None, column_width_presets: config.column_width_presets, diff --git a/compositor/src/state/prediction.rs b/compositor/src/state/prediction.rs index 3a1cc2a..15a6a3c 100644 --- a/compositor/src/state/prediction.rs +++ b/compositor/src/state/prediction.rs @@ -388,6 +388,7 @@ mod tests { .unwrap(); state.focus_effect = crate::config::FocusEffectSettings { enabled: true, + blur: true, active_opacity: 0.9, inactive_opacity: 0.6, inactive_dim: 0.2, diff --git a/docs/configuration/appearance.md b/docs/configuration/appearance.md index f559fa6..c58c6fb 100644 --- a/docs/configuration/appearance.md +++ b/docs/configuration/appearance.md @@ -29,6 +29,7 @@ one appearance. | `appearance.focus-effect.enabled` | boolean | `true` | Apply window opacity and dimming; disabling preserves the saved settings | | `appearance.focus-effect.active-opacity` | number 0–1 | `1` | Active application opacity, including fullscreen | | `appearance.focus-effect.inactive-opacity` | number 0–1 | `1` | Other application window opacity | +| `appearance.focus-effect.blur` | boolean | `true` | Blur behind translucent application windows using the shared blur radius | | `appearance.focus-effect.inactive-dim` | number 0–1 | `0` | Darkening strength for inactive windows; 0 disables | | `appearance.focus-effect.duration-ms` | number ≥ 0 | `150` | Shared opacity and dimming transition; 0 snaps | | `theme.typography.font-family` | string | `"Inter"` | Shell, Settings and overview font | @@ -47,6 +48,10 @@ opacity still follows activation; dimming fades out as overview opens. Fullscree windows use the same opacity settings. Opening and closing fades multiply window opacity. Reduced motion disables the focus transition. +**Window blur** in Settings controls blur behind both active and inactive windows. +It uses `theme.material.blur-radius` independently of the shell material style. +Fully opaque or invisible windows skip blur. Reduced transparency disables it. + Settings → Wallpaper shows the current image and both bundled defaults. Use **Apply to** to change both modes or just one. **Match appearance** restores the bundled light and dark pair. Custom images can also be selected separately in KDL: @@ -105,11 +110,16 @@ the automatic gradient. Inactive window borders derive their gradient from the border color and keep its transparency. Automatic accent endpoints are adjusted for contrast against the theme's surfaces. -In Settings → Appearance, choose **Gradient** or **Solid** for **Accent style** -and **Inactive border style**. In KDL, `theme.focus-ring.style` and +In Settings → Appearance, **Accent style** controls the gradient used by focus +borders and selected shell controls. Settings → Windows contains **Inactive +border style** and **Focus border**. Accent style and inactive border style +offer **Gradient** or **Solid**. In KDL, `theme.focus-ring.style` and `theme.border.style` accept `"auto"` (default) or `"solid"`. Solid disables the gradient, including any explicit endpoints, without deleting them from the config. +Shell surface style, opacity and corner radius are in Settings → Panel & Shell. +**Background blur** in Appearance sets the blur radius for shell surfaces and windows. + For custom endpoints, set `theme.focus-ring.gradient` or `theme.border.gradient`. Both require `from` and `to` colors; `angle` is optional and defaults to `0`. Angles are clockwise: 0 is left-to-right, 90 top-to-bottom. Explicit gradients diff --git a/docs/configuration/session-protocols.md b/docs/configuration/session-protocols.md index c65ba76..3965d8f 100644 --- a/docs/configuration/session-protocols.md +++ b/docs/configuration/session-protocols.md @@ -12,7 +12,7 @@ environment for screenshots to work. Text input is available by default; Ctrl+Alt+Escape releases an active shortcut inhibitor. Use `feresectl --help` for runtime control commands. -Shell rounding is controlled in Settings → Appearance → Shell corner radius. +Shell rounding is controlled in Settings → Panel & Shell → Shell corner radius. The shell and compositor use the same squircle profile for fills, borders, shadows and blur masks. Small controls cap the radius to fit their size; circles and pills keep circular outlines. The measured profile has small tangent and curvature diff --git a/docs/panel-configuration.md b/docs/panel-configuration.md index 11d3c45..d519526 100644 --- a/docs/panel-configuration.md +++ b/docs/panel-configuration.md @@ -255,7 +255,7 @@ Notifications or Controls view. Escape or the close button collapses it. With an Activities item configured, running tasks can occupy the collapsed and peek states, and the hub gains an Activities tab. **Visible activities** and -**Show activity count** in Appearance control its compact slots and `+n` badge; +**Show activity count** in Panel & Shell control its compact slots and `+n` badge; see [Activities configuration](configuration/activities.md). The Workspaces view shows miniature window layouts. Click a tile to switch diff --git a/shell/ferese-settings/src/panel_controls.rs b/shell/ferese-settings/src/panel_controls.rs index 06d7777..ce244a2 100644 --- a/shell/ferese-settings/src/panel_controls.rs +++ b/shell/ferese-settings/src/panel_controls.rs @@ -315,6 +315,20 @@ impl App { .padding(12) .class(crate::visuals::surface(palette.card, 14.)), ); + let mut shell = widget::column([]) + .spacing(4) + .push(self.panel_heading("Shell appearance", 15.)); + for field in crate::schema::fields(Page::Bar) + .into_iter() + .filter(|field| field.path.starts_with("theme.")) + { + shell = shell.push(self.field(field)); + } + rows = rows.push( + widget::container(shell) + .padding(12) + .class(crate::visuals::surface(palette.card, 14.)), + ); } PanelPage::Arrange => return self.panel_workspace(), } diff --git a/shell/ferese-settings/src/schema.rs b/shell/ferese-settings/src/schema.rs index 53a7648..15cc6a2 100644 --- a/shell/ferese-settings/src/schema.rs +++ b/shell/ferese-settings/src/schema.rs @@ -99,7 +99,7 @@ impl Page { Self::Appearance => "A desktop that feels like yours.", Self::Accessibility => "Make the desktop easier to see and use.", Self::Wallpaper => "Set the scene for your workspace.", - Self::Bar => "Arrange panel items and groups.", + Self::Bar => "Arrange your panel and change the shell's appearance.", Self::Notifications => "Stay informed on your terms.", Self::LockScreen => "Your desktop, safely put away.", Self::Windows => "Window layouts, spacing and focus effects.", @@ -392,19 +392,6 @@ pub fn fields(page: Page) -> Vec { "system follows your desktop timezone; or enter an IANA timezone.", "system", ), - range( - "theme.geometry.shell_radius", - "Shell corner radius", - "Rounds the bar, menus, notifications and hover backgrounds. Window corners are separate.", - RangeSpec { - default: 14.0, - min: 0.0, - max: 32.0, - step: 1.0, - suffix: " px", - integer: false, - }, - ), text( "theme.accent", "Accent color", @@ -418,37 +405,10 @@ pub fn fields(page: Page) -> Vec { "auto", &[("auto", "Gradient"), ("solid", "Solid")], ), - choice( - "theme.border.style", - "Inactive border style", - "Gradients retain the border color's transparency.", - "auto", - &[("auto", "Gradient"), ("solid", "Solid")], - ), - choice( - "theme.material.style", - "Surface style", - "Choose the finish for the bar and menus.", - "solid", - &[("solid", "Solid"), ("translucent", "Translucent")], - ), - range( - "theme.material.opacity", - "Shell opacity", - "Background opacity for the bar, popovers, menus, notifications and sharing dialog. Text and icons stay opaque.", - RangeSpec { - default: ferese_config::DEFAULT_MATERIAL_OPACITY, - min: 0.0, - max: 1.0, - step: 0.01, - suffix: "", - integer: false, - }, - ), range( "theme.material.blur_radius", "Background blur", - "Softness behind translucent surfaces.", + "Softness behind translucent shell surfaces and windows. Zero disables blur.", RangeSpec { default: 12.0, min: 0.0, @@ -464,19 +424,6 @@ pub fn fields(page: Page) -> Vec { "Use the name of an installed font family.", "Inter", ), - range( - "theme.geometry.focus_ring_width", - "Focus border", - "A subtle outline around the active window.", - RangeSpec { - default: 2.0, - min: 0.0, - max: 4.0, - step: 0.5, - suffix: " px", - integer: false, - }, - ), ], Page::Wallpaper => vec![ text( @@ -521,6 +468,39 @@ pub fn fields(page: Page) -> Vec { ), ], Page::Bar => vec![ + range( + "theme.geometry.shell_radius", + "Shell corner radius", + "Rounds the bar, menus, notifications and hover backgrounds. Window corners are separate.", + RangeSpec { + default: 14.0, + min: 0.0, + max: 32.0, + step: 1.0, + suffix: " px", + integer: false, + }, + ), + choice( + "theme.material.style", + "Surface style", + "Choose the finish for the panel, launcher, menus and notifications.", + "solid", + &[("solid", "Solid"), ("translucent", "Translucent")], + ), + range( + "theme.material.opacity", + "Shell opacity", + "Background opacity for the bar, popovers, menus, notifications and sharing dialog. Text and icons stay opaque.", + RangeSpec { + default: ferese_config::DEFAULT_MATERIAL_OPACITY, + min: 0.0, + max: 1.0, + step: 0.01, + suffix: "", + integer: false, + }, + ), range( "panels.0.geometry.height", "Height", @@ -651,6 +631,32 @@ pub fn fields(page: Page) -> Vec { integer: false, }, ), + choice( + "theme.border.style", + "Inactive border style", + "Gradients retain the border color's transparency.", + "auto", + &[("auto", "Gradient"), ("solid", "Solid")], + ), + range( + "theme.geometry.focus_ring_width", + "Focus border", + "A subtle outline around the active window.", + RangeSpec { + default: 2.0, + min: 0.0, + max: 4.0, + step: 0.5, + suffix: " px", + integer: false, + }, + ), + toggle( + "input.focus_follows_mouse", + "Focus follows pointer", + "Focus visible windows without raising or scrolling. Click to bring them into view.", + false, + ), toggle( "appearance.focus_effect.enabled", "Window focus effects", @@ -683,6 +689,12 @@ pub fn fields(page: Page) -> Vec { integer: false, }, ), + toggle( + "appearance.focus_effect.blur", + "Window blur", + "Soften the background behind translucent windows. Adjust the amount in Appearance.", + true, + ), range( "appearance.focus_effect.inactive_dim", "Inactive window dimming", @@ -770,12 +782,6 @@ pub fn fields(page: Page) -> Vec { integer: true, }, ), - toggle( - "input.focus_follows_mouse", - "Focus follows pointer", - "Focus visible windows without raising or scrolling. Click to bring them into view.", - false, - ), toggle( "input.touchpad.tap", "Tap to click", diff --git a/shell/ferese-shell/src/launcher/mod.rs b/shell/ferese-shell/src/launcher/mod.rs index 2a5c814..6601ae4 100644 --- a/shell/ferese-shell/src/launcher/mod.rs +++ b/shell/ferese-shell/src/launcher/mod.rs @@ -33,7 +33,6 @@ pub(super) enum Message { pub(super) struct Launcher { pub id: window::Id, pub(super) output: String, - top: f32, input: cosmic::widget::Id, query: String, category: Option, @@ -138,7 +137,13 @@ impl FereseShell { }; let name = output.name.clone().unwrap_or(name); let wl_output = output.output.clone(); - let top = output.size.map_or(240., |(_, h)| h as f32 * 0.3); + let (output_width, output_height) = output.size.unwrap_or((1920, 1080)); + // Reserve the largest browse view once, avoiding buffer reallocations during springs. + let width = 720u32.min(output_width.max(1) as u32); + let height = 404u32.min((output_height - 32).max(1) as u32); + let top = ((output_height as f32 * 0.3) as i32) + .min(output_height - height as i32 - 16) + .max(0); let dismissed = Task::batch([self.destroy_menu(), self.destroy_system_modal(true)]); let id = window::Id::unique(); let mut progress = AnimatedValue::new(0.); @@ -151,7 +156,6 @@ impl FereseShell { self.launcher = Some(Launcher { id, output: name, - top, input: cosmic::widget::Id::unique(), query: String::new(), category: None, @@ -191,11 +195,15 @@ impl FereseShell { id, layer: Layer::Overlay, keyboard_interactivity: KeyboardInteractivity::Exclusive, - anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT, + anchor: Anchor::TOP, output: IcedOutput::Output(wl_output.clone()), namespace: "ferese-launcher".into(), exclusive_zone: -1, - size: Some((None, None)), + size: Some((Some(width), Some(height))), + margin: IcedMargin { + top, + ..Default::default() + }, ..Default::default() }, Some(Box::new(move |app| app.view_launcher(id))), diff --git a/shell/ferese-shell/src/launcher/view.rs b/shell/ferese-shell/src/launcher/view.rs index 66cf9c6..6808363 100644 --- a/shell/ferese-shell/src/launcher/view.rs +++ b/shell/ferese-shell/src/launcher/view.rs @@ -484,15 +484,6 @@ impl FereseShell { } body = body.push(container(rows).padding([0, 12])); } - let available_height = self - .outputs - .iter() - .find(|o| o.name.as_deref() == Some(&launcher.output)) - .and_then(|o| o.size) - .map(|(_, h)| h as f32); - let top = available_height.map_or(launcher.top, |h| { - (h * 0.3).min(h - launcher.height.current as f32 - 16.).max(16.) - }); let mut background = color_with_opacity(self.config.theme.surface_popover, opacity); if self.config.theme.high_contrast || self.config.theme.reduce_transparency { background.a = opacity; @@ -521,11 +512,7 @@ impl FereseShell { launcher.regions.clone(), )) .width(Length::Fill) - .align_x(cosmic::iced::Alignment::Center) - .padding(cosmic::iced::Padding { - top, - ..Default::default() - }); + .align_x(cosmic::iced::Alignment::Center); let dismiss = message(Message::Dismiss(id)); let backdrop = mouse_area(Space::new().width(Length::Fill).height(Length::Fill)) .on_press(dismiss.clone())