aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoruser <user@clank>2026-08-08 21:06:40 +0200
committeruser <user@clank>2026-08-12 19:34:19 +0200
commit0ba57cf6e9bfec08c198fc8eb5fa219b0c8c96ba (patch)
tree28bcfea151d04f0fa7a8294f802df686c6f845e7
parentsl0p: uniform pane gap + aspect-corrected horizontal gap (diff)
downloadsl0ppi-0ba57cf6e9bfec08c198fc8eb5fa219b0c8c96ba.tar.gz
sl0ppi-0ba57cf6e9bfec08c198fc8eb5fa219b0c8c96ba.tar.xz
sl0ppi-0ba57cf6e9bfec08c198fc8eb5fa219b0c8c96ba.zip
sl0p: CSS-style 1/2/4-value shorthand for inner padding
`padding` now accepts: padding 1 -> all four sides padding 1 2 -> up/down = 1, left/right = 2 padding 1 2 3 4 -> up=1 down=2 left=3 right=4 NOTE the 4-value order is UP DOWN LEFT RIGHT as requested, deliberately NOT CSS's clockwise top-right-bottom-left. Documented on PanePadding and at the parser, since the similarity to CSS invites the wrong assumption. 3 or 5 values are rejected with a message naming the accepted forms, rather than being silently reinterpreted. Negative values clamp to 0. ASPECT: only the 1-value form is aspect-corrected (left/right multiplied by gap_aspect) so `padding 1` looks square on a non-square cell grid. The 2- and 4-value forms are literal: specifying axes separately means the caller is already controlling horizontal size, and silently doubling it would surprise. Style now carries a PanePadding {top,bottom,left,right} instead of an x/y pair, which also collapses the reconfigure plumbing back to a single field rather than growing another parameter per side. to_kdl always emits the explicit 4-value form: emitting the 1-value form would re-apply gap_aspect on the next read and silently double the padding on every config round-trip. Baselines held: zellij-utils 435/5, zellij-server 1421/30.
-rw-r--r--sl0ppij/zellij-client/src/web_client/server_listener.rs3
-rw-r--r--sl0ppij/zellij-server/src/lib.rs9
-rw-r--r--sl0ppij/zellij-server/src/panes/floating_panes/mod.rs8
-rw-r--r--sl0ppij/zellij-server/src/panes/plugin_pane.rs14
-rw-r--r--sl0ppij/zellij-server/src/panes/terminal_pane.rs14
-rw-r--r--sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs8
-rw-r--r--sl0ppij/zellij-server/src/screen.rs16
-rw-r--r--sl0ppij/zellij-server/src/tab/mod.rs15
-rw-r--r--sl0ppij/zellij-utils/src/data.rs34
-rw-r--r--sl0ppij/zellij-utils/src/input/config.rs45
-rw-r--r--sl0ppij/zellij-utils/src/input/theme.rs12
-rw-r--r--sl0ppij/zellij-utils/src/kdl/mod.rs64
-rw-r--r--sl0ppij/zellij-utils/src/plugin_api/event.rs3
-rw-r--r--sl0ppij/zellij-utils/src/plugin_api/style.rs3
14 files changed, 171 insertions, 77 deletions
diff --git a/sl0ppij/zellij-client/src/web_client/server_listener.rs b/sl0ppij/zellij-client/src/web_client/server_listener.rs
index 8f80f4a..398c973 100644
--- a/sl0ppij/zellij-client/src/web_client/server_listener.rs
+++ b/sl0ppij/zellij-client/src/web_client/server_listener.rs
@@ -86,8 +86,7 @@ pub fn zellij_server_listener(
pane_title_alignment: config.ui.pane_frames.title_alignment,
pane_gap_x: config.ui.pane_frames.resolved_gap().0,
pane_gap_y: config.ui.pane_frames.resolved_gap().1,
- pane_padding_x: config.ui.pane_frames.resolved_padding().0,
- pane_padding_y: config.ui.pane_frames.resolved_padding().1,
+ pane_padding: config.ui.pane_frames.resolved_padding(),
},
};
diff --git a/sl0ppij/zellij-server/src/lib.rs b/sl0ppij/zellij-server/src/lib.rs
index a80bda2..fe5b3b1 100644
--- a/sl0ppij/zellij-server/src/lib.rs
+++ b/sl0ppij/zellij-server/src/lib.rs
@@ -462,8 +462,7 @@ impl SessionMetaData {
pane_title_alignment: new_config.ui.pane_frames.title_alignment,
pane_gap_x: new_config.ui.pane_frames.resolved_gap().0,
pane_gap_y: new_config.ui.pane_frames.resolved_gap().1,
- pane_padding_x: new_config.ui.pane_frames.resolved_padding().0,
- pane_padding_y: new_config.ui.pane_frames.resolved_padding().1,
+ pane_padding: new_config.ui.pane_frames.resolved_padding(),
stacked_resize: new_config.options.stacked_resize.unwrap_or(true),
stacked_pane_list: new_config.options.stacked_pane_list.unwrap_or(true),
default_editor: new_config.options.scrollback_editor.clone(),
@@ -1008,8 +1007,7 @@ pub fn start_server_impl(
pane_title_alignment: config.ui.pane_frames.title_alignment,
pane_gap_x: config.ui.pane_frames.resolved_gap().0,
pane_gap_y: config.ui.pane_frames.resolved_gap().1,
- pane_padding_x: config.ui.pane_frames.resolved_padding().0,
- pane_padding_y: config.ui.pane_frames.resolved_padding().1,
+ pane_padding: config.ui.pane_frames.resolved_padding(),
},
};
@@ -1196,8 +1194,7 @@ pub fn start_server_impl(
pane_title_alignment: config.ui.pane_frames.title_alignment,
pane_gap_x: config.ui.pane_frames.resolved_gap().0,
pane_gap_y: config.ui.pane_frames.resolved_gap().1,
- pane_padding_x: config.ui.pane_frames.resolved_padding().0,
- pane_padding_y: config.ui.pane_frames.resolved_padding().1,
+ pane_padding: config.ui.pane_frames.resolved_padding(),
},
};
diff --git a/sl0ppij/zellij-server/src/panes/floating_panes/mod.rs b/sl0ppij/zellij-server/src/panes/floating_panes/mod.rs
index 8252612..3e5e80f 100644
--- a/sl0ppij/zellij-server/src/panes/floating_panes/mod.rs
+++ b/sl0ppij/zellij-server/src/panes/floating_panes/mod.rs
@@ -1725,16 +1725,14 @@ impl FloatingPanes {
title_alignment: zellij_utils::data::PaneTitleAlignment,
gap_x: usize,
gap_y: usize,
- padding_x: usize,
- padding_y: usize,
+ padding: zellij_utils::data::PanePadding,
) {
self.style.pane_title_alignment = title_alignment;
self.style.pane_gap_x = gap_x;
self.style.pane_gap_y = gap_y;
- self.style.pane_padding_x = padding_x;
- self.style.pane_padding_y = padding_y;
+ self.style.pane_padding = padding;
for pane in self.panes.values_mut() {
- pane.update_frame_layout(title_alignment, gap_x, gap_y, padding_x, padding_y);
+ pane.update_frame_layout(title_alignment, gap_x, gap_y, padding);
}
// Unlike the tiled panes, `set_pane_frame_style` only re-runs the
// content-offset sweep for a fullscreen floating pane, so gap/padding
diff --git a/sl0ppij/zellij-server/src/panes/plugin_pane.rs b/sl0ppij/zellij-server/src/panes/plugin_pane.rs
index 4f4e138..6c1e870 100644
--- a/sl0ppij/zellij-server/src/panes/plugin_pane.rs
+++ b/sl0ppij/zellij-server/src/panes/plugin_pane.rs
@@ -773,10 +773,10 @@ impl Pane for PluginPane {
Offset::default()
} else {
Offset {
- top: self.gap_offset.top.saturating_add(self.style.pane_padding_y),
- bottom: self.gap_offset.bottom.saturating_add(self.style.pane_padding_y),
- left: self.gap_offset.left.saturating_add(self.style.pane_padding_x),
- right: self.gap_offset.right.saturating_add(self.style.pane_padding_x),
+ top: self.gap_offset.top.saturating_add(self.style.pane_padding.top),
+ bottom: self.gap_offset.bottom.saturating_add(self.style.pane_padding.bottom),
+ left: self.gap_offset.left.saturating_add(self.style.pane_padding.left),
+ right: self.gap_offset.right.saturating_add(self.style.pane_padding.right),
}
};
self.content_offset =
@@ -915,14 +915,12 @@ impl Pane for PluginPane {
title_alignment: zellij_utils::data::PaneTitleAlignment,
gap_x: usize,
gap_y: usize,
- padding_x: usize,
- padding_y: usize,
+ padding: zellij_utils::data::PanePadding,
) {
self.style.pane_title_alignment = title_alignment;
self.style.pane_gap_x = gap_x;
self.style.pane_gap_y = gap_y;
- self.style.pane_padding_x = padding_x;
- self.style.pane_padding_y = padding_y;
+ self.style.pane_padding = padding;
self.frame.clear();
}
fn set_should_be_suppressed(&mut self, should_be_suppressed: bool) {
diff --git a/sl0ppij/zellij-server/src/panes/terminal_pane.rs b/sl0ppij/zellij-server/src/panes/terminal_pane.rs
index 8c44d7b..7fb7817 100644
--- a/sl0ppij/zellij-server/src/panes/terminal_pane.rs
+++ b/sl0ppij/zellij-server/src/panes/terminal_pane.rs
@@ -991,10 +991,10 @@ impl Pane for TerminalPane {
Offset::default()
} else {
Offset {
- top: self.gap_offset.top.saturating_add(self.style.pane_padding_y),
- bottom: self.gap_offset.bottom.saturating_add(self.style.pane_padding_y),
- left: self.gap_offset.left.saturating_add(self.style.pane_padding_x),
- right: self.gap_offset.right.saturating_add(self.style.pane_padding_x),
+ top: self.gap_offset.top.saturating_add(self.style.pane_padding.top),
+ bottom: self.gap_offset.bottom.saturating_add(self.style.pane_padding.bottom),
+ left: self.gap_offset.left.saturating_add(self.style.pane_padding.left),
+ right: self.gap_offset.right.saturating_add(self.style.pane_padding.right),
}
};
self.content_offset =
@@ -1269,14 +1269,12 @@ impl Pane for TerminalPane {
title_alignment: zellij_utils::data::PaneTitleAlignment,
gap_x: usize,
gap_y: usize,
- padding_x: usize,
- padding_y: usize,
+ padding: zellij_utils::data::PanePadding,
) {
self.style.pane_title_alignment = title_alignment;
self.style.pane_gap_x = gap_x;
self.style.pane_gap_y = gap_y;
- self.style.pane_padding_x = padding_x;
- self.style.pane_padding_y = padding_y;
+ self.style.pane_padding = padding;
self.frame.clear();
}
fn drain_fake_cursors(&mut self) -> Option<HashSet<(usize, usize)>> {
diff --git a/sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs b/sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs
index 835c8e7..4d8f0a4 100644
--- a/sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs
+++ b/sl0ppij/zellij-server/src/panes/tiled_panes/mod.rs
@@ -3156,16 +3156,14 @@ impl TiledPanes {
title_alignment: zellij_utils::data::PaneTitleAlignment,
gap_x: usize,
gap_y: usize,
- padding_x: usize,
- padding_y: usize,
+ padding: zellij_utils::data::PanePadding,
) {
self.style.pane_title_alignment = title_alignment;
self.style.pane_gap_x = gap_x;
self.style.pane_gap_y = gap_y;
- self.style.pane_padding_x = padding_x;
- self.style.pane_padding_y = padding_y;
+ self.style.pane_padding = padding;
for pane in self.panes.values_mut() {
- pane.update_frame_layout(title_alignment, gap_x, gap_y, padding_x, padding_y);
+ pane.update_frame_layout(title_alignment, gap_x, gap_y, padding);
}
}
pub fn stack_panes(
diff --git a/sl0ppij/zellij-server/src/screen.rs b/sl0ppij/zellij-server/src/screen.rs
index c3c06ca..438fee2 100644
--- a/sl0ppij/zellij-server/src/screen.rs
+++ b/sl0ppij/zellij-server/src/screen.rs
@@ -43,7 +43,7 @@ use zellij_utils::data::{
CommandOrPlugin, Direction, EventType, FloatingPaneCoordinates, GetFocusedPaneInfoResponse,
HostTerminalThemeMode, KeyWithModifier, LayoutInfo, LayoutWithError, ListPanesResponse,
ListTabsResponse, NewPanePlacement, PaneContents, PaneInfo, PaneListEntry, PaneManifest,
- PaneRenderReport, PaneScrollbackResponse, PaneTitleAlignment, PluginPermission,
+ PaneRenderReport, PanePadding, PaneScrollbackResponse, PaneTitleAlignment, PluginPermission,
RegexHighlight, Resize,
ResizeStrategy, SessionInfo, Styling, TabInfo, ThemeHue, WebSharing,
};
@@ -779,8 +779,7 @@ pub enum ScreenInstruction {
pane_title_alignment: PaneTitleAlignment,
pane_gap_x: usize,
pane_gap_y: usize,
- pane_padding_x: usize,
- pane_padding_y: usize,
+ pane_padding: PanePadding,
stacked_resize: bool,
stacked_pane_list: bool,
default_editor: Option<PathBuf>,
@@ -6270,8 +6269,7 @@ impl Screen {
pane_title_alignment: PaneTitleAlignment,
pane_gap_x: usize,
pane_gap_y: usize,
- pane_padding_x: usize,
- pane_padding_y: usize,
+ pane_padding: PanePadding,
stacked_resize: bool,
stacked_pane_list: bool,
default_editor: Option<PathBuf>,
@@ -6331,7 +6329,7 @@ impl Screen {
tab.update_copy_options(&self.copy_options);
// must precede set_pane_frames: gap/padding are baked into
// content_offset by the sweep that set_pane_frames triggers
- tab.update_frame_layout(pane_title_alignment, pane_gap_x, pane_gap_y, pane_padding_x, pane_padding_y);
+ tab.update_frame_layout(pane_title_alignment, pane_gap_x, pane_gap_y, pane_padding);
tab.set_pane_frames(pane_frame_style);
tab.update_arrow_fonts(should_support_arrow_fonts);
tab.update_advanced_mouse_actions(advanced_mouse_actions);
@@ -10850,8 +10848,7 @@ pub(crate) fn screen_thread_main(
pane_title_alignment,
pane_gap_x,
pane_gap_y,
- pane_padding_x,
- pane_padding_y,
+ pane_padding,
stacked_resize,
stacked_pane_list,
default_editor,
@@ -10882,8 +10879,7 @@ pub(crate) fn screen_thread_main(
pane_title_alignment,
pane_gap_x,
pane_gap_y,
- pane_padding_x,
- pane_padding_y,
+ pane_padding,
stacked_resize,
stacked_pane_list,
default_editor,
diff --git a/sl0ppij/zellij-server/src/tab/mod.rs b/sl0ppij/zellij-server/src/tab/mod.rs
index c9047d6..ec08842 100644
--- a/sl0ppij/zellij-server/src/tab/mod.rs
+++ b/sl0ppij/zellij-server/src/tab/mod.rs
@@ -751,8 +751,7 @@ pub trait Pane {
_title_alignment: zellij_utils::data::PaneTitleAlignment,
_gap_x: usize,
_gap_y: usize,
- _padding_x: usize,
- _padding_y: usize,
+ _padding: zellij_utils::data::PanePadding,
) {
}
fn set_should_be_suppressed(&mut self, _should_be_suppressed: bool) {}
@@ -7420,20 +7419,18 @@ impl Tab {
title_alignment: zellij_utils::data::PaneTitleAlignment,
gap_x: usize,
gap_y: usize,
- padding_x: usize,
- padding_y: usize,
+ padding: zellij_utils::data::PanePadding,
) {
self.style.pane_title_alignment = title_alignment;
self.style.pane_gap_x = gap_x;
self.style.pane_gap_y = gap_y;
- self.style.pane_padding_x = padding_x;
- self.style.pane_padding_y = padding_y;
+ self.style.pane_padding = padding;
self.floating_panes
- .update_pane_frame_layout(title_alignment, gap_x, gap_y, padding_x, padding_y);
+ .update_pane_frame_layout(title_alignment, gap_x, gap_y, padding);
self.tiled_panes
- .update_pane_frame_layout(title_alignment, gap_x, gap_y, padding_x, padding_y);
+ .update_pane_frame_layout(title_alignment, gap_x, gap_y, padding);
for (_, pane) in self.suppressed_panes.values_mut() {
- pane.update_frame_layout(title_alignment, gap_x, gap_y, padding_x, padding_y);
+ pane.update_frame_layout(title_alignment, gap_x, gap_y, padding);
}
}
pub fn update_arrow_fonts(&mut self, should_support_arrow_fonts: bool) {
diff --git a/sl0ppij/zellij-utils/src/data.rs b/sl0ppij/zellij-utils/src/data.rs
index aa455b3..218db21 100644
--- a/sl0ppij/zellij-utils/src/data.rs
+++ b/sl0ppij/zellij-utils/src/data.rs
@@ -1413,6 +1413,32 @@ impl std::str::FromStr for PaneTitleAlignment {
}
}
+/// Inner padding between a pane's frame and its content, per side.
+///
+/// Configured CSS-shorthand style, but note the 4-value order is
+/// **up down left right** (as requested), NOT the CSS clockwise
+/// top-right-bottom-left. See the KDL parser for the accepted forms.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
+pub struct PanePadding {
+ pub top: usize,
+ pub bottom: usize,
+ pub left: usize,
+ pub right: usize,
+}
+
+impl PanePadding {
+ pub fn uniform(n: usize) -> Self {
+ Self { top: n, bottom: n, left: n, right: n }
+ }
+ /// vertical (up/down) and horizontal (left/right)
+ pub fn axes(v: usize, h: usize) -> Self {
+ Self { top: v, bottom: v, left: h, right: h }
+ }
+ pub fn is_zero(&self) -> bool {
+ self.top == 0 && self.bottom == 0 && self.left == 0 && self.right == 0
+ }
+}
+
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
pub struct Style {
pub colors: Styling,
@@ -1436,11 +1462,9 @@ pub struct Style {
/// an equal distance. See FrameConfig::gap_aspect.
pub pane_gap_x: usize,
pub pane_gap_y: usize,
- /// INNER padding: cells between a pane's frame and its content. Purely
- /// cosmetic breathing room inside the pane; does not move the frame.
- /// Also aspect-corrected.
- pub pane_padding_x: usize,
- pub pane_padding_y: usize,
+ /// INNER padding: cells between a pane's frame and its content, per side.
+ /// Purely cosmetic breathing room inside the pane; does not move the frame.
+ pub pane_padding: PanePadding,
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
diff --git a/sl0ppij/zellij-utils/src/input/config.rs b/sl0ppij/zellij-utils/src/input/config.rs
index be08adb..db86de5 100644
--- a/sl0ppij/zellij-utils/src/input/config.rs
+++ b/sl0ppij/zellij-utils/src/input/config.rs
@@ -1393,6 +1393,51 @@ mod config_test {
}
#[test]
+ fn pane_padding_accepts_1_2_and_4_value_shorthand() {
+ let parse = |padding: &str| {
+ let contents = format!(
+ "ui {{\n pane_frames {{\n padding {}\n }}\n}}",
+ padding
+ );
+ Config::from_kdl(&contents, None).map(|c| c.ui.pane_frames.padding)
+ };
+
+ // 1 value: all sides, and left/right are aspect-corrected (x2 default)
+ // so that `padding 1` reads as square on a non-square cell grid
+ let p = parse("1").unwrap();
+ assert_eq!((p.top, p.bottom), (1, 1), "1-value: vertical");
+ assert_eq!((p.left, p.right), (2, 2), "1-value: horizontal is aspect-corrected");
+
+ // 2 values: up/down then left/right, taken LITERALLY (no aspect scaling -
+ // the caller is already controlling the horizontal size)
+ let p = parse("1 2").unwrap();
+ assert_eq!((p.top, p.bottom, p.left, p.right), (1, 1, 2, 2), "2-value");
+
+ // 4 values: up down left right (NOT css clockwise order)
+ let p = parse("1 2 3 4").unwrap();
+ assert_eq!((p.top, p.bottom, p.left, p.right), (1, 2, 3, 4), "4-value");
+
+ // 3 and 5 are rejected rather than silently reinterpreted
+ assert!(parse("1 2 3").is_err(), "3 values must be rejected");
+ assert!(parse("1 2 3 4 5").is_err(), "5 values must be rejected");
+
+ // negatives clamp to 0 instead of wrapping to a huge usize
+ let p = parse("-4").unwrap();
+ assert!(p.is_zero(), "negative padding must clamp to zero, got {:?}", p);
+ }
+
+ #[test]
+ fn pane_padding_aspect_is_configurable() {
+ let contents = "ui {\n pane_frames {\n gap_aspect 1\n padding 1\n }\n}";
+ let p = Config::from_kdl(contents, None).unwrap().ui.pane_frames.padding;
+ assert_eq!(
+ (p.top, p.left),
+ (1, 1),
+ "gap_aspect 1 disables horizontal correction"
+ );
+ }
+
+ #[test]
fn can_define_ui_configuration_in_configfile() {
let config_contents = r#"
ui {
diff --git a/sl0ppij/zellij-utils/src/input/theme.rs b/sl0ppij/zellij-utils/src/input/theme.rs
index f4f2ec8..f1e0adf 100644
--- a/sl0ppij/zellij-utils/src/input/theme.rs
+++ b/sl0ppij/zellij-utils/src/input/theme.rs
@@ -30,8 +30,9 @@ pub struct FrameConfig {
/// outer gap between panes, expressed in ROWS (see Style::pane_gap_y).
/// Columns are scaled by `gap_aspect` so the gap looks square.
pub gap: usize,
- /// inner padding between a pane's frame and its content, in ROWS
- pub padding: usize,
+ /// inner padding between a pane's frame and its content, per side.
+ /// Already resolved at parse time (including any aspect correction).
+ pub padding: crate::data::PanePadding,
/// Columns per row. Terminal cells are roughly twice as tall as they are
/// wide, so 1 row of gap needs ~2 columns to look like the same distance.
/// 0 is treated as 1 (no correction).
@@ -49,7 +50,7 @@ impl Default for FrameConfig {
hide_session_name: false,
title_alignment: Default::default(),
gap: 0,
- padding: 0,
+ padding: Default::default(),
gap_aspect: 2,
}
}
@@ -63,9 +64,8 @@ impl FrameConfig {
pub fn resolved_gap(&self) -> (usize, usize) {
(self.gap * self.aspect(), self.gap)
}
- /// (columns, rows) of inner padding
- pub fn resolved_padding(&self) -> (usize, usize) {
- (self.padding * self.aspect(), self.padding)
+ pub fn resolved_padding(&self) -> crate::data::PanePadding {
+ self.padding
}
}
diff --git a/sl0ppij/zellij-utils/src/kdl/mod.rs b/sl0ppij/zellij-utils/src/kdl/mod.rs
index c2500eb..b567019 100644
--- a/sl0ppij/zellij-utils/src/kdl/mod.rs
+++ b/sl0ppij/zellij-utils/src/kdl/mod.rs
@@ -3,7 +3,7 @@ use crate::data::{
BareKey, Direction, FloatingPaneCoordinates, InputMode, KeyWithModifier, LayoutInfo,
LayoutMetadata, MultiplayerColors, Palette, PaletteColor, PaneId, PaneInfo, PaneManifest,
PermissionType, Resize, SessionInfo, StyleDeclaration, Styling, TabInfo, WebSharing,
- DEFAULT_STYLES,
+ DEFAULT_STYLES, PanePadding,
};
use crate::envs::EnvironmentVariables;
use crate::home::{find_default_config_dir, get_layout_dir};
@@ -5548,16 +5548,53 @@ impl UiConfig {
};
// negative values are meaningless here and would wrap on `as usize`,
// so clamp at 0 rather than silently producing a gigantic inset
- let gap = kdl_children_property_first_arg_as_i64!(pane_frames, "gap")
- .unwrap_or(0)
- .max(0) as usize;
- let padding = kdl_children_property_first_arg_as_i64!(pane_frames, "padding")
- .unwrap_or(0)
- .max(0) as usize;
// default 2: terminal cells are ~2x taller than wide
let gap_aspect = kdl_children_property_first_arg_as_i64!(pane_frames, "gap_aspect")
.unwrap_or(2)
.max(1) as usize;
+ let gap = kdl_children_property_first_arg_as_i64!(pane_frames, "gap")
+ .unwrap_or(0)
+ .max(0) as usize;
+ // `padding` accepts CSS-style shorthand, but note the 4-value order
+ // is UP DOWN LEFT RIGHT (not CSS's clockwise top-right-bottom-left):
+ // padding 1 -> all four sides
+ // padding 1 2 -> up/down = 1, left/right = 2
+ // padding 1 2 3 4 -> up=1 down=2 left=3 right=4
+ //
+ // ASPECT: the single-value form is aspect-corrected (left/right are
+ // multiplied by gap_aspect) so `padding 1` looks square. The 2- and
+ // 4-value forms are taken literally, because specifying axes
+ // separately means you are already controlling the horizontal size
+ // and silently doubling it would be surprising.
+ let padding = match pane_frames.children().and_then(|c| c.get("padding")) {
+ Some(node) => {
+ let values: Vec<i64> = kdl_entries_as_i64!(node).flatten().collect();
+ let clamp = |v: i64| v.max(0) as usize;
+ match values.len() {
+ 0 => Default::default(),
+ 1 => PanePadding::axes(clamp(values[0]), clamp(values[0]) * gap_aspect),
+ 2 => PanePadding::axes(clamp(values[0]), clamp(values[1])),
+ 4 => PanePadding {
+ top: clamp(values[0]),
+ bottom: clamp(values[1]),
+ left: clamp(values[2]),
+ right: clamp(values[3]),
+ },
+ n => {
+ return Err(ConfigError::new_kdl_error(
+ format!(
+ "padding takes 1, 2 or 4 values (got {}): \
+`padding <all>`, `padding <up/down> <left/right>`, or `padding <up> <down> <left> <right>`",
+ n
+ ),
+ node.span().offset(),
+ node.span().len(),
+ ))
+ },
+ }
+ },
+ None => Default::default(),
+ };
let frame_config = FrameConfig {
rounded_corners,
hide_session_name,
@@ -5607,10 +5644,19 @@ impl UiConfig {
gap.push(KdlValue::Base10(self.pane_frames.gap as i64));
frame_config_children.nodes_mut().push(gap);
}
- if self.pane_frames.padding > 0 {
+ if !self.pane_frames.padding.is_zero() {
has_ui_config = true;
+ // always the explicit 4-value form: the 1-value form would be
+ // re-multiplied by gap_aspect when read back
let mut padding = KdlNode::new("padding");
- padding.push(KdlValue::Base10(self.pane_frames.padding as i64));
+ for v in [
+ self.pane_frames.padding.top,
+ self.pane_frames.padding.bottom,
+ self.pane_frames.padding.left,
+ self.pane_frames.padding.right,
+ ] {
+ padding.push(KdlValue::Base10(v as i64));
+ }
frame_config_children.nodes_mut().push(padding);
}
if self.pane_frames.gap_aspect != 2 {
diff --git a/sl0ppij/zellij-utils/src/plugin_api/event.rs b/sl0ppij/zellij-utils/src/plugin_api/event.rs
index 87af043..7bb1c7a 100644
--- a/sl0ppij/zellij-utils/src/plugin_api/event.rs
+++ b/sl0ppij/zellij-utils/src/plugin_api/event.rs
@@ -2495,8 +2495,7 @@ fn serialize_mode_update_event_with_non_default_values() {
pane_title_alignment: Default::default(),
pane_gap_x: Default::default(),
pane_gap_y: Default::default(),
- pane_padding_x: Default::default(),
- pane_padding_y: Default::default(),
+ pane_padding: Default::default(),
},
capabilities: PluginCapabilities { arrow_fonts: false },
session_name: Some("my awesome test session".to_owned()),
diff --git a/sl0ppij/zellij-utils/src/plugin_api/style.rs b/sl0ppij/zellij-utils/src/plugin_api/style.rs
index 266ab7f..527f984 100644
--- a/sl0ppij/zellij-utils/src/plugin_api/style.rs
+++ b/sl0ppij/zellij-utils/src/plugin_api/style.rs
@@ -28,8 +28,7 @@ impl TryFrom<ProtobufStyle> for Style {
pane_title_alignment: Default::default(),
pane_gap_x: Default::default(),
pane_gap_y: Default::default(),
- pane_padding_x: Default::default(),
- pane_padding_y: Default::default(),
+ pane_padding: Default::default(),
})
}
}