feat(bar): optional workspaces on Komorebi widget

This commit makes the `workspaces` on `Komorebi` widget optional. This
way it allows adding the `workspaces` on one Alignment and the
`focused_window` on another one, for example.
This commit is contained in:
alex-ds13
2024-12-14 16:46:21 +00:00
committed by جاد
parent 3c0b12f9af
commit c3769e7881
3 changed files with 143 additions and 148 deletions

View File

@@ -281,34 +281,26 @@ impl Komobar {
self.render_config self.render_config
.replace(config.new_renderconfig(ctx, theme_color)); .replace(config.new_renderconfig(ctx, theme_color));
let mut komorebi_widget = None;
let mut komorebi_widget_idx = None;
let mut komorebi_notification_state = previous_notification_state; let mut komorebi_notification_state = previous_notification_state;
let mut side = None; let mut komorebi_widgets = Vec::new();
for (idx, widget_config) in config.left_widgets.iter().enumerate() { for (idx, widget_config) in config.left_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config { if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(config)); komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Left));
komorebi_widget_idx = Some(idx);
side = Some(Alignment::Left);
} }
} }
if let Some(center_widgets) = &config.center_widgets { if let Some(center_widgets) = &config.center_widgets {
for (idx, widget_config) in center_widgets.iter().enumerate() { for (idx, widget_config) in center_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config { if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(config)); komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Center));
komorebi_widget_idx = Some(idx);
side = Some(Alignment::Center);
} }
} }
} }
for (idx, widget_config) in config.right_widgets.iter().enumerate() { for (idx, widget_config) in config.right_widgets.iter().enumerate() {
if let WidgetConfig::Komorebi(config) = widget_config { if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(config)); komorebi_widgets.push((Komorebi::from(config), idx, Alignment::Right));
komorebi_widget_idx = Some(idx);
side = Some(Alignment::Right);
} }
} }
@@ -335,28 +327,33 @@ impl Komobar {
.map(|config| config.as_boxed_bar_widget()) .map(|config| config.as_boxed_bar_widget())
.collect::<Vec<Box<dyn BarWidget>>>(); .collect::<Vec<Box<dyn BarWidget>>>();
if let (Some(idx), Some(mut widget), Some(side)) = if !komorebi_widgets.is_empty() {
(komorebi_widget_idx, komorebi_widget, side) komorebi_widgets
{ .into_iter()
match komorebi_notification_state { .for_each(|(mut widget, idx, side)| {
None => { match komorebi_notification_state {
komorebi_notification_state = Some(widget.komorebi_notification_state.clone()); None => {
} komorebi_notification_state =
Some(ref previous) => { Some(widget.komorebi_notification_state.clone());
previous }
.borrow_mut() Some(ref previous) => {
.update_from_config(&widget.komorebi_notification_state.borrow()); if widget.workspaces.map_or(false, |w| w.enable) {
previous.borrow_mut().update_from_config(
&widget.komorebi_notification_state.borrow(),
);
}
widget.komorebi_notification_state = previous.clone(); widget.komorebi_notification_state = previous.clone();
} }
} }
let boxed: Box<dyn BarWidget> = Box::new(widget); let boxed: Box<dyn BarWidget> = Box::new(widget);
match side { match side {
Alignment::Left => left_widgets[idx] = boxed, Alignment::Left => left_widgets[idx] = boxed,
Alignment::Center => center_widgets[idx] = boxed, Alignment::Center => center_widgets[idx] = boxed,
Alignment::Right => right_widgets[idx] = boxed, Alignment::Right => right_widgets[idx] = boxed,
} }
});
} }
right_widgets.reverse(); right_widgets.reverse();

View File

@@ -45,7 +45,7 @@ use std::sync::atomic::Ordering;
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)] #[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct KomorebiConfig { pub struct KomorebiConfig {
/// Configure the Workspaces widget /// Configure the Workspaces widget
pub workspaces: KomorebiWorkspacesConfig, pub workspaces: Option<KomorebiWorkspacesConfig>,
/// Configure the Layout widget /// Configure the Layout widget
pub layout: Option<KomorebiLayoutConfig>, pub layout: Option<KomorebiLayoutConfig>,
/// Configure the Focused Window widget /// Configure the Focused Window widget
@@ -112,7 +112,10 @@ impl From<&KomorebiConfig> for Komorebi {
selected_workspace: String::new(), selected_workspace: String::new(),
layout: KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP), layout: KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),
workspaces: vec![], workspaces: vec![],
hide_empty_workspaces: value.workspaces.hide_empty_workspaces, hide_empty_workspaces: value
.workspaces
.map(|w| w.hide_empty_workspaces)
.unwrap_or_default(),
mouse_follows_focus: true, mouse_follows_focus: true,
work_area_offset: None, work_area_offset: None,
focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY, focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY,
@@ -130,7 +133,7 @@ impl From<&KomorebiConfig> for Komorebi {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Komorebi { pub struct Komorebi {
pub komorebi_notification_state: Rc<RefCell<KomorebiNotificationState>>, pub komorebi_notification_state: Rc<RefCell<KomorebiNotificationState>>,
pub workspaces: KomorebiWorkspacesConfig, pub workspaces: Option<KomorebiWorkspacesConfig>,
pub layout: Option<KomorebiLayoutConfig>, pub layout: Option<KomorebiLayoutConfig>,
pub focused_window: Option<KomorebiFocusedWindowConfig>, pub focused_window: Option<KomorebiFocusedWindowConfig>,
pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>, pub configuration_switcher: Option<KomorebiConfigurationSwitcherConfig>,
@@ -141,124 +144,128 @@ impl BarWidget for Komorebi {
let mut komorebi_notification_state = self.komorebi_notification_state.borrow_mut(); let mut komorebi_notification_state = self.komorebi_notification_state.borrow_mut();
let icon_size = Vec2::splat(config.icon_font_id.size); let icon_size = Vec2::splat(config.icon_font_id.size);
if self.workspaces.enable { if let Some(workspaces) = self.workspaces {
let mut update = None; if workspaces.enable {
let mut update = None;
if !komorebi_notification_state.workspaces.is_empty() { if !komorebi_notification_state.workspaces.is_empty() {
let format = self.workspaces.display.unwrap_or(DisplayFormat::Text); let format = workspaces.display.unwrap_or(DisplayFormat::Text);
config.apply_on_widget(false, ui, |ui| { config.apply_on_widget(false, ui, |ui| {
for (i, (ws, container_information)) in for (i, (ws, container_information)) in
komorebi_notification_state.workspaces.iter().enumerate() komorebi_notification_state.workspaces.iter().enumerate()
{
if SelectableFrame::new(
komorebi_notification_state.selected_workspace.eq(ws),
)
.show(ui, |ui| {
let mut has_icon = false;
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
let icons: Vec<_> =
container_information.icons.iter().flatten().collect();
if !icons.is_empty() {
Frame::none()
.inner_margin(Margin::same(
ui.style().spacing.button_padding.y,
))
.show(ui, |ui| {
for icon in icons {
ui.add(
Image::from(&img_to_texture(ctx, icon))
.maintain_aspect_ratio(true)
.fit_to_exact_size(icon_size),
);
if !has_icon {
has_icon = true;
}
}
});
}
}
// draw a custom icon when there is no app icon
if match format {
DisplayFormat::Icon => !has_icon,
_ => false,
} {
let (response, painter) =
ui.allocate_painter(icon_size, Sense::hover());
let stroke =
Stroke::new(1.0, ctx.style().visuals.selection.stroke.color);
let mut rect = response.rect;
let rounding = Rounding::same(rect.width() * 0.1);
rect = rect.shrink(stroke.width);
let c = rect.center();
let r = rect.width() / 2.0;
painter.rect_stroke(rect, rounding, stroke);
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
response.on_hover_text(ws.to_string())
} else if match format {
DisplayFormat::Icon => has_icon,
_ => false,
} {
ui.response().on_hover_text(ws.to_string())
} else {
ui.add(Label::new(ws.to_string()).selectable(false))
}
})
.clicked()
{ {
update = Some(ws.to_string()); if SelectableFrame::new(
komorebi_notification_state.selected_workspace.eq(ws),
)
.show(ui, |ui| {
let mut has_icon = false;
if komorebi_notification_state.mouse_follows_focus { if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
if komorebi_client::send_batch([ let icons: Vec<_> =
SocketMessage::MouseFollowsFocus(false), container_information.icons.iter().flatten().collect();
if !icons.is_empty() {
Frame::none()
.inner_margin(Margin::same(
ui.style().spacing.button_padding.y,
))
.show(ui, |ui| {
for icon in icons {
ui.add(
Image::from(&img_to_texture(ctx, icon))
.maintain_aspect_ratio(true)
.fit_to_exact_size(icon_size),
);
if !has_icon {
has_icon = true;
}
}
});
}
}
// draw a custom icon when there is no app icon
if match format {
DisplayFormat::Icon => !has_icon,
_ => false,
} {
let (response, painter) =
ui.allocate_painter(icon_size, Sense::hover());
let stroke = Stroke::new(
1.0,
ctx.style().visuals.selection.stroke.color,
);
let mut rect = response.rect;
let rounding = Rounding::same(rect.width() * 0.1);
rect = rect.shrink(stroke.width);
let c = rect.center();
let r = rect.width() / 2.0;
painter.rect_stroke(rect, rounding, stroke);
painter.line_segment([c - vec2(r, r), c + vec2(r, r)], stroke);
response.on_hover_text(ws.to_string())
} else if match format {
DisplayFormat::Icon => has_icon,
_ => false,
} {
ui.response().on_hover_text(ws.to_string())
} else {
ui.add(Label::new(ws.to_string()).selectable(false))
}
})
.clicked()
{
update = Some(ws.to_string());
if komorebi_notification_state.mouse_follows_focus {
if komorebi_client::send_batch([
SocketMessage::MouseFollowsFocus(false),
SocketMessage::FocusMonitorWorkspaceNumber(
komorebi_notification_state.monitor_index,
i,
),
SocketMessage::RetileWithResizeDimensions,
SocketMessage::MouseFollowsFocus(true),
])
.is_err()
{
tracing::error!(
"could not send the following batch of messages to komorebi:\n
MouseFollowsFocus(false)\n
FocusMonitorWorkspaceNumber({}, {})\n
RetileWithResizeDimensions
MouseFollowsFocus(true)\n",
komorebi_notification_state.monitor_index,
i,
);
}
} else if komorebi_client::send_batch([
SocketMessage::FocusMonitorWorkspaceNumber( SocketMessage::FocusMonitorWorkspaceNumber(
komorebi_notification_state.monitor_index, komorebi_notification_state.monitor_index,
i, i,
), ),
SocketMessage::RetileWithResizeDimensions, SocketMessage::RetileWithResizeDimensions,
SocketMessage::MouseFollowsFocus(true),
]) ])
.is_err() .is_err()
{ {
tracing::error!( tracing::error!(
"could not send the following batch of messages to komorebi:\n "could not send the following batch of messages to komorebi:\n
MouseFollowsFocus(false)\n
FocusMonitorWorkspaceNumber({}, {})\n FocusMonitorWorkspaceNumber({}, {})\n
RetileWithResizeDimensions RetileWithResizeDimensions",
MouseFollowsFocus(true)\n",
komorebi_notification_state.monitor_index, komorebi_notification_state.monitor_index,
i, i,
); );
} }
} else if komorebi_client::send_batch([
SocketMessage::FocusMonitorWorkspaceNumber(
komorebi_notification_state.monitor_index,
i,
),
SocketMessage::RetileWithResizeDimensions,
])
.is_err()
{
tracing::error!(
"could not send the following batch of messages to komorebi:\n
FocusMonitorWorkspaceNumber({}, {})\n
RetileWithResizeDimensions",
komorebi_notification_state.monitor_index,
i,
);
} }
} }
} });
}); }
}
if let Some(update) = update { if let Some(update) = update {
komorebi_notification_state.selected_workspace = update; komorebi_notification_state.selected_workspace = update;
}
} }
} }

View File

@@ -61,22 +61,13 @@ impl WidgetConfig {
WidgetConfig::Cpu(config) => config.enable, WidgetConfig::Cpu(config) => config.enable,
WidgetConfig::Date(config) => config.enable, WidgetConfig::Date(config) => config.enable,
WidgetConfig::Komorebi(config) => { WidgetConfig::Komorebi(config) => {
config.workspaces.enable config.workspaces.as_ref().map_or(false, |w| w.enable)
|| (if let Some(layout) = &config.layout { || config.layout.as_ref().map_or(false, |w| w.enable)
layout.enable || config.focused_window.as_ref().map_or(false, |w| w.enable)
} else { || config
false .configuration_switcher
}) .as_ref()
|| (if let Some(focused_window) = &config.focused_window { .map_or(false, |w| w.enable)
focused_window.enable
} else {
false
})
|| (if let Some(configuration_switcher) = &config.configuration_switcher {
configuration_switcher.enable
} else {
false
})
} }
WidgetConfig::Media(config) => config.enable, WidgetConfig::Media(config) => config.enable,
WidgetConfig::Memory(config) => config.enable, WidgetConfig::Memory(config) => config.enable,