mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-20 15:51:32 +02:00
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)
312 lines
13 KiB
Rust
312 lines
13 KiB
Rust
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);
|
|
}
|
|
}
|