From d38d3c956d51a8fa4f791fb7b56c192417ac990d Mon Sep 17 00:00:00 2001 From: Csaba Date: Wed, 2 Apr 2025 20:15:43 +0200 Subject: [PATCH] feat(bar): add locked container widget, use accent colour for icons This commit adds a new komorebi widget to indicate whether or not the focused container is locked. This commit also includes an icon colour change on the layer and layout widgets to the accent colour. The commit also renames the locked_window widget to locked_container as it is more suitable. PR: #1394 --- komorebi-bar/src/widgets/komorebi.rs | 134 +++++++++++-- komorebi-bar/src/widgets/komorebi_layout.rs | 2 +- komorebi-bar/src/widgets/widget.rs | 2 +- schema.bar.json | 201 ++++++++++++++++++-- schema.json | 39 ++++ 5 files changed, 341 insertions(+), 37 deletions(-) diff --git a/komorebi-bar/src/widgets/komorebi.rs b/komorebi-bar/src/widgets/komorebi.rs index bdf6fe1d..63a0e04d 100644 --- a/komorebi-bar/src/widgets/komorebi.rs +++ b/komorebi-bar/src/widgets/komorebi.rs @@ -11,7 +11,9 @@ use crate::widgets::widget::BarWidget; use crate::ICON_CACHE; use crate::MAX_LABEL_WIDTH; use crate::MONITOR_INDEX; +use eframe::egui::text::LayoutJob; use eframe::egui::vec2; +use eframe::egui::Align; use eframe::egui::Color32; use eframe::egui::ColorImage; use eframe::egui::Context; @@ -24,6 +26,7 @@ use eframe::egui::RichText; use eframe::egui::Sense; use eframe::egui::Stroke; use eframe::egui::StrokeKind; +use eframe::egui::TextFormat; use eframe::egui::TextureHandle; use eframe::egui::TextureOptions; use eframe::egui::Ui; @@ -55,8 +58,11 @@ pub struct KomorebiConfig { pub layout: Option, /// Configure the Workspace Layer widget pub workspace_layer: Option, - /// Configure the Focused Window widget - pub focused_window: Option, + /// Configure the Focused Container widget + #[serde(alias = "focused_window")] + pub focused_container: Option, + /// Configure the Locked Container widget + pub locked_container: Option, /// Configure the Configuration Switcher widget pub configuration_switcher: Option, } @@ -96,15 +102,26 @@ pub struct KomorebiWorkspaceLayerConfig { #[derive(Copy, Clone, Debug, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] -pub struct KomorebiFocusedWindowConfig { - /// Enable the Komorebi Focused Window widget +pub struct KomorebiFocusedContainerConfig { + /// Enable the Komorebi Focused Container widget pub enable: bool, - /// DEPRECATED: use 'display' instead (Show the icon of the currently focused window) + /// DEPRECATED: use 'display' instead (Show the icon of the currently focused container) pub show_icon: Option, - /// Display format of the currently focused window + /// Display format of the currently focused container pub display: Option, } +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] +pub struct KomorebiLockedContainerConfig { + /// Enable the Komorebi Locked Container widget + pub enable: bool, + /// Display format of the current locked state + pub display: Option, + /// Show the widget event if the layer is unlocked + pub show_when_unlocked: Option, +} + #[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct KomorebiConfigurationSwitcherConfig { @@ -140,15 +157,19 @@ impl From<&KomorebiConfig> for Komorebi { .unwrap_or_default(), mouse_follows_focus: true, work_area_offset: None, - focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY, + focused_container_information: ( + false, + KomorebiNotificationStateContainerInformation::EMPTY, + ), stack_accent: None, monitor_index: MONITOR_INDEX.load(Ordering::SeqCst), monitor_usr_idx_map: HashMap::new(), })), workspaces: value.workspaces, layout: value.layout.clone(), - focused_window: value.focused_window, + focused_container: value.focused_container, workspace_layer: value.workspace_layer, + locked_container: value.locked_container, configuration_switcher, } } @@ -159,8 +180,9 @@ pub struct Komorebi { pub komorebi_notification_state: Rc>, pub workspaces: Option, pub layout: Option, - pub focused_window: Option, + pub focused_container: Option, pub workspace_layer: Option, + pub locked_container: Option, pub configuration_switcher: Option, } @@ -337,7 +359,7 @@ impl BarWidget for Komorebi { if matches!(layer, WorkspaceLayer::Tiling) { let (response, painter) = ui.allocate_painter(size, Sense::hover()); - let color = ui.style().visuals.text_color(); + let color = ctx.style().visuals.selection.stroke.color; let stroke = Stroke::new(1.0, color); let mut rect = response.rect; let corner = @@ -367,7 +389,7 @@ impl BarWidget for Komorebi { } else { let (response, painter) = ui.allocate_painter(size, Sense::hover()); - let color = ui.style().visuals.text_color(); + let color = ctx.style().visuals.selection.stroke.color; let stroke = Stroke::new(1.0, color); let mut rect = response.rect; let corner = @@ -504,18 +526,85 @@ impl BarWidget for Komorebi { } } - if let Some(focused_window) = self.focused_window { - if focused_window.enable { + if let Some(locked_container_config) = self.locked_container { + if locked_container_config.enable { + let is_locked = komorebi_notification_state.focused_container_information.0; + + if locked_container_config + .show_when_unlocked + .unwrap_or_default() + || is_locked + { + let titles = &komorebi_notification_state + .focused_container_information + .1 + .titles; + + if !titles.is_empty() { + let display_format = locked_container_config + .display + .unwrap_or(DisplayFormat::Text); + + let mut layout_job = LayoutJob::simple( + if display_format != DisplayFormat::Text { + if is_locked { + egui_phosphor::regular::LOCK_KEY.to_string() + } else { + egui_phosphor::regular::LOCK_SIMPLE_OPEN.to_string() + } + } else { + String::new() + }, + config.icon_font_id.clone(), + ctx.style().visuals.selection.stroke.color, + 100.0, + ); + + if display_format != DisplayFormat::Icon { + layout_job.append( + if is_locked { "Locked" } else { "Unlocked" }, + 10.0, + TextFormat { + font_id: config.text_font_id.clone(), + color: ctx.style().visuals.text_color(), + valign: Align::Center, + ..Default::default() + }, + ); + } + + config.apply_on_widget(false, ui, |ui| { + if SelectableFrame::new(false) + .show(ui, |ui| ui.add(Label::new(layout_job).selectable(false))) + .clicked() + && komorebi_client::send_batch([ + SocketMessage::FocusMonitorAtCursor, + SocketMessage::ToggleLock, + ]) + .is_err() + { + tracing::error!("could not send ToggleLock"); + } + }); + } + } + } + } + + if let Some(focused_container_config) = self.focused_container { + if focused_container_config.enable { let titles = &komorebi_notification_state .focused_container_information + .1 .titles; + if !titles.is_empty() { config.apply_on_widget(false, ui, |ui| { let icons = &komorebi_notification_state - .focused_container_information + .focused_container_information.1 .icons; let focused_window_idx = komorebi_notification_state - .focused_container_information + .focused_container_information.1 .focused_window_idx; let iter = titles.iter().zip(icons.iter()); @@ -523,13 +612,13 @@ impl BarWidget for Komorebi { for (i, (title, icon)) in iter.enumerate() { let selected = i == focused_window_idx && len != 1; - let text_color = if selected { ctx.style().visuals.selection.stroke.color} else { ui.style().visuals.text_color() }; + let text_color = if selected { ctx.style().visuals.selection.stroke.color } else { ui.style().visuals.text_color() }; if SelectableFrame::new(selected) .show(ui, |ui| { // handle legacy setting - let format = focused_window.display.unwrap_or( - if focused_window.show_icon.unwrap_or(false) { + let format = focused_container_config.display.unwrap_or( + if focused_container_config.show_icon.unwrap_or(false) { DisplayFormat::IconAndText } else { DisplayFormat::Text @@ -632,7 +721,7 @@ pub struct KomorebiNotificationState { bool, )>, pub selected_workspace: String, - pub focused_container_information: KomorebiNotificationStateContainerInformation, + pub focused_container_information: (bool, KomorebiNotificationStateContainerInformation), pub layout: KomorebiLayout, pub hide_empty_workspaces: bool, pub mouse_follows_focus: bool, @@ -800,7 +889,12 @@ impl KomorebiNotificationState { }; } - self.focused_container_information = (&monitor.workspaces()[focused_workspace_idx]).into(); + let focused_workspace = &monitor.workspaces()[focused_workspace_idx]; + let is_focused = focused_workspace + .locked_containers() + .contains(&focused_workspace.focused_container_idx()); + + self.focused_container_information = (is_focused, focused_workspace.into()); } } diff --git a/komorebi-bar/src/widgets/komorebi_layout.rs b/komorebi-bar/src/widgets/komorebi_layout.rs index 495d980c..b8361097 100644 --- a/komorebi-bar/src/widgets/komorebi_layout.rs +++ b/komorebi-bar/src/widgets/komorebi_layout.rs @@ -251,7 +251,7 @@ impl KomorebiLayout { let layout_frame = SelectableFrame::new(false) .show(ui, |ui| { if let DisplayFormat::Icon | DisplayFormat::IconAndText = format { - self.show_icon(false, font_id.clone(), ctx, ui); + self.show_icon(true, font_id.clone(), ctx, ui); } if let DisplayFormat::Text | DisplayFormat::IconAndText = format { diff --git a/komorebi-bar/src/widgets/widget.rs b/komorebi-bar/src/widgets/widget.rs index 1ad0f43d..630a5808 100644 --- a/komorebi-bar/src/widgets/widget.rs +++ b/komorebi-bar/src/widgets/widget.rs @@ -72,7 +72,7 @@ impl WidgetConfig { WidgetConfig::Komorebi(config) => { config.workspaces.as_ref().is_some_and(|w| w.enable) || config.layout.as_ref().is_some_and(|w| w.enable) - || config.focused_window.as_ref().is_some_and(|w| w.enable) + || config.focused_container.as_ref().is_some_and(|w| w.enable) || config .configuration_switcher .as_ref() diff --git a/schema.bar.json b/schema.bar.json index 7b693884..1f4ee472 100644 --- a/schema.bar.json +++ b/schema.bar.json @@ -365,15 +365,15 @@ } } }, - "focused_window": { - "description": "Configure the Focused Window widget", + "focused_container": { + "description": "Configure the Focused Container widget", "type": "object", "required": [ "enable" ], "properties": { "display": { - "description": "Display format of the currently focused window", + "description": "Display format of the currently focused container", "oneOf": [ { "description": "Show only icon", @@ -413,11 +413,11 @@ ] }, "enable": { - "description": "Enable the Komorebi Focused Window widget", + "description": "Enable the Komorebi Focused Container widget", "type": "boolean" }, "show_icon": { - "description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused window)", + "description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused container)", "type": "boolean" } } @@ -508,6 +508,63 @@ } } }, + "locked_container": { + "description": "Configure the Locked Container widget", + "type": "object", + "required": [ + "enable" + ], + "properties": { + "display": { + "description": "Display format of the current locked state", + "oneOf": [ + { + "description": "Show only icon", + "type": "string", + "enum": [ + "Icon" + ] + }, + { + "description": "Show only text", + "type": "string", + "enum": [ + "Text" + ] + }, + { + "description": "Show an icon and text for the selected element, and text on the rest", + "type": "string", + "enum": [ + "TextAndIconOnSelected" + ] + }, + { + "description": "Show both icon and text", + "type": "string", + "enum": [ + "IconAndText" + ] + }, + { + "description": "Show an icon and text for the selected element, and icons on the rest", + "type": "string", + "enum": [ + "IconAndTextOnSelected" + ] + } + ] + }, + "enable": { + "description": "Enable the Komorebi Locked Container widget", + "type": "boolean" + }, + "show_when_unlocked": { + "description": "Show the widget event if the layer is unlocked", + "type": "boolean" + } + } + }, "workspace_layer": { "description": "Configure the Workspace Layer widget", "type": "object", @@ -1775,15 +1832,15 @@ } } }, - "focused_window": { - "description": "Configure the Focused Window widget", + "focused_container": { + "description": "Configure the Focused Container widget", "type": "object", "required": [ "enable" ], "properties": { "display": { - "description": "Display format of the currently focused window", + "description": "Display format of the currently focused container", "oneOf": [ { "description": "Show only icon", @@ -1823,11 +1880,11 @@ ] }, "enable": { - "description": "Enable the Komorebi Focused Window widget", + "description": "Enable the Komorebi Focused Container widget", "type": "boolean" }, "show_icon": { - "description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused window)", + "description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused container)", "type": "boolean" } } @@ -1918,6 +1975,63 @@ } } }, + "locked_container": { + "description": "Configure the Locked Container widget", + "type": "object", + "required": [ + "enable" + ], + "properties": { + "display": { + "description": "Display format of the current locked state", + "oneOf": [ + { + "description": "Show only icon", + "type": "string", + "enum": [ + "Icon" + ] + }, + { + "description": "Show only text", + "type": "string", + "enum": [ + "Text" + ] + }, + { + "description": "Show an icon and text for the selected element, and text on the rest", + "type": "string", + "enum": [ + "TextAndIconOnSelected" + ] + }, + { + "description": "Show both icon and text", + "type": "string", + "enum": [ + "IconAndText" + ] + }, + { + "description": "Show an icon and text for the selected element, and icons on the rest", + "type": "string", + "enum": [ + "IconAndTextOnSelected" + ] + } + ] + }, + "enable": { + "description": "Enable the Komorebi Locked Container widget", + "type": "boolean" + }, + "show_when_unlocked": { + "description": "Show the widget event if the layer is unlocked", + "type": "boolean" + } + } + }, "workspace_layer": { "description": "Configure the Workspace Layer widget", "type": "object", @@ -3118,15 +3232,15 @@ } } }, - "focused_window": { - "description": "Configure the Focused Window widget", + "focused_container": { + "description": "Configure the Focused Container widget", "type": "object", "required": [ "enable" ], "properties": { "display": { - "description": "Display format of the currently focused window", + "description": "Display format of the currently focused container", "oneOf": [ { "description": "Show only icon", @@ -3166,11 +3280,11 @@ ] }, "enable": { - "description": "Enable the Komorebi Focused Window widget", + "description": "Enable the Komorebi Focused Container widget", "type": "boolean" }, "show_icon": { - "description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused window)", + "description": "DEPRECATED: use 'display' instead (Show the icon of the currently focused container)", "type": "boolean" } } @@ -3261,6 +3375,63 @@ } } }, + "locked_container": { + "description": "Configure the Locked Container widget", + "type": "object", + "required": [ + "enable" + ], + "properties": { + "display": { + "description": "Display format of the current locked state", + "oneOf": [ + { + "description": "Show only icon", + "type": "string", + "enum": [ + "Icon" + ] + }, + { + "description": "Show only text", + "type": "string", + "enum": [ + "Text" + ] + }, + { + "description": "Show an icon and text for the selected element, and text on the rest", + "type": "string", + "enum": [ + "TextAndIconOnSelected" + ] + }, + { + "description": "Show both icon and text", + "type": "string", + "enum": [ + "IconAndText" + ] + }, + { + "description": "Show an icon and text for the selected element, and icons on the rest", + "type": "string", + "enum": [ + "IconAndTextOnSelected" + ] + } + ] + }, + "enable": { + "description": "Enable the Komorebi Locked Container widget", + "type": "boolean" + }, + "show_when_unlocked": { + "description": "Show the widget event if the layer is unlocked", + "type": "boolean" + } + } + }, "workspace_layer": { "description": "Configure the Workspace Layer widget", "type": "object", diff --git a/schema.json b/schema.json index 38ff1a79..2e4e15d7 100644 --- a/schema.json +++ b/schema.json @@ -354,6 +354,45 @@ "format": "color-hex" } ] + }, + "unfocused_locked": { + "description": "Border colour when the container is unfocused and locked", + "anyOf": [ + { + "description": "Colour represented as RGB", + "type": "object", + "required": [ + "b", + "g", + "r" + ], + "properties": { + "b": { + "description": "Blue", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "g": { + "description": "Green", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + }, + "r": { + "description": "Red", + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + } + }, + { + "description": "Colour represented as Hex", + "type": "string", + "format": "color-hex" + } + ] } } },