mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-06-12 21:44:27 +02:00
feat(bar): komorebi widget visual changes
The visual changes include: * the focused_window section is now indicating the active window in a stack and has hover effect. * custom icons for all the layouts, including `paused`, `floating`, `monocle` states. * custom layout/state picker with configurable options. * display format configuration for the layouts (Icon/Text/IconAndText) * display format configuration for the focused_window section (Icon/Text/IconAndText) * display format configuration for the workspaces section (Icon/Text/IconAndText)
This commit is contained in:
@@ -187,3 +187,13 @@ pub enum LabelPrefix {
|
|||||||
/// Show an icon and text
|
/// Show an icon and text
|
||||||
IconAndText,
|
IconAndText,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
|
pub enum DisplayFormat {
|
||||||
|
/// Show only icon
|
||||||
|
Icon,
|
||||||
|
/// Show only text
|
||||||
|
Text,
|
||||||
|
/// Show both icon and text
|
||||||
|
IconAndText,
|
||||||
|
}
|
||||||
|
|||||||
+337
-302
@@ -1,38 +1,44 @@
|
|||||||
use crate::bar::apply_theme;
|
use crate::bar::apply_theme;
|
||||||
|
use crate::config::DisplayFormat;
|
||||||
use crate::config::KomobarTheme;
|
use crate::config::KomobarTheme;
|
||||||
|
use crate::komorebi_layout::KomorebiLayout;
|
||||||
use crate::render::RenderConfig;
|
use crate::render::RenderConfig;
|
||||||
|
use crate::selected_frame::SelectableFrame;
|
||||||
use crate::ui::CustomUi;
|
use crate::ui::CustomUi;
|
||||||
use crate::widget::BarWidget;
|
use crate::widget::BarWidget;
|
||||||
use crate::MAX_LABEL_WIDTH;
|
use crate::MAX_LABEL_WIDTH;
|
||||||
use crate::MONITOR_INDEX;
|
use crate::MONITOR_INDEX;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
use crossbeam_channel::TryRecvError;
|
use crossbeam_channel::TryRecvError;
|
||||||
use eframe::egui::text::LayoutJob;
|
use eframe::egui::vec2;
|
||||||
use eframe::egui::Color32;
|
use eframe::egui::Color32;
|
||||||
use eframe::egui::ColorImage;
|
use eframe::egui::ColorImage;
|
||||||
use eframe::egui::Context;
|
use eframe::egui::Context;
|
||||||
use eframe::egui::FontId;
|
use eframe::egui::FontId;
|
||||||
|
use eframe::egui::Frame;
|
||||||
use eframe::egui::Image;
|
use eframe::egui::Image;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::SelectableLabel;
|
use eframe::egui::Margin;
|
||||||
|
use eframe::egui::Rounding;
|
||||||
use eframe::egui::Sense;
|
use eframe::egui::Sense;
|
||||||
|
use eframe::egui::Stroke;
|
||||||
use eframe::egui::TextStyle;
|
use eframe::egui::TextStyle;
|
||||||
use eframe::egui::TextureHandle;
|
use eframe::egui::TextureHandle;
|
||||||
use eframe::egui::TextureOptions;
|
use eframe::egui::TextureOptions;
|
||||||
use eframe::egui::Ui;
|
use eframe::egui::Ui;
|
||||||
use eframe::egui::Vec2;
|
use eframe::egui::Vec2;
|
||||||
use image::RgbaImage;
|
use image::RgbaImage;
|
||||||
use komorebi_client::CycleDirection;
|
use komorebi_client::Container;
|
||||||
use komorebi_client::NotificationEvent;
|
use komorebi_client::NotificationEvent;
|
||||||
use komorebi_client::Rect;
|
use komorebi_client::Rect;
|
||||||
use komorebi_client::SocketMessage;
|
use komorebi_client::SocketMessage;
|
||||||
|
use komorebi_client::Window;
|
||||||
|
use komorebi_client::Workspace;
|
||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::Display;
|
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
@@ -55,20 +61,28 @@ pub struct KomorebiWorkspacesConfig {
|
|||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
/// Hide workspaces without any windows
|
/// Hide workspaces without any windows
|
||||||
pub hide_empty_workspaces: bool,
|
pub hide_empty_workspaces: bool,
|
||||||
|
/// Display format of the workspace
|
||||||
|
pub display: Option<DisplayFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct KomorebiLayoutConfig {
|
pub struct KomorebiLayoutConfig {
|
||||||
/// Enable the Komorebi Layout widget
|
/// Enable the Komorebi Layout widget
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
|
/// List of layout options
|
||||||
|
pub options: Option<Vec<KomorebiLayout>>,
|
||||||
|
/// Display format of the current layout
|
||||||
|
pub display: Option<DisplayFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct KomorebiFocusedWindowConfig {
|
pub struct KomorebiFocusedWindowConfig {
|
||||||
/// Enable the Komorebi Focused Window widget
|
/// Enable the Komorebi Focused Window widget
|
||||||
pub enable: bool,
|
pub enable: bool,
|
||||||
/// Show the icon of the currently focused window
|
/// DEPRECATED: use 'display' instead (Show the icon of the currently focused window)
|
||||||
pub show_icon: bool,
|
pub show_icon: Option<bool>,
|
||||||
|
/// Display format of the currently focused window
|
||||||
|
pub display: Option<DisplayFormat>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
@@ -102,12 +116,12 @@ impl From<&KomorebiConfig> for Komorebi {
|
|||||||
hide_empty_workspaces: value.workspaces.hide_empty_workspaces,
|
hide_empty_workspaces: value.workspaces.hide_empty_workspaces,
|
||||||
mouse_follows_focus: true,
|
mouse_follows_focus: true,
|
||||||
work_area_offset: None,
|
work_area_offset: None,
|
||||||
focused_container_information: (vec![], vec![], 0),
|
focused_container_information: KomorebiNotificationStateContainerInformation::EMPTY,
|
||||||
stack_accent: None,
|
stack_accent: None,
|
||||||
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
|
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
|
||||||
})),
|
})),
|
||||||
workspaces: value.workspaces,
|
workspaces: value.workspaces,
|
||||||
layout: value.layout,
|
layout: value.layout.clone(),
|
||||||
focused_window: value.focused_window,
|
focused_window: value.focused_window,
|
||||||
configuration_switcher,
|
configuration_switcher,
|
||||||
}
|
}
|
||||||
@@ -130,120 +144,153 @@ impl BarWidget for Komorebi {
|
|||||||
if self.workspaces.enable {
|
if self.workspaces.enable {
|
||||||
let mut update = None;
|
let mut update = None;
|
||||||
|
|
||||||
// NOTE: There should always be at least one workspace if the bar is connected to komorebi.
|
if !komorebi_notification_state.workspaces.is_empty() {
|
||||||
config.apply_on_widget(false, ui, |ui| {
|
let format = self.workspaces.display.unwrap_or(DisplayFormat::Text);
|
||||||
for (i, (ws, should_show)) in
|
|
||||||
komorebi_notification_state.workspaces.iter().enumerate()
|
config.apply_on_widget(false, ui, |ui| {
|
||||||
{
|
for (i, (ws, container_information)) in
|
||||||
if *should_show
|
komorebi_notification_state.workspaces.iter().enumerate()
|
||||||
&& ui
|
|
||||||
.add(SelectableLabel::new(
|
|
||||||
komorebi_notification_state.selected_workspace.eq(ws),
|
|
||||||
ws.to_string(),
|
|
||||||
))
|
|
||||||
.clicked()
|
|
||||||
{
|
{
|
||||||
update = Some(ws.to_string());
|
if SelectableFrame::new(
|
||||||
let mut proceed = true;
|
komorebi_notification_state.selected_workspace.eq(ws),
|
||||||
|
)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let mut has_icon = false;
|
||||||
|
|
||||||
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(false))
|
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
|
||||||
.is_err()
|
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)
|
||||||
|
.shrink_to_fit(),
|
||||||
|
);
|
||||||
|
|
||||||
|
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 font_id = ctx
|
||||||
|
.style()
|
||||||
|
.text_styles
|
||||||
|
.get(&TextStyle::Body)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(FontId::default);
|
||||||
|
|
||||||
|
let (response, painter) =
|
||||||
|
ui.allocate_painter(Vec2::splat(font_id.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()
|
||||||
{
|
{
|
||||||
tracing::error!(
|
update = Some(ws.to_string());
|
||||||
"could not send message to komorebi: MouseFollowsFocus"
|
let mut proceed = true;
|
||||||
);
|
|
||||||
proceed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if proceed
|
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
|
||||||
&& komorebi_client::send_message(
|
false,
|
||||||
&SocketMessage::FocusMonitorWorkspaceNumber(
|
|
||||||
komorebi_notification_state.monitor_index,
|
|
||||||
i,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"could not send message to komorebi: FocusWorkspaceNumber"
|
|
||||||
);
|
|
||||||
proceed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if proceed
|
|
||||||
&& komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
|
|
||||||
komorebi_notification_state.mouse_follows_focus,
|
|
||||||
))
|
))
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"could not send message to komorebi: MouseFollowsFocus"
|
"could not send message to komorebi: MouseFollowsFocus"
|
||||||
);
|
);
|
||||||
proceed = false;
|
proceed = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if proceed
|
if proceed
|
||||||
&& komorebi_client::send_message(
|
&& komorebi_client::send_message(
|
||||||
&SocketMessage::RetileWithResizeDimensions,
|
&SocketMessage::FocusMonitorWorkspaceNumber(
|
||||||
)
|
komorebi_notification_state.monitor_index,
|
||||||
.is_err()
|
i,
|
||||||
{
|
),
|
||||||
tracing::error!("could not send message to komorebi: Retile");
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"could not send message to komorebi: FocusWorkspaceNumber"
|
||||||
|
);
|
||||||
|
proceed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if proceed
|
||||||
|
&& komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
|
||||||
|
komorebi_notification_state.mouse_follows_focus,
|
||||||
|
))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"could not send message to komorebi: MouseFollowsFocus"
|
||||||
|
);
|
||||||
|
proceed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if proceed
|
||||||
|
&& komorebi_client::send_message(
|
||||||
|
&SocketMessage::RetileWithResizeDimensions,
|
||||||
|
)
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
tracing::error!("could not send message to komorebi: Retile");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
if let Some(update) = update {
|
if let Some(update) = update {
|
||||||
komorebi_notification_state.selected_workspace = update;
|
komorebi_notification_state.selected_workspace = update;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(layout) = self.layout {
|
if let Some(layout_config) = &self.layout {
|
||||||
if layout.enable {
|
if layout_config.enable {
|
||||||
config.apply_on_widget(true, ui, |ui| {
|
let workspace_idx: Option<usize> = komorebi_notification_state
|
||||||
if ui
|
.workspaces
|
||||||
.add(
|
.iter()
|
||||||
Label::new(komorebi_notification_state.layout.to_string())
|
.position(|o| komorebi_notification_state.selected_workspace.eq(&o.0));
|
||||||
.selectable(false)
|
|
||||||
.sense(Sense::click()),
|
komorebi_notification_state.layout.show(
|
||||||
)
|
ctx,
|
||||||
.clicked()
|
ui,
|
||||||
{
|
config,
|
||||||
match komorebi_notification_state.layout {
|
layout_config,
|
||||||
KomorebiLayout::Default(_) => {
|
workspace_idx,
|
||||||
if komorebi_client::send_message(&SocketMessage::CycleLayout(
|
);
|
||||||
CycleDirection::Next,
|
|
||||||
))
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"could not send message to komorebi: CycleLayout"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KomorebiLayout::Floating => {
|
|
||||||
if komorebi_client::send_message(&SocketMessage::ToggleTiling)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"could not send message to komorebi: ToggleTiling"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KomorebiLayout::Paused => {
|
|
||||||
if komorebi_client::send_message(&SocketMessage::TogglePause)
|
|
||||||
.is_err()
|
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"could not send message to komorebi: TogglePause"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KomorebiLayout::Custom => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,9 +299,10 @@ impl BarWidget for Komorebi {
|
|||||||
for (name, location) in configuration_switcher.configurations.iter() {
|
for (name, location) in configuration_switcher.configurations.iter() {
|
||||||
let path = PathBuf::from(location);
|
let path = PathBuf::from(location);
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
config.apply_on_widget(true, ui,|ui|{
|
config.apply_on_widget(false, ui,|ui|{
|
||||||
if ui
|
if SelectableFrame::new(false).show(ui, |ui|{
|
||||||
.add(Label::new(name).selectable(false).sense(Sense::click()))
|
ui.add(Label::new(name).selectable(false))
|
||||||
|
})
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path);
|
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path);
|
||||||
@@ -307,112 +355,103 @@ impl BarWidget for Komorebi {
|
|||||||
|
|
||||||
if let Some(focused_window) = self.focused_window {
|
if let Some(focused_window) = self.focused_window {
|
||||||
if focused_window.enable {
|
if focused_window.enable {
|
||||||
let titles = &komorebi_notification_state.focused_container_information.0;
|
let titles = &komorebi_notification_state
|
||||||
|
.focused_container_information
|
||||||
|
.titles;
|
||||||
if !titles.is_empty() {
|
if !titles.is_empty() {
|
||||||
config.apply_on_widget(true, ui, |ui| {
|
config.apply_on_widget(false, ui, |ui| {
|
||||||
let icons = &komorebi_notification_state.focused_container_information.1;
|
let icons = &komorebi_notification_state
|
||||||
let focused_window_idx =
|
.focused_container_information
|
||||||
komorebi_notification_state.focused_container_information.2;
|
.icons;
|
||||||
|
let focused_window_idx = komorebi_notification_state
|
||||||
|
.focused_container_information
|
||||||
|
.focused_window_idx;
|
||||||
|
|
||||||
let iter = titles.iter().zip(icons.iter());
|
let iter = titles.iter().zip(icons.iter());
|
||||||
|
|
||||||
for (i, (title, icon)) in iter.enumerate() {
|
for (i, (title, icon)) in iter.enumerate() {
|
||||||
if focused_window.show_icon {
|
let selected = i == focused_window_idx;
|
||||||
if let Some(img) = icon {
|
|
||||||
ui.add(
|
if SelectableFrame::new(selected)
|
||||||
Image::from(&img_to_texture(ctx, img))
|
.show(ui, |ui| {
|
||||||
.maintain_aspect_ratio(true)
|
// handle legacy setting
|
||||||
.max_height(15.0),
|
let format = focused_window.display.unwrap_or(
|
||||||
|
if focused_window.show_icon.unwrap_or(false) {
|
||||||
|
DisplayFormat::IconAndText
|
||||||
|
} else {
|
||||||
|
DisplayFormat::Text
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format
|
||||||
|
{
|
||||||
|
if let Some(img) = icon {
|
||||||
|
Frame::none()
|
||||||
|
.inner_margin(Margin::same(
|
||||||
|
ui.style().spacing.button_padding.y,
|
||||||
|
))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let response = ui.add(
|
||||||
|
Image::from(&img_to_texture(ctx, img))
|
||||||
|
.maintain_aspect_ratio(true)
|
||||||
|
.shrink_to_fit(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if let DisplayFormat::Icon = format {
|
||||||
|
response.on_hover_text(title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let DisplayFormat::Text | DisplayFormat::IconAndText = format
|
||||||
|
{
|
||||||
|
let available_height = ui.available_height();
|
||||||
|
let mut custom_ui = CustomUi(ui);
|
||||||
|
|
||||||
|
custom_ui.add_sized_left_to_right(
|
||||||
|
Vec2::new(
|
||||||
|
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
|
||||||
|
available_height,
|
||||||
|
),
|
||||||
|
Label::new(title).selectable(false).truncate(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
if selected {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if i == focused_window_idx {
|
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
|
||||||
let font_id = ctx
|
false,
|
||||||
.style()
|
))
|
||||||
.text_styles
|
.is_err()
|
||||||
.get(&TextStyle::Body)
|
|
||||||
.cloned()
|
|
||||||
.unwrap_or_else(FontId::default);
|
|
||||||
|
|
||||||
let layout_job = LayoutJob::simple(
|
|
||||||
title.to_string(),
|
|
||||||
font_id.clone(),
|
|
||||||
komorebi_notification_state
|
|
||||||
.stack_accent
|
|
||||||
.unwrap_or(ctx.style().visuals.selection.stroke.color),
|
|
||||||
100.0,
|
|
||||||
);
|
|
||||||
|
|
||||||
if titles.len() > 1 {
|
|
||||||
let available_height = ui.available_height();
|
|
||||||
let mut custom_ui = CustomUi(ui);
|
|
||||||
custom_ui.add_sized_left_to_right(
|
|
||||||
Vec2::new(
|
|
||||||
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
|
|
||||||
available_height,
|
|
||||||
),
|
|
||||||
Label::new(layout_job).selectable(false).truncate(),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
let available_height = ui.available_height();
|
|
||||||
let mut custom_ui = CustomUi(ui);
|
|
||||||
custom_ui.add_sized_left_to_right(
|
|
||||||
Vec2::new(
|
|
||||||
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
|
|
||||||
available_height,
|
|
||||||
),
|
|
||||||
Label::new(title).selectable(false).truncate(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let available_height = ui.available_height();
|
|
||||||
let mut custom_ui = CustomUi(ui);
|
|
||||||
|
|
||||||
if custom_ui
|
|
||||||
.add_sized_left_to_right(
|
|
||||||
Vec2::new(
|
|
||||||
MAX_LABEL_WIDTH.load(Ordering::SeqCst) as f32,
|
|
||||||
available_height,
|
|
||||||
),
|
|
||||||
Label::new(title)
|
|
||||||
.selectable(false)
|
|
||||||
.sense(Sense::click())
|
|
||||||
.truncate(),
|
|
||||||
)
|
|
||||||
.clicked()
|
|
||||||
{
|
{
|
||||||
if komorebi_client::send_message(
|
tracing::error!(
|
||||||
&SocketMessage::MouseFollowsFocus(false),
|
"could not send message to komorebi: MouseFollowsFocus"
|
||||||
)
|
);
|
||||||
.is_err()
|
}
|
||||||
{
|
|
||||||
tracing::error!(
|
|
||||||
"could not send message to komorebi: MouseFollowsFocus"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if komorebi_client::send_message(
|
if komorebi_client::send_message(&SocketMessage::FocusStackWindow(
|
||||||
&SocketMessage::FocusStackWindow(i),
|
i,
|
||||||
)
|
))
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"could not send message to komorebi: FocusStackWindow"
|
"could not send message to komorebi: FocusStackWindow"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if komorebi_client::send_message(
|
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
|
||||||
&SocketMessage::MouseFollowsFocus(
|
komorebi_notification_state.mouse_follows_focus,
|
||||||
komorebi_notification_state.mouse_follows_focus,
|
))
|
||||||
),
|
.is_err()
|
||||||
)
|
{
|
||||||
.is_err()
|
tracing::error!(
|
||||||
{
|
"could not send message to komorebi: MouseFollowsFocus"
|
||||||
tracing::error!(
|
);
|
||||||
"could not send message to komorebi: MouseFollowsFocus"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -432,9 +471,9 @@ fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle {
|
|||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct KomorebiNotificationState {
|
pub struct KomorebiNotificationState {
|
||||||
pub workspaces: Vec<(String, bool)>,
|
pub workspaces: Vec<(String, KomorebiNotificationStateContainerInformation)>,
|
||||||
pub selected_workspace: String,
|
pub selected_workspace: String,
|
||||||
pub focused_container_information: (Vec<String>, Vec<Option<RgbaImage>>, usize),
|
pub focused_container_information: KomorebiNotificationStateContainerInformation,
|
||||||
pub layout: KomorebiLayout,
|
pub layout: KomorebiLayout,
|
||||||
pub hide_empty_workspaces: bool,
|
pub hide_empty_workspaces: bool,
|
||||||
pub mouse_follows_focus: bool,
|
pub mouse_follows_focus: bool,
|
||||||
@@ -443,25 +482,6 @@ pub struct KomorebiNotificationState {
|
|||||||
pub monitor_index: usize,
|
pub monitor_index: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
pub enum KomorebiLayout {
|
|
||||||
Default(komorebi_client::DefaultLayout),
|
|
||||||
Floating,
|
|
||||||
Paused,
|
|
||||||
Custom,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for KomorebiLayout {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
match self {
|
|
||||||
KomorebiLayout::Default(layout) => write!(f, "{layout}"),
|
|
||||||
KomorebiLayout::Floating => write!(f, "Floating"),
|
|
||||||
KomorebiLayout::Paused => write!(f, "Paused"),
|
|
||||||
KomorebiLayout::Custom => write!(f, "Custom"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl KomorebiNotificationState {
|
impl KomorebiNotificationState {
|
||||||
pub fn update_from_config(&mut self, config: &Self) {
|
pub fn update_from_config(&mut self, config: &Self) {
|
||||||
self.hide_empty_workspaces = config.hide_empty_workspaces;
|
self.hide_empty_workspaces = config.hide_empty_workspaces;
|
||||||
@@ -526,85 +546,100 @@ impl KomorebiNotificationState {
|
|||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
workspaces.push((
|
if should_show {
|
||||||
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
workspaces.push((
|
||||||
should_show,
|
ws.name().to_owned().unwrap_or_else(|| format!("{}", i + 1)),
|
||||||
));
|
ws.into(),
|
||||||
}
|
));
|
||||||
|
|
||||||
self.workspaces = workspaces;
|
|
||||||
self.layout = match monitor.workspaces()[focused_workspace_idx].layout() {
|
|
||||||
komorebi_client::Layout::Default(layout) => KomorebiLayout::Default(*layout),
|
|
||||||
komorebi_client::Layout::Custom(_) => KomorebiLayout::Custom,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !*monitor.workspaces()[focused_workspace_idx].tile() {
|
|
||||||
self.layout = KomorebiLayout::Floating;
|
|
||||||
}
|
|
||||||
|
|
||||||
if notification.state.is_paused {
|
|
||||||
self.layout = KomorebiLayout::Paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut has_window_container_information = false;
|
|
||||||
|
|
||||||
if let Some(container) =
|
|
||||||
monitor.workspaces()[focused_workspace_idx].monocle_container()
|
|
||||||
{
|
|
||||||
has_window_container_information = true;
|
|
||||||
self.focused_container_information = (
|
|
||||||
container
|
|
||||||
.windows()
|
|
||||||
.iter()
|
|
||||||
.map(|w| w.title().unwrap_or_default())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
container
|
|
||||||
.windows()
|
|
||||||
.iter()
|
|
||||||
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
container.focused_window_idx(),
|
|
||||||
);
|
|
||||||
} else if let Some(container) =
|
|
||||||
monitor.workspaces()[focused_workspace_idx].focused_container()
|
|
||||||
{
|
|
||||||
has_window_container_information = true;
|
|
||||||
self.focused_container_information = (
|
|
||||||
container
|
|
||||||
.windows()
|
|
||||||
.iter()
|
|
||||||
.map(|w| w.title().unwrap_or_default())
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
container
|
|
||||||
.windows()
|
|
||||||
.iter()
|
|
||||||
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
container.focused_window_idx(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
for floating_window in
|
|
||||||
monitor.workspaces()[focused_workspace_idx].floating_windows()
|
|
||||||
{
|
|
||||||
if floating_window.is_focused() {
|
|
||||||
has_window_container_information = true;
|
|
||||||
self.focused_container_information = (
|
|
||||||
vec![floating_window.title().unwrap_or_default()],
|
|
||||||
vec![windows_icons::get_icon_by_process_id(
|
|
||||||
floating_window.process_id(),
|
|
||||||
)],
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !has_window_container_information {
|
self.workspaces = workspaces;
|
||||||
self.focused_container_information.0.clear();
|
|
||||||
self.focused_container_information.1.clear();
|
if monitor.workspaces()[focused_workspace_idx]
|
||||||
self.focused_container_information.2 = 0;
|
.monocle_container()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
self.layout = KomorebiLayout::Monocle;
|
||||||
|
} else if !*monitor.workspaces()[focused_workspace_idx].tile() {
|
||||||
|
self.layout = KomorebiLayout::Floating;
|
||||||
|
} else if notification.state.is_paused {
|
||||||
|
self.layout = KomorebiLayout::Paused;
|
||||||
|
} else {
|
||||||
|
self.layout = match monitor.workspaces()[focused_workspace_idx].layout() {
|
||||||
|
komorebi_client::Layout::Default(layout) => {
|
||||||
|
KomorebiLayout::Default(*layout)
|
||||||
|
}
|
||||||
|
komorebi_client::Layout::Custom(_) => KomorebiLayout::Custom,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.focused_container_information =
|
||||||
|
(&monitor.workspaces()[focused_workspace_idx]).into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct KomorebiNotificationStateContainerInformation {
|
||||||
|
pub titles: Vec<String>,
|
||||||
|
pub icons: Vec<Option<RgbaImage>>,
|
||||||
|
pub focused_window_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Workspace> for KomorebiNotificationStateContainerInformation {
|
||||||
|
fn from(value: &Workspace) -> Self {
|
||||||
|
let mut container_info = Self::EMPTY;
|
||||||
|
|
||||||
|
if let Some(container) = value.monocle_container() {
|
||||||
|
container_info = container.into();
|
||||||
|
} else if let Some(container) = value.focused_container() {
|
||||||
|
container_info = container.into();
|
||||||
|
}
|
||||||
|
|
||||||
|
for floating_window in value.floating_windows() {
|
||||||
|
if floating_window.is_focused() {
|
||||||
|
container_info = floating_window.into();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
container_info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Container> for KomorebiNotificationStateContainerInformation {
|
||||||
|
fn from(value: &Container) -> Self {
|
||||||
|
Self {
|
||||||
|
titles: value
|
||||||
|
.windows()
|
||||||
|
.iter()
|
||||||
|
.map(|w| w.title().unwrap_or_default())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
icons: value
|
||||||
|
.windows()
|
||||||
|
.iter()
|
||||||
|
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
focused_window_idx: value.focused_window_idx(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&Window> for KomorebiNotificationStateContainerInformation {
|
||||||
|
fn from(value: &Window) -> Self {
|
||||||
|
Self {
|
||||||
|
titles: vec![value.title().unwrap_or_default()],
|
||||||
|
icons: vec![windows_icons::get_icon_by_process_id(value.process_id())],
|
||||||
|
focused_window_idx: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KomorebiNotificationStateContainerInformation {
|
||||||
|
pub const EMPTY: Self = Self {
|
||||||
|
titles: vec![],
|
||||||
|
icons: vec![],
|
||||||
|
focused_window_idx: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,311 @@
|
|||||||
|
use crate::config::DisplayFormat;
|
||||||
|
use crate::komorebi::KomorebiLayoutConfig;
|
||||||
|
use crate::render::RenderConfig;
|
||||||
|
use crate::selected_frame::SelectableFrame;
|
||||||
|
use eframe::egui::vec2;
|
||||||
|
use eframe::egui::Context;
|
||||||
|
use eframe::egui::FontId;
|
||||||
|
use eframe::egui::Frame;
|
||||||
|
use eframe::egui::Label;
|
||||||
|
use eframe::egui::Rounding;
|
||||||
|
use eframe::egui::Sense;
|
||||||
|
use eframe::egui::Stroke;
|
||||||
|
use eframe::egui::TextStyle;
|
||||||
|
use eframe::egui::Ui;
|
||||||
|
use eframe::egui::Vec2;
|
||||||
|
use komorebi_client::SocketMessage;
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::de::Error;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Deserializer;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::from_str;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::fmt::Formatter;
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, Serialize, JsonSchema, PartialEq)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum KomorebiLayout {
|
||||||
|
Default(komorebi_client::DefaultLayout),
|
||||||
|
Monocle,
|
||||||
|
Floating,
|
||||||
|
Paused,
|
||||||
|
Custom,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for KomorebiLayout {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let s: String = String::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
// Attempt to deserialize the string as a DefaultLayout
|
||||||
|
if let Ok(default_layout) =
|
||||||
|
from_str::<komorebi_client::DefaultLayout>(&format!("\"{}\"", s))
|
||||||
|
{
|
||||||
|
return Ok(KomorebiLayout::Default(default_layout));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle other cases
|
||||||
|
match s.as_str() {
|
||||||
|
"Monocle" => Ok(KomorebiLayout::Monocle),
|
||||||
|
"Floating" => Ok(KomorebiLayout::Floating),
|
||||||
|
"Paused" => Ok(KomorebiLayout::Paused),
|
||||||
|
"Custom" => Ok(KomorebiLayout::Custom),
|
||||||
|
_ => Err(Error::custom(format!("Invalid layout: {}", s))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for KomorebiLayout {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
KomorebiLayout::Default(layout) => write!(f, "{layout}"),
|
||||||
|
KomorebiLayout::Monocle => write!(f, "Monocle"),
|
||||||
|
KomorebiLayout::Floating => write!(f, "Floating"),
|
||||||
|
KomorebiLayout::Paused => write!(f, "Paused"),
|
||||||
|
KomorebiLayout::Custom => write!(f, "Custom"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KomorebiLayout {
|
||||||
|
fn is_default(&mut self) -> bool {
|
||||||
|
matches!(self, KomorebiLayout::Default(_))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_click(
|
||||||
|
&mut self,
|
||||||
|
show_options: &bool,
|
||||||
|
monitor_idx: usize,
|
||||||
|
workspace_idx: Option<usize>,
|
||||||
|
) -> bool {
|
||||||
|
if self.is_default() {
|
||||||
|
!show_options
|
||||||
|
} else {
|
||||||
|
self.on_click_option(monitor_idx, workspace_idx);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_click_option(&mut self, monitor_idx: usize, workspace_idx: Option<usize>) {
|
||||||
|
match self {
|
||||||
|
KomorebiLayout::Default(option) => {
|
||||||
|
if let Some(ws_idx) = workspace_idx {
|
||||||
|
if komorebi_client::send_message(&SocketMessage::WorkspaceLayout(
|
||||||
|
monitor_idx,
|
||||||
|
ws_idx,
|
||||||
|
*option,
|
||||||
|
))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
tracing::error!("could not send message to komorebi: WorkspaceLayout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KomorebiLayout::Monocle => {
|
||||||
|
if komorebi_client::send_message(&SocketMessage::ToggleMonocle).is_err() {
|
||||||
|
tracing::error!("could not send message to komorebi: ToggleMonocle");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KomorebiLayout::Floating => {
|
||||||
|
if komorebi_client::send_message(&SocketMessage::ToggleTiling).is_err() {
|
||||||
|
tracing::error!("could not send message to komorebi: ToggleTiling");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KomorebiLayout::Paused => {
|
||||||
|
if komorebi_client::send_message(&SocketMessage::TogglePause).is_err() {
|
||||||
|
tracing::error!("could not send message to komorebi: TogglePause");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
KomorebiLayout::Custom => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_icon(&mut self, font_id: FontId, ctx: &Context, ui: &mut Ui) {
|
||||||
|
// paint custom icons for the layout
|
||||||
|
let size = Vec2::splat(font_id.size);
|
||||||
|
let (response, painter) = ui.allocate_painter(size, Sense::hover());
|
||||||
|
let color = ctx.style().visuals.selection.stroke.color;
|
||||||
|
let stroke = Stroke::new(1.0, 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);
|
||||||
|
|
||||||
|
match self {
|
||||||
|
KomorebiLayout::Default(layout) => match layout {
|
||||||
|
komorebi_client::DefaultLayout::BSP => {
|
||||||
|
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
|
||||||
|
painter.line_segment([c, c + vec2(r, 0.0)], stroke);
|
||||||
|
painter.line_segment([c + vec2(r / 2.0, 0.0), c + vec2(r / 2.0, r)], stroke);
|
||||||
|
}
|
||||||
|
komorebi_client::DefaultLayout::Columns => {
|
||||||
|
painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke);
|
||||||
|
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
|
||||||
|
painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke);
|
||||||
|
}
|
||||||
|
komorebi_client::DefaultLayout::Rows => {
|
||||||
|
painter.line_segment([c - vec2(r, r / 2.0), c + vec2(r, -r / 2.0)], stroke);
|
||||||
|
painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);
|
||||||
|
painter.line_segment([c - vec2(r, -r / 2.0), c + vec2(r, r / 2.0)], stroke);
|
||||||
|
}
|
||||||
|
komorebi_client::DefaultLayout::VerticalStack => {
|
||||||
|
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
|
||||||
|
painter.line_segment([c, c + vec2(r, 0.0)], stroke);
|
||||||
|
}
|
||||||
|
komorebi_client::DefaultLayout::RightMainVerticalStack => {
|
||||||
|
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
|
||||||
|
painter.line_segment([c - vec2(r, 0.0), c], stroke);
|
||||||
|
}
|
||||||
|
komorebi_client::DefaultLayout::HorizontalStack => {
|
||||||
|
painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);
|
||||||
|
painter.line_segment([c, c + vec2(0.0, r)], stroke);
|
||||||
|
}
|
||||||
|
komorebi_client::DefaultLayout::UltrawideVerticalStack => {
|
||||||
|
painter.line_segment([c - vec2(r / 2.0, r), c + vec2(-r / 2.0, r)], stroke);
|
||||||
|
painter.line_segment([c + vec2(r / 2.0, 0.0), c + vec2(r, 0.0)], stroke);
|
||||||
|
painter.line_segment([c - vec2(-r / 2.0, r), c + vec2(r / 2.0, r)], stroke);
|
||||||
|
}
|
||||||
|
komorebi_client::DefaultLayout::Grid => {
|
||||||
|
painter.line_segment([c - vec2(r, 0.0), c + vec2(r, 0.0)], stroke);
|
||||||
|
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
KomorebiLayout::Monocle => {}
|
||||||
|
KomorebiLayout::Floating => {
|
||||||
|
let mut rect_left = response.rect;
|
||||||
|
rect_left.set_width(rect.width() * 0.5);
|
||||||
|
rect_left.set_height(rect.height() * 0.5);
|
||||||
|
let mut rect_right = rect_left;
|
||||||
|
rect_left = rect_left.translate(Vec2::new(
|
||||||
|
rect.width() * 0.1 + stroke.width,
|
||||||
|
rect.width() * 0.1 + stroke.width,
|
||||||
|
));
|
||||||
|
rect_right = rect_right.translate(Vec2::new(
|
||||||
|
rect.width() * 0.35 + stroke.width,
|
||||||
|
rect.width() * 0.35 + stroke.width,
|
||||||
|
));
|
||||||
|
painter.rect_filled(rect_left, rounding, color);
|
||||||
|
painter.rect_stroke(rect_right, rounding, stroke);
|
||||||
|
}
|
||||||
|
KomorebiLayout::Paused => {
|
||||||
|
let mut rect_left = response.rect;
|
||||||
|
rect_left.set_width(rect.width() * 0.25);
|
||||||
|
rect_left.set_height(rect.height() * 0.8);
|
||||||
|
let mut rect_right = rect_left;
|
||||||
|
rect_left = rect_left.translate(Vec2::new(
|
||||||
|
rect.width() * 0.2 + stroke.width,
|
||||||
|
rect.width() * 0.1 + stroke.width,
|
||||||
|
));
|
||||||
|
rect_right = rect_right.translate(Vec2::new(
|
||||||
|
rect.width() * 0.55 + stroke.width,
|
||||||
|
rect.width() * 0.1 + stroke.width,
|
||||||
|
));
|
||||||
|
painter.rect_filled(rect_left, rounding, color);
|
||||||
|
painter.rect_filled(rect_right, rounding, color);
|
||||||
|
}
|
||||||
|
KomorebiLayout::Custom => {
|
||||||
|
painter.line_segment([c - vec2(0.0, r), c + vec2(0.0, r)], stroke);
|
||||||
|
painter.line_segment([c + vec2(0.0, r / 2.0), c + vec2(r, r / 2.0)], stroke);
|
||||||
|
painter.line_segment([c - vec2(0.0, r / 3.0), c - vec2(r, r / 3.0)], stroke);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show(
|
||||||
|
&mut self,
|
||||||
|
ctx: &Context,
|
||||||
|
ui: &mut Ui,
|
||||||
|
render_config: &mut RenderConfig,
|
||||||
|
layout_config: &KomorebiLayoutConfig,
|
||||||
|
workspace_idx: Option<usize>,
|
||||||
|
) {
|
||||||
|
let monitor_idx = render_config.monitor_idx;
|
||||||
|
let font_id = ctx
|
||||||
|
.style()
|
||||||
|
.text_styles
|
||||||
|
.get(&TextStyle::Body)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(FontId::default);
|
||||||
|
|
||||||
|
let mut show_options = RenderConfig::load_show_komorebi_layout_options();
|
||||||
|
let format = layout_config.display.unwrap_or(DisplayFormat::IconAndText);
|
||||||
|
|
||||||
|
if !self.is_default() {
|
||||||
|
show_options = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
render_config.apply_on_widget(false, ui, |ui| {
|
||||||
|
let layout_frame = SelectableFrame::new(false)
|
||||||
|
.show(ui, |ui| {
|
||||||
|
if let DisplayFormat::Icon | DisplayFormat::IconAndText = format {
|
||||||
|
self.show_icon(font_id.clone(), ctx, ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let DisplayFormat::Text | DisplayFormat::IconAndText = format {
|
||||||
|
ui.add(Label::new(self.to_string()).selectable(false));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on_hover_text(self.to_string());
|
||||||
|
|
||||||
|
if layout_frame.clicked() {
|
||||||
|
show_options = self.on_click(&show_options, monitor_idx, workspace_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
if show_options {
|
||||||
|
if let Some(workspace_idx) = workspace_idx {
|
||||||
|
Frame::none().show(ui, |ui| {
|
||||||
|
ui.add(
|
||||||
|
Label::new(egui_phosphor::regular::ARROW_FAT_LINES_RIGHT.to_string())
|
||||||
|
.selectable(false),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut layout_options = layout_config.options.clone().unwrap_or(vec![
|
||||||
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::BSP),
|
||||||
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::Columns),
|
||||||
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::Rows),
|
||||||
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::VerticalStack),
|
||||||
|
KomorebiLayout::Default(
|
||||||
|
komorebi_client::DefaultLayout::RightMainVerticalStack,
|
||||||
|
),
|
||||||
|
KomorebiLayout::Default(
|
||||||
|
komorebi_client::DefaultLayout::HorizontalStack,
|
||||||
|
),
|
||||||
|
KomorebiLayout::Default(
|
||||||
|
komorebi_client::DefaultLayout::UltrawideVerticalStack,
|
||||||
|
),
|
||||||
|
KomorebiLayout::Default(komorebi_client::DefaultLayout::Grid),
|
||||||
|
//KomorebiLayout::Custom,
|
||||||
|
KomorebiLayout::Monocle,
|
||||||
|
KomorebiLayout::Floating,
|
||||||
|
KomorebiLayout::Paused,
|
||||||
|
]);
|
||||||
|
|
||||||
|
for layout_option in &mut layout_options {
|
||||||
|
if SelectableFrame::new(self == layout_option)
|
||||||
|
.show(ui, |ui| layout_option.show_icon(font_id.clone(), ctx, ui))
|
||||||
|
.on_hover_text(match layout_option {
|
||||||
|
KomorebiLayout::Default(layout) => layout.to_string(),
|
||||||
|
KomorebiLayout::Monocle => "Toggle monocle".to_string(),
|
||||||
|
KomorebiLayout::Floating => "Toggle tiling".to_string(),
|
||||||
|
KomorebiLayout::Paused => "Toggle pause".to_string(),
|
||||||
|
KomorebiLayout::Custom => "Custom".to_string(),
|
||||||
|
})
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
layout_option.on_click_option(monitor_idx, Some(workspace_idx));
|
||||||
|
show_options = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
RenderConfig::store_show_komorebi_layout_options(show_options);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,12 @@ mod config;
|
|||||||
mod cpu;
|
mod cpu;
|
||||||
mod date;
|
mod date;
|
||||||
mod komorebi;
|
mod komorebi;
|
||||||
|
mod komorebi_layout;
|
||||||
mod media;
|
mod media;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod network;
|
mod network;
|
||||||
mod render;
|
mod render;
|
||||||
|
mod selected_frame;
|
||||||
mod storage;
|
mod storage;
|
||||||
mod time;
|
mod time;
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ use eframe::egui::Vec2;
|
|||||||
use schemars::JsonSchema;
|
use schemars::JsonSchema;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use std::sync::atomic::AtomicUsize;
|
||||||
|
use std::sync::atomic::Ordering;
|
||||||
|
|
||||||
|
static SHOW_KOMOREBI_LAYOUT_OPTIONS: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
|
||||||
#[serde(tag = "kind")]
|
#[serde(tag = "kind")]
|
||||||
@@ -27,6 +31,8 @@ pub enum Grouping {
|
|||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
pub struct RenderConfig {
|
pub struct RenderConfig {
|
||||||
|
/// Komorebi monitor index of the monitor on which to render the bar
|
||||||
|
pub monitor_idx: usize,
|
||||||
/// Spacing between widgets
|
/// Spacing between widgets
|
||||||
pub spacing: f32,
|
pub spacing: f32,
|
||||||
/// Sets how widgets are grouped
|
/// Sets how widgets are grouped
|
||||||
@@ -48,6 +54,7 @@ pub trait RenderExt {
|
|||||||
impl RenderExt for &KomobarConfig {
|
impl RenderExt for &KomobarConfig {
|
||||||
fn new_renderconfig(&self, background_color: Color32) -> RenderConfig {
|
fn new_renderconfig(&self, background_color: Color32) -> RenderConfig {
|
||||||
RenderConfig {
|
RenderConfig {
|
||||||
|
monitor_idx: self.monitor.index,
|
||||||
spacing: self.widget_spacing.unwrap_or(10.0),
|
spacing: self.widget_spacing.unwrap_or(10.0),
|
||||||
grouping: self.grouping.unwrap_or(Grouping::None),
|
grouping: self.grouping.unwrap_or(Grouping::None),
|
||||||
background_color,
|
background_color,
|
||||||
@@ -59,8 +66,17 @@ impl RenderExt for &KomobarConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RenderConfig {
|
impl RenderConfig {
|
||||||
|
pub fn load_show_komorebi_layout_options() -> bool {
|
||||||
|
SHOW_KOMOREBI_LAYOUT_OPTIONS.load(Ordering::SeqCst) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn store_show_komorebi_layout_options(show: bool) {
|
||||||
|
SHOW_KOMOREBI_LAYOUT_OPTIONS.store(show as usize, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
monitor_idx: 0,
|
||||||
spacing: 0.0,
|
spacing: 0.0,
|
||||||
grouping: Grouping::None,
|
grouping: Grouping::None,
|
||||||
background_color: Color32::BLACK,
|
background_color: Color32::BLACK,
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
use eframe::egui::Frame;
|
||||||
|
use eframe::egui::Margin;
|
||||||
|
use eframe::egui::Response;
|
||||||
|
use eframe::egui::Sense;
|
||||||
|
use eframe::egui::Ui;
|
||||||
|
|
||||||
|
/// Same as SelectableLabel, but supports all content
|
||||||
|
pub struct SelectableFrame {
|
||||||
|
selected: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SelectableFrame {
|
||||||
|
pub fn new(selected: bool) -> Self {
|
||||||
|
Self { selected }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> Response {
|
||||||
|
let Self { selected } = self;
|
||||||
|
|
||||||
|
Frame::none()
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let response = ui.interact(ui.max_rect(), ui.unique_id(), Sense::click());
|
||||||
|
|
||||||
|
if ui.is_rect_visible(response.rect) {
|
||||||
|
let inner_margin = Margin::symmetric(
|
||||||
|
ui.style().spacing.button_padding.x,
|
||||||
|
ui.style().spacing.button_padding.y,
|
||||||
|
);
|
||||||
|
|
||||||
|
if selected
|
||||||
|
|| response.hovered()
|
||||||
|
|| response.highlighted()
|
||||||
|
|| response.has_focus()
|
||||||
|
{
|
||||||
|
let visuals = ui.style().interact_selectable(&response, selected);
|
||||||
|
|
||||||
|
Frame::none()
|
||||||
|
.stroke(visuals.bg_stroke)
|
||||||
|
.rounding(visuals.rounding)
|
||||||
|
.fill(visuals.bg_fill)
|
||||||
|
.inner_margin(inner_margin)
|
||||||
|
.show(ui, add_contents);
|
||||||
|
} else {
|
||||||
|
Frame::none()
|
||||||
|
.inner_margin(inner_margin)
|
||||||
|
.show(ui, add_contents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response
|
||||||
|
})
|
||||||
|
.inner
|
||||||
|
.on_hover_cursor(eframe::egui::CursorIcon::PointingHand)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user