Add window blur, organize Settings, and reduce launcher memory
This commit is contained in:
1 parent
bbc2f226d8
commit
07a8e50f47
17 files changed
+260
-88
No files matched your search
@@ -5,6 +5,7 @@ impl Config {
|
|||||||
let effect = &self.appearance.focus_effect;
|
let effect = &self.appearance.focus_effect;
|
||||||
Ok(FocusEffectSettings {
|
Ok(FocusEffectSettings {
|
||||||
enabled: effect.enabled,
|
enabled: effect.enabled,
|
||||||
|
blur: effect.blur,
|
||||||
active_opacity: unit_theme_value(effect.active_opacity, "appearance.focus_effect.active_opacity")?,
|
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_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")?,
|
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)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct FocusEffectSettings {
|
pub struct FocusEffectSettings {
|
||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
|
pub blur: bool,
|
||||||
pub active_opacity: f64,
|
pub active_opacity: f64,
|
||||||
pub inactive_opacity: f64,
|
pub inactive_opacity: f64,
|
||||||
pub inactive_dim: f64,
|
pub inactive_dim: f64,
|
||||||
@@ -264,6 +266,7 @@ pub struct FocusEffectSettings {
|
|||||||
#[serde(default, deny_unknown_fields)]
|
#[serde(default, deny_unknown_fields)]
|
||||||
pub(super) struct FocusEffectConfig {
|
pub(super) struct FocusEffectConfig {
|
||||||
pub(super) enabled: bool,
|
pub(super) enabled: bool,
|
||||||
|
pub(super) blur: bool,
|
||||||
pub(super) active_opacity: f64,
|
pub(super) active_opacity: f64,
|
||||||
pub(super) inactive_opacity: f64,
|
pub(super) inactive_opacity: f64,
|
||||||
pub(super) inactive_dim: f64,
|
pub(super) inactive_dim: f64,
|
||||||
@@ -274,6 +277,7 @@ impl Default for FocusEffectConfig {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
blur: true,
|
||||||
active_opacity: 1.0,
|
active_opacity: 1.0,
|
||||||
inactive_opacity: 1.0,
|
inactive_opacity: 1.0,
|
||||||
inactive_dim: 0.0,
|
inactive_dim: 0.0,
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ mod tests {
|
|||||||
fn settings() -> FocusEffectSettings {
|
fn settings() -> FocusEffectSettings {
|
||||||
FocusEffectSettings {
|
FocusEffectSettings {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
blur: true,
|
||||||
active_opacity: 1.0,
|
active_opacity: 1.0,
|
||||||
inactive_opacity: 0.8,
|
inactive_opacity: 0.8,
|
||||||
inactive_dim: 0.2,
|
inactive_dim: 0.2,
|
||||||
|
|||||||
+25
-2
@@ -593,7 +593,8 @@ impl Ferese {
|
|||||||
}
|
}
|
||||||
return;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
let pointer = self.seat.get_pointer().expect("seat has a pointer");
|
||||||
@@ -626,11 +627,33 @@ impl Ferese {
|
|||||||
return;
|
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
|
if event.state() == ButtonState::Pressed
|
||||||
&& !pointer.is_grabbed()
|
&& !pointer.is_grabbed()
|
||||||
&& let Some(id) = self.tab_at(pointer.current_location())
|
&& 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 {
|
if event.button_code() == BTN_LEFT {
|
||||||
self.activate_managed_window(id);
|
self.activate_managed_window(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,12 @@ pub(super) fn grouped_elements(
|
|||||||
{
|
{
|
||||||
elements.push(material);
|
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 theme = &state.theme_settings;
|
||||||
let (offset_factor, blur_factor, opacity_factor) = window.presentation.shadow_factors();
|
let (offset_factor, blur_factor, opacity_factor) = window.presentation.shadow_factors();
|
||||||
if let Some(shadow) = window_shadow_element(
|
if let Some(shadow) = window_shadow_element(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
mod backdrop;
|
mod backdrop;
|
||||||
mod capture;
|
mod capture;
|
||||||
mod closing;
|
mod closing;
|
||||||
|
mod window_blur;
|
||||||
mod window_content;
|
mod window_content;
|
||||||
pub(crate) use closing::ClosedWindow;
|
pub(crate) use closing::ClosedWindow;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ pub(super) struct WindowBuffers {
|
|||||||
pub borders: WindowBorderBuffers,
|
pub borders: WindowBorderBuffers,
|
||||||
pub dim: WindowBorderBuffers,
|
pub dim: WindowBorderBuffers,
|
||||||
pub resize_fill: WindowBorderBuffers,
|
pub resize_fill: WindowBorderBuffers,
|
||||||
|
pub blur: HashMap<(ErasedContextId, OutputId), super::window_blur::WindowBlur>,
|
||||||
pub shadow: WindowShadowBuffers,
|
pub shadow: WindowShadowBuffers,
|
||||||
corner_shape: Option<CornerShape>,
|
corner_shape: Option<CornerShape>,
|
||||||
thumbnail_shapes: HashMap<OutputId, CornerShape>,
|
thumbnail_shapes: HashMap<OutputId, CornerShape>,
|
||||||
@@ -48,6 +49,7 @@ impl RenderResources {
|
|||||||
buffers.dim.contexts.clear();
|
buffers.dim.contexts.clear();
|
||||||
buffers.resize_fill.contexts.clear();
|
buffers.resize_fill.contexts.clear();
|
||||||
buffers.shadow.contexts.clear();
|
buffers.shadow.contexts.clear();
|
||||||
|
buffers.blur.clear();
|
||||||
|
|
||||||
if let Some(snapshot) = &mut buffers.snapshot {
|
if let Some(snapshot) = &mut buffers.snapshot {
|
||||||
snapshot.commit.increment();
|
snapshot.commit.increment();
|
||||||
@@ -101,6 +103,7 @@ impl RenderResources {
|
|||||||
|
|
||||||
for window in self.windows.values_mut() {
|
for window in self.windows.values_mut() {
|
||||||
window.thumbnail_shapes.remove(&id);
|
window.thumbnail_shapes.remove(&id);
|
||||||
|
window.blur.retain(|(_, output), _| *output != id);
|
||||||
}
|
}
|
||||||
|
|
||||||
for surface in self.surfaces.values_mut() {
|
for surface in self.surfaces.values_mut() {
|
||||||
@@ -167,6 +170,7 @@ impl RenderResources {
|
|||||||
window.dim.contexts.remove(context);
|
window.dim.contexts.remove(context);
|
||||||
window.resize_fill.contexts.remove(context);
|
window.resize_fill.contexts.remove(context);
|
||||||
window.shadow.contexts.remove(context);
|
window.shadow.contexts.remove(context);
|
||||||
|
window.blur.retain(|(key, _), _| key != context);
|
||||||
|
|
||||||
if window
|
if window
|
||||||
.snapshot
|
.snapshot
|
||||||
|
|||||||
@@ -340,6 +340,12 @@ fn scene_elements(
|
|||||||
{
|
{
|
||||||
elements.push(background);
|
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 {
|
if let Some(shadow) = shadow {
|
||||||
elements.push(shadow.into());
|
elements.push(shadow.into());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<AnimatedWindowRenderElement> {
|
||||||
|
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::<GlesTexture>::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())
|
||||||
|
}
|
||||||
@@ -467,7 +467,7 @@ pub struct Ferese {
|
|||||||
pub(crate) tab_indicator: tab_indicator::TabIndicatorState,
|
pub(crate) tab_indicator: tab_indicator::TabIndicatorState,
|
||||||
pub(crate) tab_indicator_settings: crate::config::TabIndicatorSettings,
|
pub(crate) tab_indicator_settings: crate::config::TabIndicatorSettings,
|
||||||
pub(crate) tab_bars: HashMap<WorkspaceId, Vec<ferese_layout::TabBar>>,
|
pub(crate) tab_bars: HashMap<WorkspaceId, Vec<ferese_layout::TabBar>>,
|
||||||
pub(crate) tab_buttons: HashSet<u32>,
|
pub(crate) consumed_pointer_buttons: HashSet<u32>,
|
||||||
pub(crate) focus_history: ferese_core::FocusHistory,
|
pub(crate) focus_history: ferese_core::FocusHistory,
|
||||||
pub(crate) focus_cycle: Option<ferese_core::FocusCycle>,
|
pub(crate) focus_cycle: Option<ferese_core::FocusCycle>,
|
||||||
column_width_presets: Vec<ColumnWidth>,
|
column_width_presets: Vec<ColumnWidth>,
|
||||||
@@ -771,7 +771,7 @@ impl Ferese {
|
|||||||
tab_indicator: tab_indicator::TabIndicatorState::default(),
|
tab_indicator: tab_indicator::TabIndicatorState::default(),
|
||||||
tab_indicator_settings: config.tab_indicator,
|
tab_indicator_settings: config.tab_indicator,
|
||||||
tab_bars: HashMap::new(),
|
tab_bars: HashMap::new(),
|
||||||
tab_buttons: HashSet::new(),
|
consumed_pointer_buttons: HashSet::new(),
|
||||||
focus_history: Default::default(),
|
focus_history: Default::default(),
|
||||||
focus_cycle: None,
|
focus_cycle: None,
|
||||||
column_width_presets: config.column_width_presets,
|
column_width_presets: config.column_width_presets,
|
||||||
|
|||||||
@@ -388,6 +388,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
state.focus_effect = crate::config::FocusEffectSettings {
|
state.focus_effect = crate::config::FocusEffectSettings {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
|
blur: true,
|
||||||
active_opacity: 0.9,
|
active_opacity: 0.9,
|
||||||
inactive_opacity: 0.6,
|
inactive_opacity: 0.6,
|
||||||
inactive_dim: 0.2,
|
inactive_dim: 0.2,
|
||||||
|
|||||||
@@ -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.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.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.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.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 |
|
| `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 |
|
| `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
|
windows use the same opacity settings. Opening and closing fades multiply window
|
||||||
opacity. Reduced motion disables the focus transition.
|
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
|
Settings → Wallpaper shows the current image and both bundled defaults. Use
|
||||||
**Apply to** to change both modes or just one. **Match appearance** restores the
|
**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:
|
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.
|
border color and keep its transparency.
|
||||||
Automatic accent endpoints are adjusted for contrast against the theme's surfaces.
|
Automatic accent endpoints are adjusted for contrast against the theme's surfaces.
|
||||||
|
|
||||||
In Settings → Appearance, choose **Gradient** or **Solid** for **Accent style**
|
In Settings → Appearance, **Accent style** controls the gradient used by focus
|
||||||
and **Inactive border style**. In KDL, `theme.focus-ring.style` and
|
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
|
`theme.border.style` accept `"auto"` (default) or `"solid"`. Solid disables the
|
||||||
gradient, including any explicit endpoints, without deleting them from the config.
|
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`.
|
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`.
|
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
|
Angles are clockwise: 0 is left-to-right, 90 top-to-bottom. Explicit gradients
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ environment for screenshots to work.
|
|||||||
Text input is available by default; Ctrl+Alt+Escape releases an active shortcut
|
Text input is available by default; Ctrl+Alt+Escape releases an active shortcut
|
||||||
inhibitor. Use `feresectl --help` for runtime control commands.
|
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
|
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
|
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
|
keep circular outlines. The measured profile has small tangent and curvature
|
||||||
|
|||||||
@@ -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
|
With an Activities item configured, running tasks can occupy the collapsed and
|
||||||
peek states, and the hub gains an Activities tab. **Visible activities** 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).
|
see [Activities configuration](configuration/activities.md).
|
||||||
|
|
||||||
The Workspaces view shows miniature window layouts. Click a tile to switch
|
The Workspaces view shows miniature window layouts. Click a tile to switch
|
||||||
|
|||||||
@@ -315,6 +315,20 @@ impl App {
|
|||||||
.padding(12)
|
.padding(12)
|
||||||
.class(crate::visuals::surface(palette.card, 14.)),
|
.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(),
|
PanelPage::Arrange => return self.panel_workspace(),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ impl Page {
|
|||||||
Self::Appearance => "A desktop that feels like yours.",
|
Self::Appearance => "A desktop that feels like yours.",
|
||||||
Self::Accessibility => "Make the desktop easier to see and use.",
|
Self::Accessibility => "Make the desktop easier to see and use.",
|
||||||
Self::Wallpaper => "Set the scene for your workspace.",
|
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::Notifications => "Stay informed on your terms.",
|
||||||
Self::LockScreen => "Your desktop, safely put away.",
|
Self::LockScreen => "Your desktop, safely put away.",
|
||||||
Self::Windows => "Window layouts, spacing and focus effects.",
|
Self::Windows => "Window layouts, spacing and focus effects.",
|
||||||
@@ -392,19 +392,6 @@ pub fn fields(page: Page) -> Vec<Field> {
|
|||||||
"system follows your desktop timezone; or enter an IANA timezone.",
|
"system follows your desktop timezone; or enter an IANA timezone.",
|
||||||
"system",
|
"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(
|
text(
|
||||||
"theme.accent",
|
"theme.accent",
|
||||||
"Accent color",
|
"Accent color",
|
||||||
@@ -418,37 +405,10 @@ pub fn fields(page: Page) -> Vec<Field> {
|
|||||||
"auto",
|
"auto",
|
||||||
&[("auto", "Gradient"), ("solid", "Solid")],
|
&[("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(
|
range(
|
||||||
"theme.material.blur_radius",
|
"theme.material.blur_radius",
|
||||||
"Background blur",
|
"Background blur",
|
||||||
"Softness behind translucent surfaces.",
|
"Softness behind translucent shell surfaces and windows. Zero disables blur.",
|
||||||
RangeSpec {
|
RangeSpec {
|
||||||
default: 12.0,
|
default: 12.0,
|
||||||
min: 0.0,
|
min: 0.0,
|
||||||
@@ -464,19 +424,6 @@ pub fn fields(page: Page) -> Vec<Field> {
|
|||||||
"Use the name of an installed font family.",
|
"Use the name of an installed font family.",
|
||||||
"Inter",
|
"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![
|
Page::Wallpaper => vec![
|
||||||
text(
|
text(
|
||||||
@@ -521,6 +468,39 @@ pub fn fields(page: Page) -> Vec<Field> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
Page::Bar => 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(
|
range(
|
||||||
"panels.0.geometry.height",
|
"panels.0.geometry.height",
|
||||||
"Height",
|
"Height",
|
||||||
@@ -651,6 +631,32 @@ pub fn fields(page: Page) -> Vec<Field> {
|
|||||||
integer: false,
|
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(
|
toggle(
|
||||||
"appearance.focus_effect.enabled",
|
"appearance.focus_effect.enabled",
|
||||||
"Window focus effects",
|
"Window focus effects",
|
||||||
@@ -683,6 +689,12 @@ pub fn fields(page: Page) -> Vec<Field> {
|
|||||||
integer: false,
|
integer: false,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
toggle(
|
||||||
|
"appearance.focus_effect.blur",
|
||||||
|
"Window blur",
|
||||||
|
"Soften the background behind translucent windows. Adjust the amount in Appearance.",
|
||||||
|
true,
|
||||||
|
),
|
||||||
range(
|
range(
|
||||||
"appearance.focus_effect.inactive_dim",
|
"appearance.focus_effect.inactive_dim",
|
||||||
"Inactive window dimming",
|
"Inactive window dimming",
|
||||||
@@ -770,12 +782,6 @@ pub fn fields(page: Page) -> Vec<Field> {
|
|||||||
integer: true,
|
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(
|
toggle(
|
||||||
"input.touchpad.tap",
|
"input.touchpad.tap",
|
||||||
"Tap to click",
|
"Tap to click",
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ pub(super) enum Message {
|
|||||||
pub(super) struct Launcher {
|
pub(super) struct Launcher {
|
||||||
pub id: window::Id,
|
pub id: window::Id,
|
||||||
pub(super) output: String,
|
pub(super) output: String,
|
||||||
top: f32,
|
|
||||||
input: cosmic::widget::Id,
|
input: cosmic::widget::Id,
|
||||||
query: String,
|
query: String,
|
||||||
category: Option<Category>,
|
category: Option<Category>,
|
||||||
@@ -138,7 +137,13 @@ impl FereseShell {
|
|||||||
};
|
};
|
||||||
let name = output.name.clone().unwrap_or(name);
|
let name = output.name.clone().unwrap_or(name);
|
||||||
let wl_output = output.output.clone();
|
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 dismissed = Task::batch([self.destroy_menu(), self.destroy_system_modal(true)]);
|
||||||
let id = window::Id::unique();
|
let id = window::Id::unique();
|
||||||
let mut progress = AnimatedValue::new(0.);
|
let mut progress = AnimatedValue::new(0.);
|
||||||
@@ -151,7 +156,6 @@ impl FereseShell {
|
|||||||
self.launcher = Some(Launcher {
|
self.launcher = Some(Launcher {
|
||||||
id,
|
id,
|
||||||
output: name,
|
output: name,
|
||||||
top,
|
|
||||||
input: cosmic::widget::Id::unique(),
|
input: cosmic::widget::Id::unique(),
|
||||||
query: String::new(),
|
query: String::new(),
|
||||||
category: None,
|
category: None,
|
||||||
@@ -191,11 +195,15 @@ impl FereseShell {
|
|||||||
id,
|
id,
|
||||||
layer: Layer::Overlay,
|
layer: Layer::Overlay,
|
||||||
keyboard_interactivity: KeyboardInteractivity::Exclusive,
|
keyboard_interactivity: KeyboardInteractivity::Exclusive,
|
||||||
anchor: Anchor::TOP | Anchor::BOTTOM | Anchor::LEFT | Anchor::RIGHT,
|
anchor: Anchor::TOP,
|
||||||
output: IcedOutput::Output(wl_output.clone()),
|
output: IcedOutput::Output(wl_output.clone()),
|
||||||
namespace: "ferese-launcher".into(),
|
namespace: "ferese-launcher".into(),
|
||||||
exclusive_zone: -1,
|
exclusive_zone: -1,
|
||||||
size: Some((None, None)),
|
size: Some((Some(width), Some(height))),
|
||||||
|
margin: IcedMargin {
|
||||||
|
top,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
Some(Box::new(move |app| app.view_launcher(id))),
|
Some(Box::new(move |app| app.view_launcher(id))),
|
||||||
|
|||||||
@@ -484,15 +484,6 @@ impl FereseShell {
|
|||||||
}
|
}
|
||||||
body = body.push(container(rows).padding([0, 12]));
|
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);
|
let mut background = color_with_opacity(self.config.theme.surface_popover, opacity);
|
||||||
if self.config.theme.high_contrast || self.config.theme.reduce_transparency {
|
if self.config.theme.high_contrast || self.config.theme.reduce_transparency {
|
||||||
background.a = opacity;
|
background.a = opacity;
|
||||||
@@ -521,11 +512,7 @@ impl FereseShell {
|
|||||||
launcher.regions.clone(),
|
launcher.regions.clone(),
|
||||||
))
|
))
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.align_x(cosmic::iced::Alignment::Center)
|
.align_x(cosmic::iced::Alignment::Center);
|
||||||
.padding(cosmic::iced::Padding {
|
|
||||||
top,
|
|
||||||
..Default::default()
|
|
||||||
});
|
|
||||||
let dismiss = message(Message::Dismiss(id));
|
let dismiss = message(Message::Dismiss(id));
|
||||||
let backdrop = mouse_area(Space::new().width(Length::Fill).height(Length::Fill))
|
let backdrop = mouse_area(Space::new().width(Length::Fill).height(Length::Fill))
|
||||||
.on_press(dismiss.clone())
|
.on_press(dismiss.clone())
|
||||||
|
|||||||
Reference in new issue
Block a user