mirror of
https://github.com/LGUG2Z/komorebi.git
synced 2026-04-25 10:08:33 +02:00
feat(bar): expand focused window subwidget
This commit ensures that the focused window komorebi subwidget is aware of multi-window containers and displays an ordered list of windows in a container stack which can be clicked to change focus in the stack. When there are >1 windows in a stack, the title of the focused window will take from komorebi.json's theme.stack_border if theme is defined (falling back to the same default value in komorebi), or from theme.accent in komorebi.bar.json, if defined.
This commit is contained in:
@@ -22,8 +22,11 @@ use eframe::egui::Vec2;
|
|||||||
use eframe::egui::ViewportCommand;
|
use eframe::egui::ViewportCommand;
|
||||||
use font_loader::system_fonts;
|
use font_loader::system_fonts;
|
||||||
use font_loader::system_fonts::FontPropertyBuilder;
|
use font_loader::system_fonts::FontPropertyBuilder;
|
||||||
|
use komorebi_client::KomorebiTheme;
|
||||||
use komorebi_themes::catppuccin_egui;
|
use komorebi_themes::catppuccin_egui;
|
||||||
|
use komorebi_themes::Base16Value;
|
||||||
use komorebi_themes::Catppuccin;
|
use komorebi_themes::Catppuccin;
|
||||||
|
use komorebi_themes::CatppuccinValue;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@@ -168,6 +171,21 @@ impl Komobar {
|
|||||||
Ok(config) => {
|
Ok(config) => {
|
||||||
if let Some(theme) = config.theme {
|
if let Some(theme) = config.theme {
|
||||||
apply_theme(ctx, KomobarTheme::from(theme), self.bg_color.clone());
|
apply_theme(ctx, KomobarTheme::from(theme), self.bg_color.clone());
|
||||||
|
|
||||||
|
let stack_accent = match theme {
|
||||||
|
KomorebiTheme::Catppuccin {
|
||||||
|
name, stack_border, ..
|
||||||
|
} => stack_border
|
||||||
|
.unwrap_or(CatppuccinValue::Green)
|
||||||
|
.color32(name.as_theme()),
|
||||||
|
KomorebiTheme::Base16 {
|
||||||
|
name, stack_border, ..
|
||||||
|
} => stack_border.unwrap_or(Base16Value::Base0B).color32(name),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(state) = &self.komorebi_notification_state {
|
||||||
|
state.borrow_mut().stack_accent = Some(stack_accent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
|
|||||||
@@ -3,13 +3,16 @@ use crate::config::KomobarTheme;
|
|||||||
use crate::widget::BarWidget;
|
use crate::widget::BarWidget;
|
||||||
use crate::WIDGET_SPACING;
|
use crate::WIDGET_SPACING;
|
||||||
use crossbeam_channel::Receiver;
|
use crossbeam_channel::Receiver;
|
||||||
|
use eframe::egui::text::LayoutJob;
|
||||||
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::Image;
|
use eframe::egui::Image;
|
||||||
use eframe::egui::Label;
|
use eframe::egui::Label;
|
||||||
use eframe::egui::SelectableLabel;
|
use eframe::egui::SelectableLabel;
|
||||||
use eframe::egui::Sense;
|
use eframe::egui::Sense;
|
||||||
|
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;
|
||||||
@@ -94,14 +97,13 @@ impl From<&KomorebiConfig> for Komorebi {
|
|||||||
Self {
|
Self {
|
||||||
komorebi_notification_state: Rc::new(RefCell::new(KomorebiNotificationState {
|
komorebi_notification_state: Rc::new(RefCell::new(KomorebiNotificationState {
|
||||||
selected_workspace: String::new(),
|
selected_workspace: String::new(),
|
||||||
focused_window_title: String::new(),
|
|
||||||
focused_window_pid: None,
|
|
||||||
focused_window_icon: None,
|
|
||||||
layout: String::new(),
|
layout: String::new(),
|
||||||
workspaces: vec![],
|
workspaces: vec![],
|
||||||
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),
|
||||||
|
stack_accent: None,
|
||||||
})),
|
})),
|
||||||
workspaces: value.workspaces,
|
workspaces: value.workspaces,
|
||||||
layout: value.layout,
|
layout: value.layout,
|
||||||
@@ -257,22 +259,80 @@ 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 {
|
||||||
if focused_window.show_icon {
|
let titles = &komorebi_notification_state.focused_container_information.0;
|
||||||
if let Some(img) = &komorebi_notification_state.focused_window_icon {
|
let icons = &komorebi_notification_state.focused_container_information.1;
|
||||||
ui.add(
|
let focused_window_idx =
|
||||||
Image::from(&img_to_texture(ctx, img))
|
komorebi_notification_state.focused_container_information.2;
|
||||||
.maintain_aspect_ratio(true)
|
|
||||||
.max_height(15.0),
|
let iter = titles.iter().zip(icons.iter());
|
||||||
);
|
|
||||||
|
for (i, (title, icon)) in iter.enumerate() {
|
||||||
|
if focused_window.show_icon {
|
||||||
|
if let Some(img) = icon {
|
||||||
|
ui.add(
|
||||||
|
Image::from(&img_to_texture(ctx, img))
|
||||||
|
.maintain_aspect_ratio(true)
|
||||||
|
.max_height(15.0),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if i == focused_window_idx {
|
||||||
|
let font_id = ctx
|
||||||
|
.style()
|
||||||
|
.text_styles
|
||||||
|
.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 {
|
||||||
|
ui.add(Label::new(layout_job).selectable(false));
|
||||||
|
} else {
|
||||||
|
ui.add(Label::new(title).selectable(false));
|
||||||
|
}
|
||||||
|
} else if ui
|
||||||
|
.add(Label::new(title).selectable(false).sense(Sense::click()))
|
||||||
|
.clicked()
|
||||||
|
{
|
||||||
|
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(false))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"could not send message to komorebi: MouseFollowsFocus"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if komorebi_client::send_message(&SocketMessage::FocusStackWindow(i))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
tracing::error!("could not send message to komorebi: FocusStackWindow");
|
||||||
|
}
|
||||||
|
|
||||||
|
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
|
||||||
|
komorebi_notification_state.mouse_follows_focus,
|
||||||
|
))
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
|
tracing::error!(
|
||||||
|
"could not send message to komorebi: MouseFollowsFocus"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.add_space(WIDGET_SPACING);
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.add(
|
|
||||||
Label::new(&komorebi_notification_state.focused_window_title).selectable(false),
|
|
||||||
);
|
|
||||||
|
|
||||||
ui.add_space(WIDGET_SPACING);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ui.add_space(WIDGET_SPACING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -288,13 +348,12 @@ fn img_to_texture(ctx: &Context, rgba_image: &RgbaImage) -> TextureHandle {
|
|||||||
pub struct KomorebiNotificationState {
|
pub struct KomorebiNotificationState {
|
||||||
pub workspaces: Vec<String>,
|
pub workspaces: Vec<String>,
|
||||||
pub selected_workspace: String,
|
pub selected_workspace: String,
|
||||||
pub focused_window_title: String,
|
pub focused_container_information: (Vec<String>, Vec<Option<RgbaImage>>, usize),
|
||||||
pub focused_window_pid: Option<u32>,
|
|
||||||
pub focused_window_icon: Option<RgbaImage>,
|
|
||||||
pub layout: String,
|
pub layout: String,
|
||||||
pub hide_empty_workspaces: bool,
|
pub hide_empty_workspaces: bool,
|
||||||
pub mouse_follows_focus: bool,
|
pub mouse_follows_focus: bool,
|
||||||
pub work_area_offset: Option<Rect>,
|
pub work_area_offset: Option<Rect>,
|
||||||
|
pub stack_accent: Option<Color32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KomorebiNotificationState {
|
impl KomorebiNotificationState {
|
||||||
@@ -355,53 +414,39 @@ impl KomorebiNotificationState {
|
|||||||
|
|
||||||
if let Some(container) = monitor.workspaces()[focused_workspace_idx].monocle_container()
|
if let Some(container) = monitor.workspaces()[focused_workspace_idx].monocle_container()
|
||||||
{
|
{
|
||||||
if let Some(window) = container.focused_window() {
|
self.focused_container_information = (
|
||||||
if let Ok(title) = window.title() {
|
container
|
||||||
self.focused_window_title.clone_from(&title);
|
.windows()
|
||||||
self.focused_window_pid = Some(window.process_id());
|
.iter()
|
||||||
if let Some(img) =
|
.map(|w| w.title().unwrap_or_default())
|
||||||
windows_icons::get_icon_by_process_id(window.process_id())
|
.collect::<Vec<_>>(),
|
||||||
{
|
container
|
||||||
self.focused_window_icon = Some(img);
|
.windows()
|
||||||
} else {
|
.iter()
|
||||||
self.focused_window_icon = None;
|
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
|
||||||
}
|
.collect::<Vec<_>>(),
|
||||||
}
|
container.focused_window_idx(),
|
||||||
}
|
);
|
||||||
} else if let Some(container) =
|
} else if let Some(container) =
|
||||||
monitor.workspaces()[focused_workspace_idx].focused_container()
|
monitor.workspaces()[focused_workspace_idx].focused_container()
|
||||||
{
|
{
|
||||||
if let Some(window) = container.focused_window() {
|
self.focused_container_information = (
|
||||||
if let Ok(title) = window.title() {
|
container
|
||||||
self.focused_window_title.clone_from(&title);
|
.windows()
|
||||||
self.focused_window_pid = Some(window.process_id());
|
.iter()
|
||||||
if let Some(img) =
|
.map(|w| w.title().unwrap_or_default())
|
||||||
windows_icons::get_icon_by_process_id(window.process_id())
|
.collect::<Vec<_>>(),
|
||||||
{
|
container
|
||||||
self.focused_window_icon = Some(img);
|
.windows()
|
||||||
} else {
|
.iter()
|
||||||
self.focused_window_icon = None;
|
.map(|w| windows_icons::get_icon_by_process_id(w.process_id()))
|
||||||
}
|
.collect::<Vec<_>>(),
|
||||||
}
|
container.focused_window_idx(),
|
||||||
}
|
);
|
||||||
} else {
|
} else {
|
||||||
self.focused_window_title.clear();
|
self.focused_container_information.0.clear();
|
||||||
self.focused_window_icon = None;
|
self.focused_container_information.1.clear();
|
||||||
}
|
self.focused_container_information.2 = 0;
|
||||||
|
|
||||||
if let Some(container) = monitor.workspaces()[focused_workspace_idx].monocle_container()
|
|
||||||
{
|
|
||||||
if let Some(window) = container.focused_window() {
|
|
||||||
if let Ok(title) = window.title() {
|
|
||||||
self.focused_window_title.clone_from(&title);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(window) = monitor.workspaces()[focused_workspace_idx].maximized_window() {
|
|
||||||
if let Ok(title) = window.title() {
|
|
||||||
self.focused_window_title.clone_from(&title);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,8 @@ impl BarWidget for Media {
|
|||||||
.add(
|
.add(
|
||||||
Label::new(layout_job)
|
Label::new(layout_job)
|
||||||
.selectable(false)
|
.selectable(false)
|
||||||
.sense(Sense::click()),
|
.sense(Sense::click())
|
||||||
|
.truncate(),
|
||||||
)
|
)
|
||||||
.clicked()
|
.clicked()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user