Center focused window titles and simplify Control Center controls
This commit is contained in:
1 parent
e9dcc968e1
commit
f933484c7b
5 files changed
+177
-68
No files matched your search
@@ -20,6 +20,7 @@ pub(crate) struct ShellConfig {
|
||||
#[serde(default)]
|
||||
pub(crate) struct StatusConfig {
|
||||
pub(crate) battery_percentage: bool,
|
||||
pub(crate) window_title: bool,
|
||||
pub(crate) low_battery_threshold: u8,
|
||||
pub(crate) settings_command: Option<Vec<String>>,
|
||||
}
|
||||
@@ -28,6 +29,7 @@ impl Default for StatusConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
battery_percentage: true,
|
||||
window_title: true,
|
||||
low_battery_threshold: 20,
|
||||
settings_command: Some(vec!["ferese-settings".into()]),
|
||||
}
|
||||
|
||||
@@ -1229,7 +1229,6 @@ impl FereseShell {
|
||||
size_limits: Limits::NONE,
|
||||
..Default::default()
|
||||
},
|
||||
|
||||
Some(Box::new(Self::view_note_drag)),
|
||||
);
|
||||
|
||||
@@ -1360,7 +1359,6 @@ impl FereseShell {
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
|
||||
Some(Box::new(move |app| app.view_note(¬e_id, id))),
|
||||
);
|
||||
|
||||
@@ -1586,7 +1584,6 @@ impl FereseShell {
|
||||
..Default::default()
|
||||
}
|
||||
},
|
||||
|
||||
Some(Box::new(move |app| app.view_desktop_clock(id))),
|
||||
);
|
||||
|
||||
@@ -2199,11 +2196,47 @@ impl FereseShell {
|
||||
]
|
||||
.spacing(8)
|
||||
.align_y(cosmic::iced::Alignment::Center);
|
||||
let available = self
|
||||
.outputs
|
||||
.iter()
|
||||
.find(|output| output.bar == id)
|
||||
.and_then(|output| output.size)
|
||||
.map_or(0., |(width, _)| width as f32)
|
||||
- 2. * (shell_theme.panel_padding + shell_theme.bar_margin_horizontal as f32);
|
||||
let title_width = (available - 920.).clamp(0., 360.);
|
||||
let title = if self.config.status.window_title && title_width > 0. {
|
||||
focused_bar_title(&self.snapshot, focused_output)
|
||||
} else {
|
||||
""
|
||||
};
|
||||
let center = text(title)
|
||||
.size(bar.text_size)
|
||||
.width(title_width)
|
||||
.height(bar.control_height)
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.wrapping(cosmic::iced::widget::text::Wrapping::None)
|
||||
.ellipsize(cosmic::iced::widget::text::Ellipsize::End(
|
||||
cosmic::iced::advanced::text::EllipsizeHeightLimit::Lines(1),
|
||||
))
|
||||
.class(theme::Text::Color(foreground));
|
||||
let right = cosmic::iced::widget::scrollable(container(right).width(Length::Shrink))
|
||||
.direction(cosmic::iced::widget::scrollable::Direction::Horizontal(
|
||||
cosmic::iced::widget::scrollable::Scrollbar::default()
|
||||
.width(0)
|
||||
.scroller_width(0),
|
||||
))
|
||||
.anchor_right()
|
||||
.width(Length::Shrink)
|
||||
.height(bar.control_height);
|
||||
let content = row![
|
||||
container(left).width(Length::Fill),
|
||||
container(right).width(Length::Shrink),
|
||||
center,
|
||||
container(right)
|
||||
.width(Length::Fill)
|
||||
.align_x(alignment::Horizontal::Right),
|
||||
]
|
||||
.spacing(16)
|
||||
.spacing(8)
|
||||
.align_y(cosmic::iced::Alignment::Center)
|
||||
.height(Length::Fill);
|
||||
|
||||
@@ -2362,6 +2395,26 @@ fn workspace_indicator(
|
||||
.into()
|
||||
}
|
||||
|
||||
fn focused_bar_title<'a>(
|
||||
snapshot: &'a ShellSnapshot,
|
||||
output: Option<&control::OutputSnapshot>,
|
||||
) -> &'a str {
|
||||
let Some(output) = output else {
|
||||
return "";
|
||||
};
|
||||
snapshot
|
||||
.windows
|
||||
.iter()
|
||||
.find(|window| window.focused && window.workspace == output.active_workspace)
|
||||
.map_or("", |window| {
|
||||
if window.title.trim().is_empty() {
|
||||
&window.app_id
|
||||
} else {
|
||||
&window.title
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn workspace_active_on_bar(workspace: u64, output: Option<&control::OutputSnapshot>) -> bool {
|
||||
output.is_some_and(|output| output.active_workspace == workspace)
|
||||
}
|
||||
@@ -3072,6 +3125,27 @@ mod tests {
|
||||
assert!(!output_bar_hidden(&snapshot, Some(2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bar_title_tracks_focus_and_the_outputs_active_workspace() {
|
||||
let mut snapshot = snapshot_with_fullscreen_window(7);
|
||||
assert_eq!(
|
||||
focused_bar_title(&snapshot, snapshot.outputs.first()),
|
||||
"Test"
|
||||
);
|
||||
snapshot.windows[0].focused = false;
|
||||
assert_eq!(focused_bar_title(&snapshot, snapshot.outputs.first()), "");
|
||||
snapshot.windows[0].focused = true;
|
||||
snapshot.windows[0].workspace = 8;
|
||||
assert_eq!(focused_bar_title(&snapshot, snapshot.outputs.first()), "");
|
||||
snapshot.windows[0].workspace = 7;
|
||||
snapshot.windows[0].title.clear();
|
||||
assert_eq!(
|
||||
focused_bar_title(&snapshot, snapshot.outputs.first()),
|
||||
"dev.ferese.Test"
|
||||
);
|
||||
assert_eq!(focused_bar_title(&snapshot, None), "");
|
||||
}
|
||||
|
||||
fn snapshot_with_fullscreen_window(workspace: u64) -> ShellSnapshot {
|
||||
ShellSnapshot {
|
||||
outputs: vec![OutputSnapshot {
|
||||
|
||||
@@ -652,8 +652,7 @@ impl FereseShell {
|
||||
}
|
||||
if combined || menu.kind == Menu::Notifications {
|
||||
if let Some(n) = &self.status.notifications {
|
||||
let toggle =
|
||||
toggle_row("Do Not Disturb", n.dnd, Action::Dnd(!n.dnd), primary, p);
|
||||
let toggle = toggle_row("Do Not Disturb", n.dnd, Action::Dnd(!n.dnd), theme, p);
|
||||
rows = rows.push(if combined {
|
||||
control_card(toggle, primary, p)
|
||||
} else {
|
||||
@@ -802,7 +801,6 @@ impl FereseShell {
|
||||
}
|
||||
if combined {
|
||||
let mut actions = column::with_capacity(2).spacing(8);
|
||||
let mut power = row::with_capacity(3).spacing(6).width(Length::Fill);
|
||||
if self
|
||||
.config
|
||||
.status
|
||||
@@ -818,33 +816,6 @@ impl FereseShell {
|
||||
p,
|
||||
));
|
||||
}
|
||||
if self.status.poweroff {
|
||||
power = power.push(footer_button(
|
||||
"Power off…",
|
||||
Message::ConfirmPower(Action::Poweroff),
|
||||
primary,
|
||||
p,
|
||||
));
|
||||
}
|
||||
if self.status.reboot {
|
||||
power = power.push(footer_button(
|
||||
"Restart…",
|
||||
Message::ConfirmPower(Action::Reboot),
|
||||
primary,
|
||||
p,
|
||||
));
|
||||
}
|
||||
if self.status.suspend {
|
||||
power = power.push(footer_button(
|
||||
"Suspend…",
|
||||
Message::ConfirmPower(Action::Suspend),
|
||||
primary,
|
||||
p,
|
||||
));
|
||||
}
|
||||
if self.status.poweroff || self.status.reboot || self.status.suspend {
|
||||
actions = actions.push(power);
|
||||
}
|
||||
rows = rows.push(control_card(actions.into(), primary, p));
|
||||
}
|
||||
if !menu.kind.available(&self.status) {
|
||||
@@ -1022,33 +993,23 @@ fn connection_control<'a>(
|
||||
source: &'static [u8],
|
||||
enabled: bool,
|
||||
action: Option<Action>,
|
||||
theme: ShellTheme,
|
||||
palette: ShellTheme,
|
||||
opacity: f32,
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
let foreground = color_with_opacity(theme.text_primary, opacity);
|
||||
let icon_accent = color_with_opacity(theme.accent, opacity);
|
||||
let accent = color_with_opacity(theme.accent, opacity * 0.14);
|
||||
let icon = container(accented_icon(source, 18, foreground, icon_accent))
|
||||
let foreground = color_with_opacity(palette.text_primary, opacity);
|
||||
let icon_color = color_with_opacity(
|
||||
if enabled {
|
||||
palette.accent
|
||||
} else {
|
||||
palette.text_muted
|
||||
},
|
||||
opacity,
|
||||
);
|
||||
let icon = container(accented_icon(source, 18, icon_color, icon_color))
|
||||
.width(32)
|
||||
.height(32)
|
||||
.align_x(alignment::Horizontal::Center)
|
||||
.align_y(alignment::Vertical::Center)
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: enabled.then_some(Background::Color(accent)),
|
||||
icon_color: Some(color_with_opacity(
|
||||
if enabled {
|
||||
theme.text_primary
|
||||
} else {
|
||||
theme.text_muted
|
||||
},
|
||||
opacity,
|
||||
)),
|
||||
border: Border {
|
||||
radius: motion::radius(20.0).into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}));
|
||||
.align_y(alignment::Vertical::Center);
|
||||
let content = column![
|
||||
icon,
|
||||
text(label).size(13),
|
||||
@@ -1058,14 +1019,12 @@ fn connection_control<'a>(
|
||||
.height(18)
|
||||
.wrapping(cosmic::iced::widget::text::Wrapping::None)
|
||||
.class(theme::Text::Color(color_with_opacity(
|
||||
theme.text_muted,
|
||||
palette.text_muted,
|
||||
opacity
|
||||
)))
|
||||
]
|
||||
.spacing(6)
|
||||
.width(Length::Fill);
|
||||
// Battery is informational, not a toggle. Match its tile's layout without
|
||||
// giving it a misleading clickable/disabled-button appearance.
|
||||
let Some(action) = action else {
|
||||
return container(content)
|
||||
.width(Length::FillPortion(1))
|
||||
@@ -1074,8 +1033,9 @@ fn connection_control<'a>(
|
||||
};
|
||||
container(motion::button(
|
||||
button::custom(content)
|
||||
.width(Length::FillPortion(1))
|
||||
.width(Length::Fill)
|
||||
.padding(4)
|
||||
.name(format!("{label}: {}", if enabled { "on" } else { "off" }))
|
||||
.on_press(cosmic::Action::App(Message::Control(action))),
|
||||
foreground,
|
||||
false,
|
||||
@@ -1267,21 +1227,87 @@ fn footer_button<'a>(
|
||||
)
|
||||
}
|
||||
|
||||
fn shell_switch<'a>(
|
||||
label: &str,
|
||||
enabled: bool,
|
||||
action: Action,
|
||||
palette: ShellTheme,
|
||||
opacity: f32,
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
let track = if enabled {
|
||||
color(palette.accent)
|
||||
} else {
|
||||
Color {
|
||||
a: 0.22,
|
||||
..color(palette.text_primary)
|
||||
}
|
||||
};
|
||||
let thumb = if enabled {
|
||||
ferese_theme::foreground(track, color(palette.text_primary))
|
||||
} else {
|
||||
color(palette.text_primary)
|
||||
};
|
||||
let knob = container(text(""))
|
||||
.width(16)
|
||||
.height(16)
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: Some(
|
||||
Color {
|
||||
a: opacity,
|
||||
..thumb
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
border: Border {
|
||||
radius: motion::radius(8.).into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}));
|
||||
let toggle = container(knob)
|
||||
.width(36)
|
||||
.height(20)
|
||||
.padding(2)
|
||||
.align_x(if enabled {
|
||||
alignment::Horizontal::Right
|
||||
} else {
|
||||
alignment::Horizontal::Left
|
||||
})
|
||||
.class(theme::Container::custom(move |_| container::Style {
|
||||
background: Some(
|
||||
Color {
|
||||
a: track.a * opacity,
|
||||
..track
|
||||
}
|
||||
.into(),
|
||||
),
|
||||
border: Border {
|
||||
radius: motion::radius(10.).into(),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
}));
|
||||
motion::button(
|
||||
button::custom(toggle)
|
||||
.padding(4)
|
||||
.name(format!("{label}: {}", if enabled { "on" } else { "off" }))
|
||||
.on_press(cosmic::Action::App(Message::Control(action))),
|
||||
color(palette.text_primary),
|
||||
false,
|
||||
opacity,
|
||||
)
|
||||
}
|
||||
|
||||
fn toggle_row<'a>(
|
||||
label: &'a str,
|
||||
on: bool,
|
||||
action: Action,
|
||||
foreground: Color,
|
||||
palette: ShellTheme,
|
||||
opacity: f32,
|
||||
) -> Element<'a, cosmic::Action<Message>> {
|
||||
row![
|
||||
text(label).width(Length::Fill).size(14),
|
||||
menu_button(
|
||||
if on { "On" } else { "Off" },
|
||||
Message::Control(action),
|
||||
foreground,
|
||||
opacity
|
||||
)
|
||||
shell_switch(label, on, action, palette, opacity)
|
||||
]
|
||||
.align_y(Alignment::Center)
|
||||
.into()
|
||||
|
||||
Reference in new issue
Block a user