refine shell workspace indicators

This commit is contained in:
Samuel Onoja committed 2026-09-25 07:55:43 +01:00
1 parent 4a6d91980c
commit dc3ef13501
2 files changed
+89 -27

No files matched your search

+2 -6
View File
@@ -28,7 +28,6 @@ pub(crate) struct ShellSnapshot {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct OutputSnapshot { pub(crate) struct OutputSnapshot {
pub(crate) id: u64, pub(crate) id: u64,
pub(crate) name: String,
pub(crate) active_workspace: u64, pub(crate) active_workspace: u64,
pub(crate) focused: bool, pub(crate) focused: bool,
} }
@@ -37,7 +36,6 @@ pub(crate) struct OutputSnapshot {
pub(crate) struct WorkspaceSnapshot { pub(crate) struct WorkspaceSnapshot {
pub(crate) id: u64, pub(crate) id: u64,
pub(crate) output: Option<u64>, pub(crate) output: Option<u64>,
pub(crate) name: String,
pub(crate) active: bool, pub(crate) active: bool,
} }
@@ -182,13 +180,12 @@ impl Dispatch<FereseShellV1, ()> for ControlState {
ferese_shell_v1::Event::Output { ferese_shell_v1::Event::Output {
output_hi, output_hi,
output_lo, output_lo,
name, name: _,
active_workspace_hi, active_workspace_hi,
active_workspace_lo, active_workspace_lo,
focused, focused,
} => state.pending.outputs.push(OutputSnapshot { } => state.pending.outputs.push(OutputSnapshot {
id: join_id(output_hi, output_lo), id: join_id(output_hi, output_lo),
name,
active_workspace: join_id(active_workspace_hi, active_workspace_lo), active_workspace: join_id(active_workspace_hi, active_workspace_lo),
focused: focused != 0, focused: focused != 0,
}), }),
@@ -197,7 +194,7 @@ impl Dispatch<FereseShellV1, ()> for ControlState {
workspace_lo, workspace_lo,
output_hi, output_hi,
output_lo, output_lo,
name, name: _,
active, active,
} => { } => {
let output = join_id(output_hi, output_lo); let output = join_id(output_hi, output_lo);
@@ -205,7 +202,6 @@ impl Dispatch<FereseShellV1, ()> for ControlState {
state.pending.workspaces.push(WorkspaceSnapshot { state.pending.workspaces.push(WorkspaceSnapshot {
id: join_id(workspace_hi, workspace_lo), id: join_id(workspace_hi, workspace_lo),
output: (output != 0).then_some(output), output: (output != 0).then_some(output),
name,
active: active != 0, active: active != 0,
}); });
} }
+87 -21
View File
@@ -32,6 +32,14 @@ const APP_ID: &str = "dev.ferese.Shell";
const BAR_HEIGHT: u32 = 32; const BAR_HEIGHT: u32 = 32;
const BAR_MARGIN: i32 = 8; const BAR_MARGIN: i32 = 8;
const EXCLUSIVE_ZONE: i32 = BAR_HEIGHT as i32; const EXCLUSIVE_ZONE: i32 = BAR_HEIGHT as i32;
const BAR_TEXT_SIZE: u16 = (BAR_HEIGHT * 3 / 8) as u16;
const BAR_ICON_SIZE: u16 = (BAR_HEIGHT / 2) as u16;
const CONTROL_HEIGHT: f32 = BAR_HEIGHT as f32 * 0.75;
const WORKSPACE_HIT_WIDTH: f32 = BAR_HEIGHT as f32 * 0.6875;
const ACTIVE_MARKER_WIDTH: f32 = BAR_HEIGHT as f32 * 0.5;
const ACTIVE_MARKER_HEIGHT: f32 = BAR_HEIGHT as f32 * 0.125;
const DOT_MARKER_SIZE: f32 = BAR_HEIGHT as f32 * 0.1875;
const EMPTY_MARKER_SIZE: f32 = BAR_HEIGHT as f32 * 0.125;
fn main() -> cosmic::iced::Result { fn main() -> cosmic::iced::Result {
let settings = Settings::default() let settings = Settings::default()
@@ -206,13 +214,13 @@ impl FereseShell {
.or_else(|| self.snapshot.outputs.first()); .or_else(|| self.snapshot.outputs.first());
let focused_output_id = focused_output.map(|output| output.id); let focused_output_id = focused_output.map(|output| output.id);
let mut workspace_row = row::with_capacity(self.snapshot.workspaces.len() + 1) let mut workspace_row = row::with_capacity(self.snapshot.workspaces.len() + 1)
.spacing(2) .spacing(1)
.align_y(cosmic::iced::Alignment::Center); .align_y(cosmic::iced::Alignment::Center);
workspace_row = workspace_row.push( workspace_row = workspace_row.push(
button::suggested("F") button::suggested("F")
.font_size(12) .font_size(BAR_TEXT_SIZE)
.height(24) .height(CONTROL_HEIGHT)
.padding([2, 9]) .padding([2, 9])
.on_press(cosmic::Action::App(Message::EnterOverview)), .on_press(cosmic::Action::App(Message::EnterOverview)),
); );
@@ -222,16 +230,21 @@ impl FereseShell {
.iter() .iter()
.filter(|workspace| workspace.output == focused_output_id) .filter(|workspace| workspace.output == focused_output_id)
{ {
let button = if workspace.active { let occupied = self
button::suggested(workspace.name.clone()) .snapshot
} else { .windows
button::text(workspace.name.clone()) .iter()
}; .any(|window| window.workspace == workspace.id);
let indicator = workspace_indicator(workspace.active, occupied);
let workspace_button = button::custom(indicator)
.height(CONTROL_HEIGHT)
.padding(0)
.class(theme::Button::Transparent)
.on_press(cosmic::Action::App(Message::ActivateWorkspace(
workspace.id,
)));
workspace_row = workspace_row = workspace_row.push(workspace_button);
workspace_row.push(button.font_size(12).height(24).padding([2, 8]).on_press(
cosmic::Action::App(Message::ActivateWorkspace(workspace.id)),
));
} }
let app_name = self let app_name = self
@@ -245,18 +258,14 @@ impl FereseShell {
}) })
.map(display_app_name) .map(display_app_name)
.unwrap_or_else(|| "Desktop".to_owned()); .unwrap_or_else(|| "Desktop".to_owned());
let left = row![workspace_row, text(app_name).size(12)] let left = row![workspace_row, text(app_name).size(BAR_TEXT_SIZE)]
.spacing(12) .spacing(12)
.align_y(cosmic::iced::Alignment::Center); .align_y(cosmic::iced::Alignment::Center);
let center = text(&self.clock).size(13); let center = text(&self.clock).size(BAR_TEXT_SIZE);
let output_hint = focused_output
.map(|output| output.name.as_str())
.unwrap_or("No display");
let right = row![ let right = row![
text("◌").size(16), text("◌").size(BAR_ICON_SIZE),
text("♪").size(15), text("♪").size(BAR_ICON_SIZE),
text(output_hint).size(11), text("●").size(BAR_ICON_SIZE),
text("●").size(10),
] ]
.spacing(10) .spacing(10)
.align_y(cosmic::iced::Alignment::Center); .align_y(cosmic::iced::Alignment::Center);
@@ -283,6 +292,63 @@ impl FereseShell {
} }
} }
fn workspace_indicator(active: bool, occupied: bool) -> Element<'static, cosmic::Action<Message>> {
let (width, height, style) = if active {
(
ACTIVE_MARKER_WIDTH,
ACTIVE_MARKER_HEIGHT,
active_workspace_style as fn(&cosmic::Theme) -> container::Style,
)
} else if occupied {
(
DOT_MARKER_SIZE,
DOT_MARKER_SIZE,
occupied_workspace_style as fn(&cosmic::Theme) -> container::Style,
)
} else {
(
EMPTY_MARKER_SIZE,
EMPTY_MARKER_SIZE,
empty_workspace_style as fn(&cosmic::Theme) -> container::Style,
)
};
let marker = container(text(""))
.width(width)
.height(height)
.class(theme::Container::custom(style));
container(marker)
.width(WORKSPACE_HIT_WIDTH)
.height(CONTROL_HEIGHT)
.align_x(alignment::Horizontal::Center)
.align_y(alignment::Vertical::Center)
.into()
}
fn active_workspace_style(_theme: &cosmic::Theme) -> container::Style {
workspace_marker_style(Color::from_rgb8(91, 140, 255), 2.0)
}
fn occupied_workspace_style(_theme: &cosmic::Theme) -> container::Style {
workspace_marker_style(Color::from_rgba8(221, 228, 240, 0.68), 3.0)
}
fn empty_workspace_style(_theme: &cosmic::Theme) -> container::Style {
workspace_marker_style(Color::from_rgba8(221, 228, 240, 0.25), 2.0)
}
fn workspace_marker_style(color: Color, radius: f32) -> container::Style {
container::Style {
background: Some(Background::Color(color)),
border: Border {
radius: radius.into(),
..Border::default()
},
snap: true,
..container::Style::default()
}
}
fn display_app_name(window: &control::WindowSnapshot) -> String { fn display_app_name(window: &control::WindowSnapshot) -> String {
let identity = window let identity = window
.app_id .app_id