config: Split Layout from LayoutPart

This commit is contained in:
Ivan Molodetskikh
2025-09-20 12:57:41 +03:00
parent 2781d3a743
commit 1fa9dd32ed
21 changed files with 579 additions and 434 deletions
+136 -108
View File
@@ -5,7 +5,7 @@ use knuffel::errors::DecodeError;
use miette::{miette, IntoDiagnostic as _};
use smithay::backend::renderer::Color32F;
use crate::utils::MergeWith;
use crate::utils::{Flag, MergeWith};
use crate::FloatOrInt;
pub const DEFAULT_BACKGROUND_COLOR: Color = Color::from_array_unpremul([0.25, 0.25, 0.25, 1.]);
@@ -222,23 +222,15 @@ impl CornerRadius {
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FocusRing {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument), default = Self::default().width)]
pub width: FloatOrInt<0, 65535>,
#[knuffel(child, default = Self::default().active_color)]
pub width: f64,
pub active_color: Color,
#[knuffel(child, default = Self::default().inactive_color)]
pub inactive_color: Color,
#[knuffel(child, default = Self::default().urgent_color)]
pub urgent_color: Color,
#[knuffel(child)]
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
#[knuffel(child)]
pub urgent_gradient: Option<Gradient>,
}
@@ -246,7 +238,7 @@ impl Default for FocusRing {
fn default() -> Self {
Self {
off: false,
width: FloatOrInt(4.),
width: 4.,
active_color: Color::from_rgba8_unpremul(127, 200, 255, 255),
inactive_color: Color::from_rgba8_unpremul(80, 80, 80, 255),
urgent_color: Color::from_rgba8_unpremul(155, 0, 0, 255),
@@ -257,23 +249,15 @@ impl Default for FocusRing {
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Border {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, unwrap(argument), default = Self::default().width)]
pub width: FloatOrInt<0, 65535>,
#[knuffel(child, default = Self::default().active_color)]
pub width: f64,
pub active_color: Color,
#[knuffel(child, default = Self::default().inactive_color)]
pub inactive_color: Color,
#[knuffel(child, default = Self::default().urgent_color)]
pub urgent_color: Color,
#[knuffel(child)]
pub active_gradient: Option<Gradient>,
#[knuffel(child)]
pub inactive_gradient: Option<Gradient>,
#[knuffel(child)]
pub urgent_gradient: Option<Gradient>,
}
@@ -281,7 +265,7 @@ impl Default for Border {
fn default() -> Self {
Self {
off: true,
width: FloatOrInt(4.),
width: 4.,
active_color: Color::from_rgba8_unpremul(255, 200, 127, 255),
inactive_color: Color::from_rgba8_unpremul(80, 80, 80, 255),
urgent_color: Color::from_rgba8_unpremul(155, 0, 0, 255),
@@ -329,7 +313,7 @@ impl MergeWith<BorderRule> for Border {
self.off = false;
}
merge_clone!((self, part), width);
merge!((self, part), width);
merge_color_gradient!(
(self, part),
@@ -348,21 +332,14 @@ impl MergeWith<BorderRule> for FocusRing {
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Shadow {
#[knuffel(child)]
pub on: bool,
#[knuffel(child, default = Self::default().offset)]
pub offset: ShadowOffset,
#[knuffel(child, unwrap(argument), default = Self::default().softness)]
pub softness: FloatOrInt<0, 1024>,
#[knuffel(child, unwrap(argument), default = Self::default().spread)]
pub spread: FloatOrInt<-1024, 1024>,
#[knuffel(child, unwrap(argument), default = Self::default().draw_behind_window)]
pub softness: f64,
pub spread: f64,
pub draw_behind_window: bool,
#[knuffel(child, default = Self::default().color)]
pub color: Color,
#[knuffel(child)]
pub inactive_color: Option<Color>,
}
@@ -374,8 +351,8 @@ impl Default for Shadow {
x: FloatOrInt(0.),
y: FloatOrInt(5.),
},
softness: FloatOrInt(30.),
spread: FloatOrInt(5.),
softness: 30.,
spread: 5.,
draw_behind_window: false,
color: Color::from_rgba8_unpremul(0, 0, 0, 0x77),
inactive_color: None,
@@ -390,7 +367,7 @@ impl MergeWith<ShadowRule> for Shadow {
self.on = false;
}
merge_clone!((self, part), softness, spread);
merge!((self, part), softness, spread);
merge_clone!((self, part), offset, draw_behind_window, color);
@@ -440,8 +417,8 @@ impl From<WorkspaceShadow> for Shadow {
Self {
on: !value.off,
offset: value.offset,
softness: value.softness,
spread: value.spread,
softness: value.softness.0,
spread: value.spread.0,
draw_behind_window: false,
color: value.color,
inactive_color: None,
@@ -449,26 +426,99 @@ impl From<WorkspaceShadow> for Shadow {
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct TabIndicator {
pub off: bool,
pub hide_when_single_tab: bool,
pub place_within_column: bool,
pub gap: f64,
pub width: f64,
pub length: TabIndicatorLength,
pub position: TabIndicatorPosition,
pub gaps_between_tabs: f64,
pub corner_radius: f64,
pub active_color: Option<Color>,
pub inactive_color: Option<Color>,
pub urgent_color: Option<Color>,
pub active_gradient: Option<Gradient>,
pub inactive_gradient: Option<Gradient>,
pub urgent_gradient: Option<Gradient>,
}
impl Default for TabIndicator {
fn default() -> Self {
Self {
off: false,
hide_when_single_tab: false,
place_within_column: false,
gap: 5.,
width: 4.,
length: TabIndicatorLength {
total_proportion: Some(0.5),
},
position: TabIndicatorPosition::Left,
gaps_between_tabs: 0.,
corner_radius: 0.,
active_color: None,
inactive_color: None,
urgent_color: None,
active_gradient: None,
inactive_gradient: None,
urgent_gradient: None,
}
}
}
impl MergeWith<TabIndicatorPart> for TabIndicator {
fn merge_with(&mut self, part: &TabIndicatorPart) {
self.off |= part.off;
if part.on {
self.off = false;
}
merge!(
(self, part),
hide_when_single_tab,
place_within_column,
gap,
width,
gaps_between_tabs,
corner_radius,
);
merge_clone!((self, part), length, position);
merge_color_gradient_opt!(
(self, part),
(active_color, active_gradient),
(inactive_color, inactive_gradient),
(urgent_color, urgent_gradient),
);
}
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct TabIndicatorPart {
#[knuffel(child)]
pub off: bool,
#[knuffel(child)]
pub hide_when_single_tab: bool,
pub on: bool,
#[knuffel(child)]
pub place_within_column: bool,
#[knuffel(child, unwrap(argument), default = Self::default().gap)]
pub gap: FloatOrInt<-65535, 65535>,
#[knuffel(child, unwrap(argument), default = Self::default().width)]
pub width: FloatOrInt<0, 65535>,
#[knuffel(child, default = Self::default().length)]
pub length: TabIndicatorLength,
#[knuffel(child, unwrap(argument), default = Self::default().position)]
pub position: TabIndicatorPosition,
#[knuffel(child, unwrap(argument), default = Self::default().gaps_between_tabs)]
pub gaps_between_tabs: FloatOrInt<0, 65535>,
#[knuffel(child, unwrap(argument), default = Self::default().corner_radius)]
pub corner_radius: FloatOrInt<0, 65535>,
pub hide_when_single_tab: Option<Flag>,
#[knuffel(child)]
pub place_within_column: Option<Flag>,
#[knuffel(child, unwrap(argument))]
pub gap: Option<FloatOrInt<-65535, 65535>>,
#[knuffel(child, unwrap(argument))]
pub width: Option<FloatOrInt<0, 65535>>,
#[knuffel(child)]
pub length: Option<TabIndicatorLength>,
#[knuffel(child, unwrap(argument))]
pub position: Option<TabIndicatorPosition>,
#[knuffel(child, unwrap(argument))]
pub gaps_between_tabs: Option<FloatOrInt<0, 65535>>,
#[knuffel(child, unwrap(argument))]
pub corner_radius: Option<FloatOrInt<0, 65535>>,
#[knuffel(child)]
pub active_color: Option<Color>,
#[knuffel(child)]
@@ -483,30 +533,6 @@ pub struct TabIndicator {
pub urgent_gradient: Option<Gradient>,
}
impl Default for TabIndicator {
fn default() -> Self {
Self {
off: false,
hide_when_single_tab: false,
place_within_column: false,
gap: FloatOrInt(5.),
width: FloatOrInt(4.),
length: TabIndicatorLength {
total_proportion: Some(0.5),
},
position: TabIndicatorPosition::Left,
gaps_between_tabs: FloatOrInt(0.),
corner_radius: FloatOrInt(0.),
active_color: None,
inactive_color: None,
urgent_color: None,
active_gradient: None,
inactive_gradient: None,
urgent_gradient: None,
}
}
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub struct TabIndicatorLength {
#[knuffel(property)]
@@ -521,13 +547,10 @@ pub enum TabIndicatorPosition {
Bottom,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct InsertHint {
#[knuffel(child)]
pub off: bool,
#[knuffel(child, default = Self::default().color)]
pub color: Color,
#[knuffel(child)]
pub gradient: Option<Gradient>,
}
@@ -541,6 +564,29 @@ impl Default for InsertHint {
}
}
impl MergeWith<InsertHintPart> for InsertHint {
fn merge_with(&mut self, part: &InsertHintPart) {
self.off |= part.off;
if part.on {
self.off = false;
}
merge_color_gradient!((self, part), (color, gradient));
}
}
#[derive(knuffel::Decode, Debug, Default, Clone, Copy, PartialEq)]
pub struct InsertHintPart {
#[knuffel(child)]
pub off: bool,
#[knuffel(child)]
pub on: bool,
#[knuffel(child)]
pub color: Option<Color>,
#[knuffel(child)]
pub gradient: Option<Gradient>,
}
#[derive(knuffel::DecodeScalar, Debug, Clone, Copy, PartialEq, Eq)]
pub enum BlockOutFrom {
Screencast,
@@ -1016,16 +1062,9 @@ mod tests {
#[test]
fn test_border_rule_on_off_merging() {
fn is_on(config: &str, rules: &[&str]) -> String {
let mut resolved = BorderRule {
off: false,
on: false,
width: None,
active_color: None,
inactive_color: None,
urgent_color: None,
active_gradient: None,
inactive_gradient: None,
urgent_gradient: None,
let mut resolved = Border {
off: config == "off",
..Default::default()
};
for rule in rules.iter().copied() {
@@ -1038,14 +1077,7 @@ mod tests {
resolved.merge_with(&rule);
}
let mut border = Border {
off: config == "off",
..Default::default()
};
border.merge_with(&resolved);
if border.off { "off" } else { "on" }.to_owned()
if resolved.off { "off" } else { "on" }.to_owned()
}
assert_snapshot!(is_on("off", &[]), @"off");
@@ -1095,13 +1127,11 @@ mod tests {
)
.unwrap();
let mut border_rule = BorderRule::default();
let mut border = config.resolve_layout().border;
for rule in &config.window_rules {
border_rule.merge_with(&rule.border);
border.merge_with(&rule.border);
}
let border = config.layout.border.merged_with(&border_rule);
// Gradient should be None because it's overwritten.
assert_debug_snapshot!(
(
@@ -1166,15 +1196,13 @@ mod tests {
)
.unwrap();
let mut border_rule = BorderRule::default();
let mut border = config.resolve_layout().border;
let mut tab_indicator_rule = TabIndicatorRule::default();
for rule in &config.window_rules {
border_rule.merge_with(&rule.border);
border.merge_with(&rule.border);
tab_indicator_rule.merge_with(&rule.tab_indicator);
}
let border = config.layout.border.merged_with(&border_rule);
// Gradient should be None because it's overwritten.
assert_debug_snapshot!(
(
+96 -31
View File
@@ -4,65 +4,130 @@ use niri_ipc::{ColumnDisplay, SizeChange};
use crate::appearance::{
Border, FocusRing, InsertHint, Shadow, TabIndicator, DEFAULT_BACKGROUND_COLOR,
};
use crate::utils::expect_only_children;
use crate::{Color, FloatOrInt};
use crate::utils::{expect_only_children, Flag, MergeWith};
use crate::{BorderRule, Color, FloatOrInt, InsertHintPart, ShadowRule, TabIndicatorPart};
#[derive(knuffel::Decode, Debug, Clone, PartialEq)]
#[derive(Debug, Clone, PartialEq)]
pub struct Layout {
#[knuffel(child, default)]
pub focus_ring: FocusRing,
#[knuffel(child, default)]
pub border: Border,
#[knuffel(child, default)]
pub shadow: Shadow,
#[knuffel(child, default)]
pub tab_indicator: TabIndicator,
#[knuffel(child, default)]
pub insert_hint: InsertHint,
#[knuffel(child, unwrap(children), default)]
pub preset_column_widths: Vec<PresetSize>,
#[knuffel(child)]
pub default_column_width: Option<DefaultPresetSize>,
#[knuffel(child, unwrap(children), default)]
pub default_column_width: Option<PresetSize>,
pub preset_window_heights: Vec<PresetSize>,
#[knuffel(child, unwrap(argument), default)]
pub center_focused_column: CenterFocusedColumn,
#[knuffel(child)]
pub always_center_single_column: bool,
#[knuffel(child)]
pub empty_workspace_above_first: bool,
#[knuffel(child, unwrap(argument, str), default = Self::default().default_column_display)]
pub default_column_display: ColumnDisplay,
#[knuffel(child, unwrap(argument), default = Self::default().gaps)]
pub gaps: FloatOrInt<0, 65535>,
#[knuffel(child, default)]
pub gaps: f64,
pub struts: Struts,
#[knuffel(child, default = DEFAULT_BACKGROUND_COLOR)]
pub background_color: Color,
}
impl Default for Layout {
fn default() -> Self {
Self {
focus_ring: Default::default(),
border: Default::default(),
shadow: Default::default(),
tab_indicator: Default::default(),
insert_hint: Default::default(),
preset_column_widths: Default::default(),
default_column_width: Default::default(),
center_focused_column: Default::default(),
focus_ring: FocusRing::default(),
border: Border::default(),
shadow: Shadow::default(),
tab_indicator: TabIndicator::default(),
insert_hint: InsertHint::default(),
preset_column_widths: vec![
PresetSize::Proportion(1. / 3.),
PresetSize::Proportion(0.5),
PresetSize::Proportion(2. / 3.),
],
default_column_width: Some(PresetSize::Proportion(0.5)),
center_focused_column: CenterFocusedColumn::Never,
always_center_single_column: false,
empty_workspace_above_first: false,
default_column_display: ColumnDisplay::Normal,
gaps: FloatOrInt(16.),
struts: Default::default(),
preset_window_heights: Default::default(),
gaps: 16.,
struts: Struts::default(),
preset_window_heights: vec![
PresetSize::Proportion(1. / 3.),
PresetSize::Proportion(0.5),
PresetSize::Proportion(2. / 3.),
],
background_color: DEFAULT_BACKGROUND_COLOR,
}
}
}
impl MergeWith<LayoutPart> for Layout {
fn merge_with(&mut self, part: &LayoutPart) {
merge!(
(self, part),
focus_ring,
border,
shadow,
tab_indicator,
insert_hint,
always_center_single_column,
empty_workspace_above_first,
gaps,
);
merge_clone!(
(self, part),
preset_column_widths,
preset_window_heights,
center_focused_column,
default_column_display,
struts,
background_color,
);
if let Some(x) = part.default_column_width {
self.default_column_width = x.0;
}
if self.preset_column_widths.is_empty() {
self.preset_column_widths = Layout::default().preset_column_widths;
}
if self.preset_window_heights.is_empty() {
self.preset_window_heights = Layout::default().preset_window_heights;
}
}
}
#[derive(knuffel::Decode, Debug, Default, Clone, PartialEq)]
pub struct LayoutPart {
#[knuffel(child)]
pub focus_ring: Option<BorderRule>,
#[knuffel(child)]
pub border: Option<BorderRule>,
#[knuffel(child)]
pub shadow: Option<ShadowRule>,
#[knuffel(child)]
pub tab_indicator: Option<TabIndicatorPart>,
#[knuffel(child)]
pub insert_hint: Option<InsertHintPart>,
#[knuffel(child, unwrap(children))]
pub preset_column_widths: Option<Vec<PresetSize>>,
#[knuffel(child)]
pub default_column_width: Option<DefaultPresetSize>,
#[knuffel(child, unwrap(children))]
pub preset_window_heights: Option<Vec<PresetSize>>,
#[knuffel(child, unwrap(argument))]
pub center_focused_column: Option<CenterFocusedColumn>,
#[knuffel(child)]
pub always_center_single_column: Option<Flag>,
#[knuffel(child)]
pub empty_workspace_above_first: Option<Flag>,
#[knuffel(child, unwrap(argument, str))]
pub default_column_display: Option<ColumnDisplay>,
#[knuffel(child, unwrap(argument))]
pub gaps: Option<FloatOrInt<0, 65535>>,
#[knuffel(child)]
pub struts: Option<Struts>,
#[knuffel(child)]
pub background_color: Option<Color>,
}
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq)]
pub enum PresetSize {
Proportion(#[knuffel(argument)] f64),
+260 -230
View File
@@ -36,6 +36,7 @@ pub use crate::layout::*;
pub use crate::misc::*;
pub use crate::output::{Output, OutputName, Outputs, Position, Vrr};
pub use crate::utils::FloatOrInt;
use crate::utils::MergeWith as _;
pub use crate::window_rule::{FloatingPosition, RelativeTo, WindowRule};
pub use crate::workspace::Workspace;
@@ -50,7 +51,7 @@ pub struct Config {
#[knuffel(children(name = "spawn-sh-at-startup"))]
pub spawn_sh_at_startup: Vec<SpawnShAtStartup>,
#[knuffel(child, default)]
pub layout: Layout,
pub layout: LayoutPart,
#[knuffel(child, default)]
pub prefer_no_csd: bool,
#[knuffel(child, default)]
@@ -133,6 +134,35 @@ impl Config {
let _span = tracy_client::span!("Config::parse");
knuffel::parse(filename, text)
}
pub fn resolve_layout(&self) -> Layout {
let mut rv = Layout::from_part(&self.layout);
// Preserve the behavior we'd always had for the border section:
// - `layout {}` gives border = off
// - `layout { border {} }` gives border = on
// - `layout { border { off } }` gives border = off
//
// This behavior is inconsistent with the rest of the config where adding an empty section
// generally doesn't change the outcome. Particularly, shadows are also disabled by default
// (like borders), and they always had an `on` instead of an `off` for this reason, so that
// writing `layout { shadow {} }` still results in shadow = off, as it should.
//
// Unfortunately, the default config has always had wording that heavily implies that
// `layout { border {} }` enables the borders. This wording is sure to be present in a lot
// of users' configs by now, which we can't change.
//
// Another way to make things consistent would be to default borders to on. However, that
// is annoying because it would mean changing many tests that rely on borders being off by
// default. This would also contradict the intended default borders value (off).
//
// So, let's just work around the problem here, preserving the original behavior.
if self.layout.border.is_some_and(|x| !x.on && !x.off) {
rv.border.off = false;
}
rv
}
}
impl Default for Config {
@@ -778,181 +808,182 @@ mod tests {
command: "qs -c ~/source/qs/MyAwesomeShell",
},
],
layout: Layout {
focus_ring: FocusRing {
off: false,
width: FloatOrInt(
5.0,
),
active_color: Color {
r: 0.0,
g: 0.39215687,
b: 0.78431374,
a: 1.0,
},
inactive_color: Color {
r: 1.0,
g: 0.78431374,
b: 0.39215687,
a: 0.0,
},
urgent_color: Color {
r: 0.60784316,
g: 0.0,
b: 0.0,
a: 1.0,
},
active_gradient: Some(
Gradient {
from: Color {
r: 0.039215688,
g: 0.078431375,
b: 0.11764706,
a: 1.0,
},
to: Color {
layout: LayoutPart {
focus_ring: Some(
BorderRule {
off: false,
on: false,
width: Some(
FloatOrInt(
5.0,
),
),
active_color: Some(
Color {
r: 0.0,
g: 0.5019608,
b: 1.0,
g: 0.39215687,
b: 0.78431374,
a: 1.0,
},
angle: 180,
relative_to: WorkspaceView,
in_: GradientInterpolation {
color_space: Srgb,
hue_interpolation: Shorter,
),
inactive_color: Some(
Color {
r: 1.0,
g: 0.78431374,
b: 0.39215687,
a: 0.0,
},
},
),
inactive_gradient: None,
urgent_gradient: None,
},
border: Border {
off: false,
width: FloatOrInt(
3.0,
),
active_color: Color {
r: 1.0,
g: 0.78431374,
b: 0.49803922,
a: 1.0,
},
inactive_color: Color {
r: 1.0,
g: 0.78431374,
b: 0.39215687,
a: 0.0,
},
urgent_color: Color {
r: 0.60784316,
g: 0.0,
b: 0.0,
a: 1.0,
},
active_gradient: None,
inactive_gradient: None,
urgent_gradient: None,
},
shadow: Shadow {
on: false,
offset: ShadowOffset {
x: FloatOrInt(
10.0,
),
y: FloatOrInt(
-20.0,
urgent_color: None,
active_gradient: Some(
Gradient {
from: Color {
r: 0.039215688,
g: 0.078431375,
b: 0.11764706,
a: 1.0,
},
to: Color {
r: 0.0,
g: 0.5019608,
b: 1.0,
a: 1.0,
},
angle: 180,
relative_to: WorkspaceView,
in_: GradientInterpolation {
color_space: Srgb,
hue_interpolation: Shorter,
},
},
),
inactive_gradient: None,
urgent_gradient: None,
},
),
border: Some(
BorderRule {
off: false,
on: false,
width: Some(
FloatOrInt(
3.0,
),
),
active_color: None,
inactive_color: Some(
Color {
r: 1.0,
g: 0.78431374,
b: 0.39215687,
a: 0.0,
},
),
urgent_color: None,
active_gradient: None,
inactive_gradient: None,
urgent_gradient: None,
},
),
shadow: Some(
ShadowRule {
off: false,
on: false,
offset: Some(
ShadowOffset {
x: FloatOrInt(
10.0,
),
y: FloatOrInt(
-20.0,
),
},
),
softness: None,
spread: None,
draw_behind_window: None,
color: None,
inactive_color: None,
},
),
tab_indicator: Some(
TabIndicatorPart {
off: false,
on: false,
hide_when_single_tab: None,
place_within_column: None,
gap: None,
width: Some(
FloatOrInt(
10.0,
),
),
length: None,
position: Some(
Top,
),
gaps_between_tabs: None,
corner_radius: None,
active_color: None,
inactive_color: None,
urgent_color: None,
active_gradient: None,
inactive_gradient: None,
urgent_gradient: None,
},
),
insert_hint: Some(
InsertHintPart {
off: false,
on: false,
color: Some(
Color {
r: 1.0,
g: 0.78431374,
b: 0.49803922,
a: 1.0,
},
),
gradient: Some(
Gradient {
from: Color {
r: 0.039215688,
g: 0.078431375,
b: 0.11764706,
a: 1.0,
},
to: Color {
r: 0.0,
g: 0.5019608,
b: 1.0,
a: 1.0,
},
angle: 180,
relative_to: WorkspaceView,
in_: GradientInterpolation {
color_space: Srgb,
hue_interpolation: Shorter,
},
},
),
},
softness: FloatOrInt(
30.0,
),
spread: FloatOrInt(
5.0,
),
draw_behind_window: false,
color: Color {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.46666667,
},
inactive_color: None,
},
tab_indicator: TabIndicator {
off: false,
hide_when_single_tab: false,
place_within_column: false,
gap: FloatOrInt(
5.0,
),
width: FloatOrInt(
10.0,
),
length: TabIndicatorLength {
total_proportion: Some(
),
preset_column_widths: Some(
[
Proportion(
0.25,
),
Proportion(
0.5,
),
},
position: Top,
gaps_between_tabs: FloatOrInt(
0.0,
),
corner_radius: FloatOrInt(
0.0,
),
active_color: None,
inactive_color: None,
urgent_color: None,
active_gradient: None,
inactive_gradient: None,
urgent_gradient: None,
},
insert_hint: InsertHint {
off: false,
color: Color {
r: 1.0,
g: 0.78431374,
b: 0.49803922,
a: 1.0,
},
gradient: Some(
Gradient {
from: Color {
r: 0.039215688,
g: 0.078431375,
b: 0.11764706,
a: 1.0,
},
to: Color {
r: 0.0,
g: 0.5019608,
b: 1.0,
a: 1.0,
},
angle: 180,
relative_to: WorkspaceView,
in_: GradientInterpolation {
color_space: Srgb,
hue_interpolation: Shorter,
},
},
),
},
preset_column_widths: [
Proportion(
0.25,
),
Proportion(
0.5,
),
Fixed(
960,
),
Fixed(
1280,
),
],
Fixed(
960,
),
Fixed(
1280,
),
],
),
default_column_width: Some(
DefaultPresetSize(
Some(
@@ -962,47 +993,52 @@ mod tests {
),
),
),
preset_window_heights: [
Proportion(
0.25,
),
Proportion(
0.5,
),
Fixed(
960,
),
Fixed(
1280,
),
],
center_focused_column: OnOverflow,
always_center_single_column: false,
empty_workspace_above_first: false,
default_column_display: Tabbed,
gaps: FloatOrInt(
8.0,
preset_window_heights: Some(
[
Proportion(
0.25,
),
Proportion(
0.5,
),
Fixed(
960,
),
Fixed(
1280,
),
],
),
struts: Struts {
left: FloatOrInt(
1.0,
center_focused_column: Some(
OnOverflow,
),
always_center_single_column: None,
empty_workspace_above_first: None,
default_column_display: Some(
Tabbed,
),
gaps: Some(
FloatOrInt(
8.0,
),
right: FloatOrInt(
2.0,
),
top: FloatOrInt(
3.0,
),
bottom: FloatOrInt(
0.0,
),
},
background_color: Color {
r: 0.25,
g: 0.25,
b: 0.25,
a: 1.0,
},
),
struts: Some(
Struts {
left: FloatOrInt(
1.0,
),
right: FloatOrInt(
2.0,
),
top: FloatOrInt(
3.0,
),
bottom: FloatOrInt(
0.0,
),
},
),
background_color: None,
},
prefer_no_csd: true,
cursor: Cursor {
@@ -1823,6 +1859,23 @@ mod tests {
default_config.window_rules.clear();
default_config.binds.0.clear();
let default_layout = default_config.resolve_layout();
let empty_layout = empty_config.resolve_layout();
default_config.layout = Default::default();
assert_snapshot!(
diff_lines(
&format!("{empty_layout:#?}"),
&format!("{default_layout:#?}")
),
@r"
- 0.3333333333333333,
+ 0.33333,
- 0.6666666666666666,
+ 0.66667,
",
);
assert_snapshot!(
diff_lines(
&format!("{empty_config:#?}"),
@@ -1846,30 +1899,7 @@ mod tests {
+ ],
+ },
+ ],
- preset_column_widths: [],
- default_column_width: None,
+ preset_column_widths: [
+ Proportion(
+ 0.33333,
+ ),
+ Proportion(
+ 0.5,
+ ),
+ Proportion(
+ 0.66667,
+ ),
+ ],
+ default_column_width: Some(
+ DefaultPresetSize(
+ Some(
+ Proportion(
+ 0.5,
+ ),
+ ),
+ ),
+ ),
"#
"#,
);
}
}
+10
View File
@@ -1,3 +1,13 @@
macro_rules! merge {
(($self:expr, $part:expr), $($field:ident),+ $(,)*) => {
$(
if let Some(x) = &$part.$field {
$self.$field.merge_with(x);
}
)+
};
}
macro_rules! merge_clone {
(($self:expr, $part:expr), $($field:ident),+ $(,)*) => {
$(
+15
View File
@@ -14,6 +14,15 @@ pub struct Percent(pub f64);
#[derive(Debug, Default, Clone, Copy, PartialEq)]
pub struct FloatOrInt<const MIN: i32, const MAX: i32>(pub f64);
/// Flag, with an optional explicit value.
///
/// Intended to be used as an `Option<MaybeBool>` field, as a tri-state:
/// - (missing): unset, `None`
/// - just `field`: set, `Some(true)`
/// - explicitly `field true` or `field false`: set, `Some(true)` or `Some(false)`
#[derive(knuffel::Decode, Debug, Clone, Copy, PartialEq, Eq)]
pub struct Flag(#[knuffel(argument, default = true)] pub bool);
/// `Regex` that implements `PartialEq` by its string form.
#[derive(Debug, Clone)]
pub struct RegexEq(pub Regex);
@@ -57,6 +66,12 @@ impl<const MIN: i32, const MAX: i32> MergeWith<FloatOrInt<MIN, MAX>> for f64 {
}
}
impl MergeWith<Flag> for bool {
fn merge_with(&mut self, part: &Flag) {
*self = part.0;
}
}
impl<S: knuffel::traits::ErrorSpan, const MIN: i32, const MAX: i32> knuffel::DecodeScalar<S>
for FloatOrInt<MIN, MAX>
{