Compare commits

..

9 Commits

Author SHA1 Message Date
LGUG2Z
559fa55b9f hardcoded cursor at top left 2024-11-19 20:49:39 -08:00
LGUG2Z
041ef5731c refactor(bar): use monitor idx when switching ws
This commit follows up on a point made by @notTamion in #1128 - since we
have the monitor index, we can use it in the bar's workspace widget to
more accurately target workspaces via
SocketMessage::FocusMonitorWorkspaceNumber.
2024-11-19 20:44:48 -08:00
LGUG2Z
779c12bc6a docs(schema): update all json schemas 2024-11-19 19:52:33 -08:00
LGUG2Z
0e48370b73 style(bar): add aliases for default grouping style
This commit adds the "CtByte" and "CtByteWithShadow" aliases for the
"Default" and "DefaultWithShadow" GroupingStyle variants respectively as
an easter egg to recognize @CtByte's work in implementing the grouping
feature.
2024-11-19 19:48:10 -08:00
LGUG2Z
8de92ec32a docs(bar): add a position.end.y val warning
For dummies like me who set this to 0.0 and then wonder why the bar
disappears on config change.
2024-11-19 19:42:36 -08:00
LGUG2Z
ac243c6992 fix(bar): read latest transparency_alpha value
This commit is a small fix which ensures that the latest value of
transparency_alpha will be read and applied from config rather than
self.config in the apply_config fn.
2024-11-19 19:33:02 -08:00
LGUG2Z
d001d8a7a6 fix(wm): ensure focus restore on float + max hwnds
This commit ensures that when switching to a workspace which contains a
floating window or a maximized window, either the maximized window or
the first floating window index will be focused.

This is to prevent windows which have been hidden on the previous
workspace from retaining keyboard focus.

fix #1130
2024-11-19 19:01:38 -08:00
CtByte
219fa8e14f feat(bar): add widget grouping options
This commit adds various widget grouping and transparency options to
komorebi-bar, and is comprised of the individual commits listed below,
worked on in PR #1108, squashed into one.

e8f5952abb
* adding RenderConfig, and some test frames on widgets

0a5e0a4c0a
* no clone

a5a7d6906c
* comment

6a91dd46cd
* ignore unused

80f0214e47
* Group enum, Copy RenderConfig

fbe5e2c1f7
* Group -> Grouping

ce49b433f9
* GroupingConfig

f446a6a45f
* "fmt --check" fix (thanks VS)

d188222be7
* added widget grouping and group module

1008ec2031
* rounding from settings, and apply_on_side

7fff6d29a9
* dereferencing

655e8ce4c1
* AlphaColour, transparency, bar background, more grouping config options

cba0fcd882
* added RoundingConfig

ec5f7dc82d
* handling grouping edge case for komorebi focus window

12117b832b
* changed default values

645c46beb8
* background color using theme color, AlphaColour.to_color32_or, updating json format for Grouping and RoundingConfig

10d2ab21c7
* hot-reload on grouping

d88774328a
* grouping correction on init

2cd237fd0d
* added shadow to grouping, optional width on grouping stroke

4f4b617f26
* grouping on bar, converting AlphaColour from_rgba_unmultiplied, simplified grouping

3808fcec8f
* widget rounding based on grouping, atomic background color, simplified config, style on grouping

be45d14f6d
* renamed Side to Alignment, group spacing

be45d14f6d
* proper widget spacing based on alignment

b43a5bda69
* added widget_spacing to config

c18e5f4dbe
* test commit

cba2b2f7ac
* refactoring of render and grouping, widget spacing WIP

9311cb00ec
* simplify no_spacing

36c267246b
* correct spacing on komorebi and network widgets (WIP)

85a41bf5b2
* correct widget spacing on all widgets

50b49ccf69
* refactoring widget spacing

9ec67ad988
* account for ui item_spacing when setting the widget_spacing

e88a2fd9c0
* format
2024-11-18 19:56:00 -08:00
LGUG2Z
e4e94fd1a6 feat(borders): use direct2d for anti-aliasing
This commit overhauls the "Komorebi" borders implementation to use
Direct2D, which enables anti-aliasing for rounded borders.

A lot of the heavy lifting was done by @lukeyou05 in the tacky-borders
project, which this commit largely adapts to komorebi. @lukeyou05
provided an incredible amount of guidance and feedback on the
implementation of this feature on the komorebi Discord.

This commit is a squashed interactive rebase of the following commits:

238271a71e
feat(borders): initial impl of direct2d border drawing

5525a382b9
feat(borders): avoid multiple render target creation calls

431970d7b6
feat(borders): reduce redraws to improve perf

47cb19e54a
feat(borders): remove black pixels around direct2d corners

3857d1a46c
feat(borders): clean up render targets on destroy
2024-11-17 10:57:57 -08:00
19 changed files with 957 additions and 366 deletions

View File

@@ -5,6 +5,10 @@ use crate::config::PositionConfig;
use crate::komorebi::Komorebi;
use crate::komorebi::KomorebiNotificationState;
use crate::process_hwnd;
use crate::render::Color32Ext;
use crate::render::Grouping;
use crate::render::RenderConfig;
use crate::render::RenderExt;
use crate::widget::BarWidget;
use crate::widget::WidgetConfig;
use crate::BAR_HEIGHT;
@@ -24,8 +28,10 @@ use eframe::egui::FontId;
use eframe::egui::Frame;
use eframe::egui::Layout;
use eframe::egui::Margin;
use eframe::egui::Rgba;
use eframe::egui::Style;
use eframe::egui::TextStyle;
use eframe::egui::Visuals;
use font_loader::system_fonts;
use font_loader::system_fonts::FontPropertyBuilder;
use komorebi_client::KomorebiTheme;
@@ -41,6 +47,7 @@ use std::sync::Arc;
pub struct Komobar {
pub config: Arc<KomobarConfig>,
pub render_config: Rc<RefCell<RenderConfig>>,
pub komorebi_notification_state: Option<Rc<RefCell<KomorebiNotificationState>>>,
pub left_widgets: Vec<Box<dyn BarWidget>>,
pub right_widgets: Vec<Box<dyn BarWidget>>,
@@ -171,6 +178,10 @@ impl Komobar {
y: BAR_HEIGHT,
});
if end.y == 0.0 {
tracing::warn!("position.end.y is set to 0.0 which will make your bar invisible on a config reload - this is usually set to 50.0 by default")
}
let rect = komorebi_client::Rect {
left: start.x as i32,
top: start.y as i32,
@@ -237,6 +248,30 @@ impl Komobar {
}
}
// apply rounding to the widgets
if let Some(
Grouping::Bar(config) | Grouping::Alignment(config) | Grouping::Widget(config),
) = &config.grouping
{
if let Some(rounding) = config.rounding {
ctx.style_mut(|style| {
style.visuals.widgets.noninteractive.rounding = rounding.into();
style.visuals.widgets.inactive.rounding = rounding.into();
style.visuals.widgets.hovered.rounding = rounding.into();
style.visuals.widgets.active.rounding = rounding.into();
style.visuals.widgets.open.rounding = rounding.into();
});
}
}
let theme_color = *self.bg_color.borrow();
self.render_config
.replace(config.new_renderconfig(theme_color));
self.bg_color
.replace(theme_color.try_apply_alpha(config.transparency_alpha));
if let Some(font_size) = &config.font_size {
tracing::info!("attempting to set custom font size: {font_size}");
Self::set_font_size(ctx, *font_size);
@@ -251,7 +286,7 @@ impl Komobar {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(config));
komorebi_widget_idx = Some(idx);
side = Some(Side::Left);
side = Some(Alignment::Left);
}
}
@@ -259,7 +294,7 @@ impl Komobar {
if let WidgetConfig::Komorebi(config) = widget_config {
komorebi_widget = Some(Komorebi::from(config));
komorebi_widget_idx = Some(idx);
side = Some(Side::Right);
side = Some(Alignment::Right);
}
}
@@ -293,8 +328,8 @@ impl Komobar {
let boxed: Box<dyn BarWidget> = Box::new(widget);
match side {
Side::Left => left_widgets[idx] = boxed,
Side::Right => right_widgets[idx] = boxed,
Alignment::Left => left_widgets[idx] = boxed,
Alignment::Right => right_widgets[idx] = boxed,
}
}
@@ -307,6 +342,7 @@ impl Komobar {
self.komorebi_notification_state = komorebi_notification_state;
}
pub fn new(
cc: &eframe::CreationContext<'_>,
rx_gui: Receiver<komorebi_client::Notification>,
@@ -315,6 +351,7 @@ impl Komobar {
) -> Self {
let mut komobar = Self {
config: config.clone(),
render_config: Rc::new(RefCell::new(RenderConfig::new())),
komorebi_notification_state: None,
left_widgets: vec![],
right_widgets: vec![],
@@ -385,13 +422,10 @@ impl Komobar {
}
}
impl eframe::App for Komobar {
// TODO: I think this is needed for transparency??
// fn clear_color(&self, _visuals: &Visuals) -> [f32; 4] {
// egui::Rgba::TRANSPARENT.to_array()
// let mut background = Color32::from_gray(18).to_normalized_gamma_f32();
// background[3] = 0.9;
// background
// }
// Needed for transparency
fn clear_color(&self, _visuals: &Visuals) -> [f32; 4] {
Rgba::TRANSPARENT.to_array()
}
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
if self.scale_factor != ctx.native_pixels_per_point().unwrap_or(1.0) {
@@ -433,18 +467,35 @@ impl eframe::App for Komobar {
Frame::none().fill(*self.bg_color.borrow())
};
CentralPanel::default().frame(frame).show(ctx, |ui| {
ui.horizontal_centered(|ui| {
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
for w in &mut self.left_widgets {
w.render(ctx, ui);
}
});
let mut render_config = self.render_config.borrow_mut();
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
for w in &mut self.right_widgets {
w.render(ctx, ui);
}
CentralPanel::default().frame(frame).show(ctx, |ui| {
// Apply grouping logic for the bar as a whole
render_config.clone().apply_on_bar(ui, |ui| {
ui.horizontal_centered(|ui| {
// Left-aligned widgets layout
ui.with_layout(Layout::left_to_right(Align::Center), |ui| {
let mut render_conf = *render_config;
render_conf.alignment = Some(Alignment::Left);
render_config.apply_on_alignment(ui, |ui| {
for w in &mut self.left_widgets {
w.render(ctx, ui, &mut render_conf);
}
});
});
// Right-aligned widgets layout
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
let mut render_conf = *render_config;
render_conf.alignment = Some(Alignment::Right);
render_config.apply_on_alignment(ui, |ui| {
for w in &mut self.right_widgets {
w.render(ctx, ui, &mut render_conf);
}
});
})
})
})
});
@@ -452,7 +503,7 @@ impl eframe::App for Komobar {
}
#[derive(Copy, Clone)]
enum Side {
pub enum Alignment {
Left,
Right,
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -115,7 +115,7 @@ impl Battery {
}
impl BarWidget for Battery {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
@@ -147,14 +147,14 @@ impl BarWidget for Battery {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
ui.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
);
config.apply_on_widget(true, ui, |ui| {
ui.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
);
});
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,3 +1,4 @@
use crate::render::Grouping;
use crate::widget::WidgetConfig;
use eframe::egui::Pos2;
use eframe::egui::TextBuffer;
@@ -28,6 +29,12 @@ pub struct KomobarConfig {
pub max_label_width: Option<f32>,
/// Theme
pub theme: Option<KomobarTheme>,
/// Alpha value for the color transparency [[0-255]] (default: 200)
pub transparency_alpha: Option<u8>,
/// Spacing between widgets (default: 10.0)
pub widget_spacing: Option<f32>,
/// Visual grouping for widgets
pub grouping: Option<Grouping>,
/// Left side widgets (ordered left-to-right)
pub left_widgets: Vec<WidgetConfig>,
/// Right side widgets (ordered left-to-right)

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -70,7 +70,7 @@ impl Cpu {
}
impl BarWidget for Cpu {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
@@ -99,22 +99,23 @@ impl BarWidget for Cpu {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
eprintln!("{}", error)
if let Err(error) =
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
{
eprintln!("{}", error)
}
}
}
});
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -86,7 +86,7 @@ impl Date {
}
impl BarWidget for Date {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let mut output = self.output();
if !output.is_empty() {
@@ -119,19 +119,19 @@ impl BarWidget for Date {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
if ui
.add(
Label::new(WidgetText::LayoutJob(layout_job.clone()))
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
self.format.next()
}
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(WidgetText::LayoutJob(layout_job.clone()))
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
self.format.next()
}
});
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,9 +1,10 @@
use crate::bar::apply_theme;
use crate::config::KomobarTheme;
use crate::render::RenderConfig;
use crate::ui::CustomUi;
use crate::widget::BarWidget;
use crate::MAX_LABEL_WIDTH;
use crate::WIDGET_SPACING;
use crate::MONITOR_INDEX;
use crossbeam_channel::Receiver;
use crossbeam_channel::TryRecvError;
use eframe::egui::text::LayoutJob;
@@ -103,6 +104,7 @@ impl From<&KomorebiConfig> for Komorebi {
work_area_offset: None,
focused_container_information: (vec![], vec![], 0),
stack_accent: None,
monitor_index: MONITOR_INDEX.load(Ordering::SeqCst),
})),
workspaces: value.workspaces,
layout: value.layout,
@@ -122,102 +124,126 @@ pub struct Komorebi {
}
impl BarWidget for Komorebi {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
let mut komorebi_notification_state = self.komorebi_notification_state.borrow_mut();
if self.workspaces.enable {
let mut update = None;
for (i, (ws, should_show)) in komorebi_notification_state.workspaces.iter().enumerate()
{
if *should_show
&& ui
.add(SelectableLabel::new(
komorebi_notification_state.selected_workspace.eq(ws),
ws.to_string(),
))
.clicked()
// NOTE: There should always be at least one workspace if the bar is connected to komorebi.
config.apply_on_widget(false, ui, |ui| {
for (i, (ws, should_show)) in
komorebi_notification_state.workspaces.iter().enumerate()
{
update = Some(ws.to_string());
let mut proceed = true;
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(false))
.is_err()
if *should_show
&& ui
.add(SelectableLabel::new(
komorebi_notification_state.selected_workspace.eq(ws),
ws.to_string(),
))
.clicked()
{
tracing::error!("could not send message to komorebi: MouseFollowsFocus");
proceed = false;
}
update = Some(ws.to_string());
let mut proceed = true;
if proceed
&& komorebi_client::send_message(&SocketMessage::FocusWorkspaceNumber(i))
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(false))
.is_err()
{
tracing::error!("could not send message to komorebi: FocusWorkspaceNumber");
proceed = false;
}
{
tracing::error!(
"could not send message to komorebi: MouseFollowsFocus"
);
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)
if proceed
&& komorebi_client::send_message(
&SocketMessage::FocusMonitorWorkspaceNumber(
komorebi_notification_state.monitor_index,
i,
),
)
.is_err()
{
tracing::error!("could not send message to komorebi: Retile");
{
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 {
komorebi_notification_state.selected_workspace = update;
}
ui.add_space(WIDGET_SPACING);
}
if let Some(layout) = self.layout {
if layout.enable {
if ui
.add(
Label::new(komorebi_notification_state.layout.to_string())
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
match komorebi_notification_state.layout {
KomorebiLayout::Default(_) => {
if komorebi_client::send_message(&SocketMessage::CycleLayout(
CycleDirection::Next,
))
.is_err()
{
tracing::error!("could not send message to komorebi: CycleLayout");
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(komorebi_notification_state.layout.to_string())
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
match komorebi_notification_state.layout {
KomorebiLayout::Default(_) => {
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::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::Paused => {
if komorebi_client::send_message(&SocketMessage::TogglePause)
.is_err()
{
tracing::error!(
"could not send message to komorebi: TogglePause"
);
}
}
KomorebiLayout::Custom => {}
}
KomorebiLayout::Custom => {}
}
}
ui.add_space(WIDGET_SPACING);
});
}
}
@@ -225,170 +251,174 @@ impl BarWidget for Komorebi {
if configuration_switcher.enable {
for (name, location) in configuration_switcher.configurations.iter() {
let path = PathBuf::from(location);
if path.is_file()
&& ui
if path.is_file() {
config.apply_on_widget(true, ui,|ui|{
if ui
.add(Label::new(name).selectable(false).sense(Sense::click()))
.clicked()
{
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path);
let mut proceed = true;
if komorebi_client::send_message(&SocketMessage::ReplaceConfiguration(
canonicalized,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: ReplaceConfiguration"
);
proceed = false;
}
let canonicalized = dunce::canonicalize(path.clone()).unwrap_or(path);
let mut proceed = true;
if komorebi_client::send_message(&SocketMessage::ReplaceConfiguration(
canonicalized,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: ReplaceConfiguration"
);
proceed = false;
}
if let Some(rect) = komorebi_notification_state.work_area_offset {
if proceed {
match komorebi_client::send_query(&SocketMessage::Query(
komorebi_client::StateQuery::FocusedMonitorIndex,
)) {
Ok(idx) => {
if let Ok(monitor_idx) = idx.parse::<usize>() {
if komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
rect,
),
)
.is_err()
{
tracing::error!(
if let Some(rect) = komorebi_notification_state.work_area_offset {
if proceed {
match komorebi_client::send_query(&SocketMessage::Query(
komorebi_client::StateQuery::FocusedMonitorIndex,
)) {
Ok(idx) => {
if let Ok(monitor_idx) = idx.parse::<usize>() {
if komorebi_client::send_message(
&SocketMessage::MonitorWorkAreaOffset(
monitor_idx,
rect,
),
)
.is_err()
{
tracing::error!(
"could not send message to komorebi: MonitorWorkAreaOffset"
);
}
}
}
}
Err(_) => {
tracing::error!(
"could not send message to komorebi: Query"
);
Err(_) => {
tracing::error!(
"could not send message to komorebi: Query"
);
}
}
}
}
}
}});
}
}
ui.add_space(WIDGET_SPACING);
}
}
if let Some(focused_window) = self.focused_window {
if focused_window.enable {
let titles = &komorebi_notification_state.focused_container_information.0;
let icons = &komorebi_notification_state.focused_container_information.1;
let focused_window_idx =
komorebi_notification_state.focused_container_information.2;
if !titles.is_empty() {
config.apply_on_widget(true, ui, |ui| {
let icons = &komorebi_notification_state.focused_container_information.1;
let focused_window_idx =
komorebi_notification_state.focused_container_information.2;
let iter = titles.iter().zip(icons.iter());
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 {
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(&SocketMessage::MouseFollowsFocus(
false,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: MouseFollowsFocus"
);
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 komorebi_client::send_message(&SocketMessage::FocusStackWindow(i))
.is_err()
{
tracing::error!(
"could not send message to komorebi: FocusStackWindow"
);
}
if i == focused_window_idx {
let font_id = ctx
.style()
.text_styles
.get(&TextStyle::Body)
.cloned()
.unwrap_or_else(FontId::default);
if komorebi_client::send_message(&SocketMessage::MouseFollowsFocus(
komorebi_notification_state.mouse_follows_focus,
))
.is_err()
{
tracing::error!(
"could not send message to komorebi: MouseFollowsFocus"
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(
&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_space(WIDGET_SPACING);
}
}
}
@@ -410,6 +440,7 @@ pub struct KomorebiNotificationState {
pub mouse_follows_focus: bool,
pub work_area_offset: Option<Rect>,
pub stack_accent: Option<Color32>,
pub monitor_index: usize,
}
#[derive(Copy, Clone, Debug)]
@@ -472,6 +503,8 @@ impl KomorebiNotificationState {
},
}
self.monitor_index = monitor_index;
self.mouse_follows_focus = notification.state.mouse_follows_focus;
let monitor = &notification.state.monitors.elements()[monitor_index];

View File

@@ -7,6 +7,7 @@ mod komorebi;
mod media;
mod memory;
mod network;
mod render;
mod storage;
mod time;
mod ui;
@@ -28,6 +29,7 @@ use std::io::BufReader;
use std::io::Read;
use std::path::PathBuf;
use std::sync::atomic::AtomicI32;
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::time::Duration;
@@ -42,12 +44,11 @@ use windows::Win32::UI::HiDpi::DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2;
use windows::Win32::UI::WindowsAndMessaging::EnumThreadWindows;
use windows::Win32::UI::WindowsAndMessaging::GetWindowThreadProcessId;
pub static WIDGET_SPACING: f32 = 10.0;
pub static MAX_LABEL_WIDTH: AtomicI32 = AtomicI32::new(400);
pub static MONITOR_LEFT: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_TOP: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_RIGHT: AtomicI32 = AtomicI32::new(0);
pub static MONITOR_INDEX: AtomicUsize = AtomicUsize::new(0);
pub static BAR_HEIGHT: f32 = 50.0;
#[derive(Parser)]
@@ -234,6 +235,8 @@ fn main() -> color_eyre::Result<()> {
Ordering::SeqCst,
);
MONITOR_INDEX.store(config.monitor.index, Ordering::SeqCst);
match config.position {
None => {
config.position = Some(PositionConfig {
@@ -266,7 +269,7 @@ fn main() -> color_eyre::Result<()> {
let viewport_builder = ViewportBuilder::default()
.with_decorations(false)
// .with_transparent(config.transparent)
.with_transparent(config.transparency_alpha.is_some())
.with_taskbar(false);
let native_options = eframe::NativeOptions {

View File

@@ -1,7 +1,7 @@
use crate::render::RenderConfig;
use crate::ui::CustomUi;
use crate::widget::BarWidget;
use crate::MAX_LABEL_WIDTH;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -78,7 +78,7 @@ impl Media {
}
impl BarWidget for Media {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
@@ -102,26 +102,26 @@ impl BarWidget for Media {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
let available_height = ui.available_height();
let mut custom_ui = CustomUi(ui);
config.apply_on_widget(true, ui, |ui| {
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(layout_job)
.selectable(false)
.sense(Sense::click())
.truncate(),
)
.clicked()
{
self.toggle();
}
ui.add_space(WIDGET_SPACING);
if 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)
.sense(Sense::click())
.truncate(),
)
.clicked()
{
self.toggle();
}
});
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -73,7 +73,7 @@ impl Memory {
}
impl BarWidget for Memory {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let output = self.output();
if !output.is_empty() {
@@ -102,22 +102,23 @@ impl BarWidget for Memory {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
eprintln!("{}", error)
if let Err(error) =
Command::new("cmd.exe").args(["/C", "taskmgr.exe"]).spawn()
{
eprintln!("{}", error)
}
}
}
});
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -317,21 +317,21 @@ impl Network {
}
impl BarWidget for Network {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.show_total_data_transmitted {
for output in self.total_data_transmitted() {
ui.add(Label::new(output).selectable(false));
config.apply_on_widget(true, ui, |ui| {
ui.add(Label::new(output).selectable(false));
});
}
ui.add_space(WIDGET_SPACING);
}
if self.show_network_activity {
for output in self.network_activity() {
ui.add(Label::new(output).selectable(false));
config.apply_on_widget(true, ui, |ui| {
ui.add(Label::new(output).selectable(false));
});
}
ui.add_space(WIDGET_SPACING);
}
if self.enable {
@@ -367,21 +367,21 @@ impl BarWidget for Network {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() {
eprintln!("{}", error)
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe").args(["/C", "ncpa"]).spawn() {
eprintln!("{}", error)
}
}
}
});
}
ui.add_space(WIDGET_SPACING);
}
}
}

265
komorebi-bar/src/render.rs Normal file
View File

@@ -0,0 +1,265 @@
use crate::bar::Alignment;
use crate::config::KomobarConfig;
use eframe::egui::Color32;
use eframe::egui::Frame;
use eframe::egui::InnerResponse;
use eframe::egui::Margin;
use eframe::egui::Rounding;
use eframe::egui::Shadow;
use eframe::egui::Ui;
use eframe::egui::Vec2;
use schemars::JsonSchema;
use serde::Deserialize;
use serde::Serialize;
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(tag = "kind")]
pub enum Grouping {
/// No grouping is applied
None,
/// Widgets are grouped as a whole
Bar(GroupingConfig),
/// Widgets are grouped by alignment
Alignment(GroupingConfig),
/// Widgets are grouped individually
Widget(GroupingConfig),
}
#[derive(Copy, Clone)]
pub struct RenderConfig {
/// Spacing between widgets
pub spacing: f32,
/// Sets how widgets are grouped
pub grouping: Grouping,
/// Background color
pub background_color: Color32,
/// Alignment of the widgets
pub alignment: Option<Alignment>,
/// Add more inner margin when adding a widget group
pub more_inner_margin: bool,
/// Set to true after the first time the apply_on_widget was called on an alignment
pub applied_on_widget: bool,
}
pub trait RenderExt {
fn new_renderconfig(&self, background_color: Color32) -> RenderConfig;
}
impl RenderExt for &KomobarConfig {
fn new_renderconfig(&self, background_color: Color32) -> RenderConfig {
RenderConfig {
spacing: self.widget_spacing.unwrap_or(10.0),
grouping: self.grouping.unwrap_or(Grouping::None),
background_color,
alignment: None,
more_inner_margin: false,
applied_on_widget: false,
}
}
}
impl RenderConfig {
pub fn new() -> Self {
Self {
spacing: 0.0,
grouping: Grouping::None,
background_color: Color32::BLACK,
alignment: None,
more_inner_margin: false,
applied_on_widget: false,
}
}
pub fn apply_on_bar<R>(
&mut self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.alignment = None;
if let Grouping::Bar(config) = self.grouping {
return self.define_group(None, config, ui, add_contents);
}
Self::fallback_group(ui, add_contents)
}
pub fn apply_on_alignment<R>(
&mut self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.alignment = None;
if let Grouping::Alignment(config) = self.grouping {
return self.define_group(None, config, ui, add_contents);
}
Self::fallback_group(ui, add_contents)
}
pub fn apply_on_widget<R>(
&mut self,
more_inner_margin: bool,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.more_inner_margin = more_inner_margin;
let outer_margin = self.widget_outer_margin(ui);
if let Grouping::Widget(config) = self.grouping {
return self.define_group(Some(outer_margin), config, ui, add_contents);
}
self.fallback_widget_group(Some(outer_margin), ui, add_contents)
}
fn fallback_group<R>(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
InnerResponse {
inner: add_contents(ui),
response: ui.response().clone(),
}
}
fn fallback_widget_group<R>(
&mut self,
outer_margin: Option<Margin>,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
Frame::none()
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
.inner_margin(match self.more_inner_margin {
true => Margin::symmetric(5.0, 0.0),
false => Margin::same(0.0),
})
.show(ui, add_contents)
}
fn define_group<R>(
&mut self,
outer_margin: Option<Margin>,
config: GroupingConfig,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
Frame::group(ui.style_mut())
.outer_margin(outer_margin.unwrap_or(Margin::ZERO))
.inner_margin(match self.more_inner_margin {
true => Margin::symmetric(8.0, 3.0),
false => Margin::symmetric(3.0, 3.0),
})
.stroke(ui.style().visuals.widgets.noninteractive.bg_stroke)
.rounding(match config.rounding {
Some(rounding) => rounding.into(),
None => ui.style().visuals.widgets.noninteractive.rounding,
})
.fill(
self.background_color
.try_apply_alpha(config.transparency_alpha),
)
.shadow(match config.style {
Some(style) => match style {
// new styles can be added if needed here
GroupingStyle::Default => Shadow::NONE,
GroupingStyle::DefaultWithShadow => Shadow {
blur: 4.0,
offset: Vec2::new(1.0, 1.0),
spread: 3.0,
color: Color32::BLACK.try_apply_alpha(config.transparency_alpha),
},
},
None => Shadow::NONE,
})
.show(ui, add_contents)
}
fn widget_outer_margin(&mut self, ui: &mut Ui) -> Margin {
let spacing = if self.applied_on_widget {
// Remove the default item spacing from the margin
self.spacing - ui.spacing().item_spacing.x
} else {
0.0
};
if !self.applied_on_widget {
self.applied_on_widget = true;
}
Margin {
left: match self.alignment {
Some(align) => match align {
Alignment::Left => spacing,
Alignment::Right => 0.0,
},
None => 0.0,
},
right: match self.alignment {
Some(align) => match align {
Alignment::Left => 0.0,
Alignment::Right => spacing,
},
None => 0.0,
},
top: 0.0,
bottom: 0.0,
}
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct GroupingConfig {
/// Styles for the grouping
pub style: Option<GroupingStyle>,
/// Alpha value for the color transparency [[0-255]] (default: 200)
pub transparency_alpha: Option<u8>,
/// Rounding values for the 4 corners. Can be a single or 4 values.
pub rounding: Option<RoundingConfig>,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub enum GroupingStyle {
#[serde(alias = "CtByte")]
Default,
/// A black shadow is added under the default group
#[serde(alias = "CtByteWithShadow")]
DefaultWithShadow,
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema)]
#[serde(untagged)]
pub enum RoundingConfig {
/// All 4 corners are the same
Same(f32),
/// All 4 corners are custom. Order: NW, NE, SW, SE
Individual([f32; 4]),
}
impl From<RoundingConfig> for Rounding {
fn from(value: RoundingConfig) -> Self {
match value {
RoundingConfig::Same(value) => Rounding::same(value),
RoundingConfig::Individual(values) => Self {
nw: values[0],
ne: values[1],
sw: values[2],
se: values[3],
},
}
}
}
pub trait Color32Ext {
fn try_apply_alpha(self, transparency_alpha: Option<u8>) -> Self;
}
impl Color32Ext for Color32 {
/// Tries to apply the alpha value to the Color32
fn try_apply_alpha(self, transparency_alpha: Option<u8>) -> Self {
if let Some(alpha) = transparency_alpha {
return Color32::from_rgba_unmultiplied(self.r(), self.g(), self.b(), alpha);
}
self
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -79,7 +79,7 @@ impl Storage {
}
impl BarWidget for Storage {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let font_id = ctx
.style()
@@ -107,27 +107,27 @@ impl BarWidget for Storage {
TextFormat::simple(font_id.clone(), ctx.style().visuals.text_color()),
);
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
if let Err(error) = Command::new("cmd.exe")
.args([
"/C",
"explorer.exe",
output.split(' ').collect::<Vec<&str>>()[0],
])
.spawn()
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
eprintln!("{}", error)
if let Err(error) = Command::new("cmd.exe")
.args([
"/C",
"explorer.exe",
output.split(' ').collect::<Vec<&str>>()[0],
])
.spawn()
{
eprintln!("{}", error)
}
}
}
ui.add_space(WIDGET_SPACING);
});
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::config::LabelPrefix;
use crate::render::RenderConfig;
use crate::widget::BarWidget;
use crate::WIDGET_SPACING;
use eframe::egui::text::LayoutJob;
use eframe::egui::Context;
use eframe::egui::FontId;
@@ -77,7 +77,7 @@ impl Time {
}
impl BarWidget for Time {
fn render(&mut self, ctx: &Context, ui: &mut Ui) {
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig) {
if self.enable {
let mut output = self.output();
if !output.is_empty() {
@@ -110,19 +110,19 @@ impl BarWidget for Time {
TextFormat::simple(font_id, ctx.style().visuals.text_color()),
);
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
self.format.toggle()
}
config.apply_on_widget(true, ui, |ui| {
if ui
.add(
Label::new(layout_job)
.selectable(false)
.sense(Sense::click()),
)
.clicked()
{
self.format.toggle()
}
});
}
ui.add_space(WIDGET_SPACING);
}
}
}

View File

@@ -12,6 +12,7 @@ use crate::memory::Memory;
use crate::memory::MemoryConfig;
use crate::network::Network;
use crate::network::NetworkConfig;
use crate::render::RenderConfig;
use crate::storage::Storage;
use crate::storage::StorageConfig;
use crate::time::Time;
@@ -23,7 +24,7 @@ use serde::Deserialize;
use serde::Serialize;
pub trait BarWidget {
fn render(&mut self, ctx: &Context, ui: &mut Ui);
fn render(&mut self, ctx: &Context, ui: &mut Ui, config: &mut RenderConfig);
}
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]

View File

@@ -39,6 +39,18 @@ impl From<Color32> for Colour {
}
}
impl From<Colour> for Color32 {
fn from(value: Colour) -> Self {
match value {
Colour::Rgb(rgb) => Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8),
Colour::Hex(hex) => {
let rgb = Rgb::from(hex);
Color32::from_rgb(rgb.r as u8, rgb.g as u8, rgb.b as u8)
}
}
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
pub struct Hex(HexColor);

View File

@@ -675,7 +675,12 @@ impl WindowsApi {
}
pub fn center_cursor_in_rect(rect: &Rect) -> Result<()> {
Self::set_cursor_pos(rect.left + (rect.right / 2), rect.top + (rect.bottom / 2))
Self::place_cursor_at_top_left_of_rect(rect)
// Self::set_cursor_pos(rect.left + (rect.right / 2), rect.top + (rect.bottom / 2))
}
pub fn place_cursor_at_top_left_of_rect(rect: &Rect) -> Result<()> {
Self::set_cursor_pos(rect.left + 15, rect.top + 15)
}
pub fn window_thread_process_id(hwnd: isize) -> (u32, u32) {

View File

@@ -246,6 +246,10 @@ impl Workspace {
if let Some(window) = to_focus {
if self.maximized_window().is_none() && self.floating_windows().is_empty() {
window.focus(mouse_follows_focus)?;
} else if let Some(maximized_window) = self.maximized_window() {
maximized_window.focus(mouse_follows_focus)?;
} else if let Some(floating_window) = self.floating_windows().first() {
floating_window.focus(mouse_follows_focus)?;
}
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "KomobarConfig",
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.30`",
"description": "The `komorebi.bar.json` configuration file reference for `v0.1.31`",
"type": "object",
"required": [
"left_widgets",
@@ -47,6 +47,203 @@
}
}
},
"grouping": {
"description": "Visual grouping for widgets",
"oneOf": [
{
"description": "No grouping is applied",
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"None"
]
}
}
},
{
"description": "Widgets are grouped as a whole",
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"Bar"
]
},
"rounding": {
"description": "Rounding values for the 4 corners. Can be a single or 4 values.",
"anyOf": [
{
"description": "All 4 corners are the same",
"type": "number",
"format": "float"
},
{
"description": "All 4 corners are custom. Order: NW, NE, SW, SE",
"type": "array",
"items": {
"type": "number",
"format": "float"
},
"maxItems": 4,
"minItems": 4
}
]
},
"style": {
"description": "Styles for the grouping",
"oneOf": [
{
"type": "string",
"enum": [
"Default"
]
},
{
"description": "A black shadow is added under the default group",
"type": "string",
"enum": [
"DefaultWithShadow"
]
}
]
},
"transparency_alpha": {
"description": "Alpha value for the color transparency [[0-255]] (default: 200)",
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
},
{
"description": "Widgets are grouped by alignment",
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"Alignment"
]
},
"rounding": {
"description": "Rounding values for the 4 corners. Can be a single or 4 values.",
"anyOf": [
{
"description": "All 4 corners are the same",
"type": "number",
"format": "float"
},
{
"description": "All 4 corners are custom. Order: NW, NE, SW, SE",
"type": "array",
"items": {
"type": "number",
"format": "float"
},
"maxItems": 4,
"minItems": 4
}
]
},
"style": {
"description": "Styles for the grouping",
"oneOf": [
{
"type": "string",
"enum": [
"Default"
]
},
{
"description": "A black shadow is added under the default group",
"type": "string",
"enum": [
"DefaultWithShadow"
]
}
]
},
"transparency_alpha": {
"description": "Alpha value for the color transparency [[0-255]] (default: 200)",
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
},
{
"description": "Widgets are grouped individually",
"type": "object",
"required": [
"kind"
],
"properties": {
"kind": {
"type": "string",
"enum": [
"Widget"
]
},
"rounding": {
"description": "Rounding values for the 4 corners. Can be a single or 4 values.",
"anyOf": [
{
"description": "All 4 corners are the same",
"type": "number",
"format": "float"
},
{
"description": "All 4 corners are custom. Order: NW, NE, SW, SE",
"type": "array",
"items": {
"type": "number",
"format": "float"
},
"maxItems": 4,
"minItems": 4
}
]
},
"style": {
"description": "Styles for the grouping",
"oneOf": [
{
"type": "string",
"enum": [
"Default"
]
},
{
"description": "A black shadow is added under the default group",
"type": "string",
"enum": [
"DefaultWithShadow"
]
}
]
},
"transparency_alpha": {
"description": "Alpha value for the color transparency [[0-255]] (default: 200)",
"type": "integer",
"format": "uint8",
"minimum": 0.0
}
}
}
]
},
"left_widgets": {
"description": "Left side widgets (ordered left-to-right)",
"type": "array",
@@ -1758,6 +1955,17 @@
}
}
]
},
"transparency_alpha": {
"description": "Alpha value for the color transparency [[0-255]] (default: 200)",
"type": "integer",
"format": "uint8",
"minimum": 0.0
},
"widget_spacing": {
"description": "Spacing between widgets (default: 10.0)",
"type": "number",
"format": "float"
}
}
}

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "StaticConfig",
"description": "The `komorebi.json` static configuration file reference for `v0.1.30`",
"description": "The `komorebi.json` static configuration file reference for `v0.1.31`",
"type": "object",
"properties": {
"animation": {