Merge remote-tracking branch 'upstream/master' into local/yoda-main

# Conflicts:
#	Cargo.lock
#	Cargo.toml
#	src/main.rs
#	src/menu.rs
#	src/terminal.rs
#	src/terminal_box.rs
This commit is contained in:
Lionel DARNIS 2026-08-19 22:02:40 +02:00
commit eda2fb1d58
38 changed files with 1406 additions and 1292 deletions

View file

@ -571,6 +571,8 @@ pub enum Message {
ShowAdvancedFontSettings(bool),
ShowHeaderBar(bool),
SshConnect(usize),
ShowPaneBorders(bool),
SyntaxTheme(ColorSchemeKind, usize),
SystemThemeChange,
TabNewInheritWorkingDirectory(bool),
@ -833,7 +835,7 @@ impl App {
// Update terminal window background color
{
let color = Color::from(theme.cosmic().background(false).base);
let color = Color::from(theme.cosmic().background(theme.transparent).base);
let bytes = color.into_rgba8();
let data = u32::from(bytes[2])
| (u32::from(bytes[1]) << 8)
@ -1483,6 +1485,7 @@ impl App {
}
fn settings(&self) -> Element<'_, Message> {
let t = self.core().system_theme();
let app_theme_selected = match self.config.app_theme {
AppTheme::Dark => 1,
AppTheme::Light => 2,
@ -1566,13 +1569,13 @@ impl App {
}),
),
)
.add(
.add_maybe((!t.transparent).then(|| {
widget::settings::item::builder(fl!("opacity"))
.description(format!("{}%", self.config.opacity))
.control(widget::slider(0..=100, self.config.opacity, |opacity| {
Message::Opacity(opacity)
})),
);
}))
}));
let mut font_section = widget::settings::section()
.title(fl!("font"))
@ -1658,10 +1661,16 @@ impl App {
font_section = font_section.add(advanced_font_settings());
}
let splits_section = widget::settings::section().title(fl!("splits")).add(
widget::settings::item::builder(fl!("focus-follow-mouse"))
.toggler(self.config.focus_follow_mouse, Message::FocusFollowMouse),
);
let splits_section = widget::settings::section()
.title(fl!("splits"))
.add(
widget::settings::item::builder(fl!("focus-follow-mouse"))
.toggler(self.config.focus_follow_mouse, Message::FocusFollowMouse),
)
.add(
widget::settings::item::builder(fl!("show-pane-borders"))
.toggler(self.config.show_pane_borders, Message::ShowPaneBorders),
);
let advanced_section = widget::settings::section()
.title(fl!("advanced"))
@ -3045,7 +3054,8 @@ impl Application for App {
}
Message::PaneClicked(pane) => {
self.pane_model.set_focus(pane);
return self.update_title(Some(pane));
let title_task = self.update_title(Some(pane));
return Task::batch([self.update_focus(), title_task]);
}
Message::PaneSplit(axis) => {
let result = self.pane_model.panes.split(
@ -3295,6 +3305,11 @@ impl Application for App {
tab_new_inherit_working_directory
);
}
Message::ShowPaneBorders(show_pane_borders) => {
if show_pane_borders != self.config.show_pane_borders {
config_set!(show_pane_borders, show_pane_borders);
}
}
Message::UseBrightBold(use_bright_bold) => {
if use_bright_bold != self.config.use_bright_bold {
config_set!(use_bright_bold, use_bright_bold);
@ -3503,7 +3518,7 @@ impl Application for App {
tasks.push(cosmic::task::message(Message::Surface(
cosmic::surface::action::app_popup(
|_: &Self| cosmic::surface::action::LiveSettings::default(),
|_| Default::default(),
move |_app: &mut Self| {
use cosmic::cctk::wayland_protocols::xdg::shell::client::xdg_positioner::{Anchor, Gravity};
use cosmic::iced::runtime::platform_specific::wayland::popup::{SctkPopupSettings, SctkPositioner};
@ -4131,8 +4146,23 @@ impl Application for App {
/// Creates a view after each update.
fn view(&self) -> Element<'_, Self::Message> {
let cosmic_theme::Spacing { space_xxs, .. } = self.core().system_theme().cosmic().spacing;
let t = self.core().system_theme();
let cosmic = t.cosmic();
let cosmic_theme::Spacing {
space_xxxs,
space_xxs,
..
} = cosmic.spacing;
let show_pane_borders =
self.config.show_pane_borders && self.pane_model.panes.panes.len() > 1;
let pane_corner_radius: iced::border::Radius = {
let pad = f32::from(space_xxxs) / 2.0;
cosmic
.radius_s()
.map(|r| if r > 0.0 { r + pad } else { 0.0 })
.into()
};
let pane_grid = PaneGrid::new(&self.pane_model.panes, |pane, tab_model, _is_maximized| {
let mut tab_column = widget::column::with_capacity(1);
@ -4181,10 +4211,16 @@ impl Application for App {
.on_open_hyperlink(Some(Box::new(Message::LaunchUrl)))
.on_window_focused(|| Message::WindowFocused)
.on_window_unfocused(|| Message::WindowUnfocused)
.opacity(self.config.opacity_ratio())
.opacity(if t.transparent {
t.cosmic().alpha_map.blurred_alpha(t.cosmic().frosted)
} else {
self.config.opacity_ratio()
})
.padding(space_xxs)
.sharp_corners(self.core.window.sharp_corners)
.show_headerbar(self.config.show_headerbar);
.show_headerbar(self.config.show_headerbar)
.pane_border_radius(show_pane_borders.then_some(pane_corner_radius))
.border(pane_border(cosmic, t.transparent, show_pane_borders));
if self.config.focus_follow_mouse {
terminal_box = terminal_box.on_mouse_enter(move || Message::MouseEnter(pane));
@ -4327,7 +4363,14 @@ impl Application for App {
.on_drag(Message::PaneDragged);
//TODO: apply window border radius xs at bottom of window
pane_grid.into()
if show_pane_borders {
// Each pane draws its own border stroke. Painting a filled
// container behind the grid instead would stack its alpha with the
// translucent panes and wash out the blurred backdrop.
pane_grid.spacing(space_xxxs).into()
} else {
pane_grid.into()
}
}
fn system_theme_update(
@ -4415,3 +4458,80 @@ fn is_wayland() -> bool {
Some(cosmic::app::cosmic::WindowingSystem::Wayland)
)
}
/// Divider color painted behind the pane grid to form pane borders.
///
/// When blur is active the transparent container must be used, otherwise the
/// fill is opaque and hides the blurred desktop behind the window.
fn pane_divider_color(
cosmic: &cosmic_theme::Theme,
transparent: bool,
) -> cosmic::cosmic_theme::palette::Srgba {
cosmic.background(transparent).divider
}
/// Border stroke drawn around each terminal pane.
///
/// The stroke sits on the pane edge rather than behind it, so the pane
/// interior keeps its own opacity and the blurred desktop stays visible.
fn pane_border(cosmic: &cosmic_theme::Theme, transparent: bool, show: bool) -> iced::Border {
if show {
iced::Border {
color: Color::from(pane_divider_color(cosmic, transparent)),
width: 1.0,
..Default::default()
}
} else {
iced::Border::default()
}
}
#[cfg(test)]
mod tests {
use super::{pane_border, pane_divider_color};
#[test]
fn pane_border_is_drawn_only_when_enabled() {
// With borders off the pane must render exactly as before, so the
// stroke has to be zero width rather than a transparent hairline.
let theme = cosmic::cosmic_theme::Theme::dark_default();
assert_eq!(pane_border(&theme, true, false).width, 0.0);
assert!(pane_border(&theme, true, true).width > 0.0);
}
#[test]
fn pane_border_uses_the_divider_color() {
// The stroke is the only thing marking the pane edge now that nothing
// is painted behind the grid, so it must match the theme divider.
let theme = cosmic::cosmic_theme::Theme::dark_default();
let expected = cosmic::iced::Color::from(pane_divider_color(&theme, true));
assert_eq!(pane_border(&theme, true, true).color, expected);
}
#[test]
fn pane_divider_is_translucent_when_blur_is_active() {
// The divider is painted as a full-window container behind the pane
// grid. With blur active it must keep alpha < 1 so the frosted
// backdrop still shows through instead of a flat opaque fill.
let theme = cosmic::cosmic_theme::Theme::dark_default();
let color = pane_divider_color(&theme, true);
assert!(
color.alpha < 1.0,
"expected translucent divider, got alpha {}",
color.alpha
);
}
#[test]
fn pane_divider_is_opaque_without_blur() {
// Without blur there is nothing to show through, so the divider keeps
// the fully opaque background color.
let theme = cosmic::cosmic_theme::Theme::dark_default();
let color = pane_divider_color(&theme, false);
assert!(
(color.alpha - 1.0).abs() < f32::EPSILON,
"expected opaque divider, got alpha {}",
color.alpha
);
}
}