Fix icon sizing.

This commit is contained in:
Greyson Parrelli
2026-08-23 14:35:47 -04:00
parent 63203433dc
commit 4eef6a28d3
4 changed files with 262 additions and 20 deletions
+6
View File
@@ -379,6 +379,11 @@ in principle, but a terminal grid is small.
its pane count once a view holds more than one its pane count once a view holds more than one
- Tabs: create, close, switch, and reorder by dragging a sidebar row, with the - Tabs: create, close, switch, and reorder by dragging a sidebar row, with the
rows shuffling live as you drag; closing the last one closes the window rows shuffling live as you drag; closing the last one closes the window
- **A resizable sidebar**: the gutter beside it is a real divider, so drag it
to taste. Dragging it all the way in — or `Ctrl+Shift+S`, or the chevron at
the sidebar's foot — collapses the column to just each tab's emoji or icon
(hover a row for its name; the status bar and wash still show); the same
toggle expands it back to the width it had
- Multiple panes per tab in an arbitrary split tree, rearranged by keyboard - Multiple panes per tab in an arbitrary split tree, rearranged by keyboard
or by dragging a pane's header, with draggable dividers between them; or by dragging a pane's header, with draggable dividers between them;
closing the last pane in a view closes its tab closing the last pane in a view closes its tab
@@ -432,6 +437,7 @@ in principle, but a terminal grid is small.
| `Ctrl+Shift+C` | copy the terminal selection | `copy` | | `Ctrl+Shift+C` | copy the terminal selection | `copy` |
| `Ctrl+Shift+R` | rename the current tab (empty name = follow the terminal) | `rename_tab` | | `Ctrl+Shift+R` | rename the current tab (empty name = follow the terminal) | `rename_tab` |
| `Ctrl+Shift+Z`, `Ctrl+Shift+F` | zoom the focused pane to fill the tab, and back | `toggle_zoom` | | `Ctrl+Shift+Z`, `Ctrl+Shift+F` | zoom the focused pane to fill the tab, and back | `toggle_zoom` |
| `Ctrl+Shift+S` | collapse the sidebar to its emoji column, and back | `toggle_sidebar` |
| `Ctrl+F` | find in the focused web page (web panes only — see [Find in page](#find-in-page)) | `find` | | `Ctrl+F` | find in the focused web page (web panes only — see [Find in page](#find-in-page)) | `find` |
| `Ctrl+,` | settings | `open_settings` | | `Ctrl+,` | settings | `open_settings` |
| `Ctrl+PageUp/PageDown`, `Alt+Shift+K/J` | previous / next tab | `prev_tab`, `next_tab` | | `Ctrl+PageUp/PageDown`, `Alt+Shift+K/J` | previous / next tab | `prev_tab`, `next_tab` |
+233 -15
View File
@@ -31,18 +31,55 @@ const shortcuts = @import("shortcuts.zig");
const Window = @This(); const Window = @This();
/// Total width of the sidebar column, margins and border included — GTK folds /// Default width of the sidebar column, margins and border included — GTK folds
/// both into a widget's size request. Widened when the sidebar became an inset /// both into a widget's size request. Widened when the sidebar became an inset
/// card: the margin, border and roomier row padding all come out of the label, /// card: the margin, border and roomier row padding all come out of the label,
/// and at the old 220 a tab name truncated a good deal earlier than it used to. /// and at the old 220 a tab name truncated a good deal earlier than it used to.
///
/// A default rather than a fixture now that the divider beside the sidebar is
/// draggable: it is where the column starts, and where the collapse toggle
/// returns to when nothing has been dragged.
const sidebar_width = 236; const sidebar_width = 236;
/// The sidebar folded down to its leading column: the margin, border, list and
/// row padding, and the 16px slot the emoji (or kind icon) draws in. Nothing
/// else fits at this width, which is the point — see `applySidebarCollapsed`.
const sidebar_collapsed_width = 56;
alloc: std.mem.Allocator, alloc: std.mem.Allocator,
window: *adw.ApplicationWindow, window: *adw.ApplicationWindow,
/// Holds one page per tab; the visible page is the active terminal. /// Holds one page per tab; the visible page is the active terminal.
stack: *gtk.Stack, stack: *gtk.Stack,
/// The paned holding the sidebar and the content, whose divider position *is*
/// the sidebar's width. A paned rather than a box so the divider is draggable.
split: *gtk.Paned,
/// Sidebar chrome that collapsing has to reach: the header (whose window
/// controls go), the layouts button beside the new-tab one, the footer (which
/// stacks its buttons when the column is narrow) and the toggle itself.
header: *adw.HeaderBar,
layout_button: *gtk.MenuButton,
footer: *gtk.Box,
collapse_button: *gtk.Button,
/// Whether the sidebar is folded down to its emoji column.
sidebar_collapsed: bool = false,
/// The width to put back when the sidebar expands: wherever the divider last
/// sat while the chrome was showing, so a collapse costs no arrangement.
sidebar_expanded_width: c_int = sidebar_width,
/// The position a drag has to reach before a collapsed sidebar unfolds. Set to
/// the expanded minimum measured at the moment of collapse, so folding and
/// unfolding by drag happen at the same width.
sidebar_expand_at: c_int = sidebar_width,
/// Set while we're moving the divider ourselves, so the position handler
/// doesn't mistake it for a drag.
applying_position: bool = false,
/// One row per tab, in the same order as `tabs`. /// One row per tab, in the same order as `tabs`.
/// ///
/// Kept in that order by a sort function rather than by moving rows around: /// Kept in that order by a sort function rather than by moving rows around:
@@ -138,6 +175,10 @@ const Tab = struct {
/// Status dot, hidden unless the tab has something to report. /// Status dot, hidden unless the tab has something to report.
dot: *gtk.Image, dot: *gtk.Image,
/// The row's close button, hidden along with the label while the sidebar
/// is collapsed.
close: *gtk.Button,
/// A name the user typed, which wins over whatever the panes report. /// A name the user typed, which wins over whatever the panes report.
/// Null means the label tracks the content, which is the default. /// Null means the label tracks the content, which is the default.
custom_name: ?[]u8 = null, custom_name: ?[]u8 = null,
@@ -191,9 +232,14 @@ pub fn create(alloc: std.mem.Allocator, app: *adw.Application) !*Window {
.alloc = alloc, .alloc = alloc,
.window = window, .window = window,
.stack = gtk.Stack.new(), .stack = gtk.Stack.new(),
.split = gtk.Paned.new(.horizontal),
.list = gtk.ListBox.new(), .list = gtk.ListBox.new(),
.layouts = .init(alloc), .layouts = .init(alloc),
.layout_popover = gtk.Popover.new(), .layout_popover = gtk.Popover.new(),
.header = adw.HeaderBar.new(),
.layout_button = gtk.MenuButton.new(),
.footer = gtk.Box.new(.horizontal, 0),
.collapse_button = gtk.Button.newFromIconName("go-previous-symbolic"),
}; };
self.layouts.load(); self.layouts.load();
if (self.layouts.load_error) |message| std.log.warn("{s}", .{message}); if (self.layouts.load_error) |message| std.log.warn("{s}", .{message});
@@ -203,13 +249,17 @@ pub fn create(alloc: std.mem.Allocator, app: *adw.Application) !*Window {
// ---- sidebar ------------------------------------------------------- // ---- sidebar -------------------------------------------------------
const sidebar = gtk.Box.new(.vertical, 0); const sidebar = gtk.Box.new(.vertical, 0);
sidebar.as(gtk.Widget).addCssClass("playpen-sidebar"); sidebar.as(gtk.Widget).addCssClass("playpen-sidebar");
sidebar.as(gtk.Widget).setSizeRequest(sidebar_width, -1);
// The request is the *floor*, not the width: the paned's divider decides
// how wide the column actually is, and this is as far in as it may be
// pushed once the chrome is out of the way.
sidebar.as(gtk.Widget).setSizeRequest(sidebar_collapsed_width, -1);
// The header bar lives inside the sidebar rather than spanning the // The header bar lives inside the sidebar rather than spanning the
// window, which is what gives the Zen-style look. It also carries the // window, which is what gives the Zen-style look. It also carries the
// window controls, which we still need since GTK draws its own // window controls, which we still need since GTK draws its own
// decorations on Wayland. // decorations on Wayland.
const header = adw.HeaderBar.new(); const header = self.header;
header.setShowTitle(0); header.setShowTitle(0);
header.as(gtk.Widget).addCssClass("flat"); header.as(gtk.Widget).addCssClass("flat");
@@ -234,7 +284,7 @@ pub fn create(alloc: std.mem.Allocator, app: *adw.Application) !*Window {
// Layouts sit behind their own button rather than replacing the plain // Layouts sit behind their own button rather than replacing the plain
// new-tab one: opening an ordinary shell stays a single click. // new-tab one: opening an ordinary shell stays a single click.
const layout_button = gtk.MenuButton.new(); const layout_button = self.layout_button;
layout_button.setIconName("view-grid-symbolic"); layout_button.setIconName("view-grid-symbolic");
layout_button.as(gtk.Widget).addCssClass("playpen-header-button"); layout_button.as(gtk.Widget).addCssClass("playpen-header-button");
layout_button.as(gtk.Widget).setTooltipText("Open a saved layout"); layout_button.as(gtk.Widget).setTooltipText("Open a saved layout");
@@ -273,7 +323,7 @@ pub fn create(alloc: std.mem.Allocator, app: *adw.Application) !*Window {
// saved layout — and a preferences button is the opposite of that: opened // saved layout — and a preferences button is the opposite of that: opened
// rarely, and never in a hurry. Below the tab list it stays out of the way // rarely, and never in a hurry. Below the tab list it stays out of the way
// of both, and it is where every other sidebar puts it. // of both, and it is where every other sidebar puts it.
const footer = gtk.Box.new(.horizontal, 0); const footer = self.footer;
footer.as(gtk.Widget).addCssClass("playpen-sidebar-footer"); footer.as(gtk.Widget).addCssClass("playpen-sidebar-footer");
const settings_button = gtk.Button.newFromIconName("emblem-system-symbolic"); const settings_button = gtk.Button.newFromIconName("emblem-system-symbolic");
@@ -288,6 +338,26 @@ pub fn create(alloc: std.mem.Allocator, app: *adw.Application) !*Window {
.{}, .{},
); );
footer.append(settings_button.as(gtk.Widget)); footer.append(settings_button.as(gtk.Widget));
// An empty stretch between the two buttons, so the collapse toggle sits at
// the far end. It has no height of its own, so when the collapsed footer
// turns vertical it simply does nothing rather than pushing the buttons
// apart.
const footer_spacer = gtk.Box.new(.horizontal, 0);
footer_spacer.as(gtk.Widget).setHexpand(1);
footer.append(footer_spacer.as(gtk.Widget));
self.collapse_button.as(gtk.Widget).addCssClass("flat");
self.collapse_button.as(gtk.Widget).addCssClass("playpen-settings-button");
self.collapse_button.as(gtk.Widget).setTooltipText("Collapse the sidebar (Ctrl+Shift+S)");
_ = gtk.Button.signals.clicked.connect(
self.collapse_button,
*Window,
&onCollapseClicked,
self,
.{},
);
footer.append(self.collapse_button.as(gtk.Widget));
sidebar.append(footer.as(gtk.Widget)); sidebar.append(footer.as(gtk.Widget));
// ---- content ------------------------------------------------------- // ---- content -------------------------------------------------------
@@ -295,11 +365,29 @@ pub fn create(alloc: std.mem.Allocator, app: *adw.Application) !*Window {
self.stack.as(gtk.Widget).setVexpand(1); self.stack.as(gtk.Widget).setVexpand(1);
self.stack.as(gtk.Widget).addCssClass("playpen-content"); self.stack.as(gtk.Widget).addCssClass("playpen-content");
const content = gtk.Box.new(.horizontal, 0); // The divider between the sidebar and the content is the 6px gutter that
content.append(sidebar.as(gtk.Widget)); // was already there, now draggable. Only the content flexes with the
content.append(self.stack.as(gtk.Widget)); // window — the sidebar keeps whatever width it was given — and neither
// side may be squeezed below its minimum, which for the sidebar is the
// emoji-only column.
self.split.as(gtk.Widget).addCssClass("playpen-root-split");
self.split.setStartChild(sidebar.as(gtk.Widget));
self.split.setEndChild(self.stack.as(gtk.Widget));
self.split.setResizeStartChild(0);
self.split.setResizeEndChild(1);
self.split.setShrinkStartChild(0);
self.split.setShrinkEndChild(0);
self.split.setWideHandle(1);
self.setSidebarPosition(sidebar_width);
_ = gobject.Object.signals.notify.connect(
self.split,
*Window,
&onSidebarPosition,
self,
.{ .detail = "position" },
);
window.setContent(content.as(gtk.Widget)); window.setContent(self.split.as(gtk.Widget));
// Window-level shortcuts run in the capture phase so they are handled // Window-level shortcuts run in the capture phase so they are handled
// before the focused terminal turns the key into a VT sequence. // before the focused terminal turns the key into a VT sequence.
@@ -385,6 +473,7 @@ fn newTabEmpty(self: *Window) !*Tab {
.icon = gtk.Image.newFromIconName("utilities-terminal-symbolic"), .icon = gtk.Image.newFromIconName("utilities-terminal-symbolic"),
.emoji_label = gtk.Label.new(null), .emoji_label = gtk.Label.new(null),
.dot = gtk.Image.newFromIconName(Pane.status_icon), .dot = gtk.Image.newFromIconName(Pane.status_icon),
.close = gtk.Button.newFromIconName("window-close-symbolic"),
.menu_popover = gtk.Popover.new(), .menu_popover = gtk.Popover.new(),
.rename_popover = gtk.Popover.new(), .rename_popover = gtk.Popover.new(),
.rename_entry = gtk.Entry.new(), .rename_entry = gtk.Entry.new(),
@@ -417,11 +506,10 @@ fn newTabEmpty(self: *Window) !*Tab {
Pane.applyAttention(tab.row.as(gtk.Widget), tab.dot, .none); Pane.applyAttention(tab.row.as(gtk.Widget), tab.dot, .none);
row_box.append(tab.dot.as(gtk.Widget)); row_box.append(tab.dot.as(gtk.Widget));
const close = gtk.Button.newFromIconName("window-close-symbolic"); tab.close.as(gtk.Widget).addCssClass("flat");
close.as(gtk.Widget).addCssClass("flat"); tab.close.as(gtk.Widget).addCssClass("playpen-close");
close.as(gtk.Widget).addCssClass("playpen-close"); _ = gtk.Button.signals.clicked.connect(tab.close, *Tab, &onCloseClicked, tab, .{});
_ = gtk.Button.signals.clicked.connect(close, *Tab, &onCloseClicked, tab, .{}); row_box.append(tab.close.as(gtk.Widget));
row_box.append(close.as(gtk.Widget));
tab.row.setChild(row_box.as(gtk.Widget)); tab.row.setChild(row_box.as(gtk.Widget));
self.buildRowMenu(tab, row_box); self.buildRowMenu(tab, row_box);
@@ -429,6 +517,9 @@ fn newTabEmpty(self: *Window) !*Tab {
installRowDragSource(tab, row_box); installRowDragSource(tab, row_box);
self.list.append(tab.row.as(gtk.Widget)); self.list.append(tab.row.as(gtk.Widget));
// A tab opened while the sidebar is collapsed starts collapsed too.
self.applyRowCollapsed(tab);
_ = self.stack.addNamed(view.widget(), tab.pageName()); _ = self.stack.addNamed(view.widget(), tab.pageName());
try self.tabs.append(self.alloc, tab); try self.tabs.append(self.alloc, tab);
@@ -1384,6 +1475,128 @@ fn closeTab(self: *Window, tab: *Tab) void {
self.select(self.tabs.items[next]); self.select(self.tabs.items[next]);
} }
// -------------------------------------------------------------------------
// Sidebar width
//
// The divider beside the sidebar is a real paned handle, so the column is
// resizable by drag. Below a certain width the sidebar stops being a list of
// names and becomes a list of emoji, and that switch is a *mode*, not a
// squeeze: the labels, dots, close buttons, layouts button and window controls
// all leave, because at emoji width every one of them would be clipped rather
// than small. The mode is entered three ways — the footer toggle, the
// `toggle_sidebar` shortcut, and dragging the divider all the way in — and all
// three meet in `applySidebarCollapsed`.
fn onCollapseClicked(_: *gtk.Button, self: *Window) callconv(.c) void {
self.toggleSidebar();
}
/// Toggle between the emoji-only column and the last expanded width — which is
/// the default width until a drag has said otherwise.
fn toggleSidebar(self: *Window) void {
if (self.sidebar_collapsed) {
self.applySidebarCollapsed(false);
self.setSidebarPosition(self.sidebar_expanded_width);
} else {
self.sidebar_expanded_width = self.split.getPosition();
self.applySidebarCollapsed(true);
self.setSidebarPosition(sidebar_collapsed_width);
}
}
/// Show or hide everything that doesn't fit an emoji-wide column. The width
/// itself is the caller's business: the toggle sets it, and a drag is already
/// setting it.
fn applySidebarCollapsed(self: *Window, collapsed: bool) void {
if (self.sidebar_collapsed == collapsed) return;
// Measured before anything hides: this is the narrowest the expanded
// sidebar can be, so it is where a drag has to reach before the chrome is
// given back — the same width folding happened at, which is what keeps a
// slow drag from flickering between the two modes.
if (collapsed) {
self.sidebar_expand_at = @max(
minPosition(self.split),
sidebar_collapsed_width + 24,
);
}
self.sidebar_collapsed = collapsed;
const shown: c_int = @intFromBool(!collapsed);
// The window controls belong to the desktop, but at 56px they would be
// clipped mid-button, which is worse than absent. Closing the window is
// still one expand away, or a keyboard shortcut that never left.
self.header.setShowStartTitleButtons(shown);
self.header.setShowEndTitleButtons(shown);
self.layout_button.as(gtk.Widget).setVisible(shown);
// Two buttons side by side don't fit the collapsed column, so the footer
// stacks them instead of losing one.
self.footer.as(gtk.Orientable).setOrientation(if (collapsed) .vertical else .horizontal);
self.collapse_button.setIconName(if (collapsed)
"go-next-symbolic"
else
"go-previous-symbolic");
self.collapse_button.as(gtk.Widget).setTooltipText(if (collapsed)
"Expand the sidebar (Ctrl+Shift+S)"
else
"Collapse the sidebar (Ctrl+Shift+S)");
for (self.tabs.items) |tab| self.applyRowCollapsed(tab);
}
/// One row's share of the collapse: everything but the emoji (or kind icon)
/// goes. The status dot goes too — the bar down the row's leading edge and the
/// wash behind it carry the state at this width, and they were designed to be
/// the glanceable half of the pair anyway.
fn applyRowCollapsed(self: *Window, tab: *Tab) void {
const shown: c_int = @intFromBool(!self.sidebar_collapsed);
tab.label.as(gtk.Widget).setVisible(shown);
tab.dot.as(gtk.Widget).setVisible(shown);
tab.close.as(gtk.Widget).setVisible(shown);
}
/// Move the divider ourselves, without the position handler reading it as a
/// drag.
fn setSidebarPosition(self: *Window, position: c_int) void {
self.applying_position = true;
defer self.applying_position = false;
self.split.setPosition(position);
}
/// The divider moved by hand. Expanded, the paned won't let a drag go below
/// the chrome's own minimum — so a position *at* that minimum means the drag is
/// pinned against it and wants less, and the answer is to fold. Hiding the
/// chrome lowers the paned's minimum, which is what lets the same drag carry on
/// down to the emoji column. Dragging back out past where the fold happened
/// unfolds again.
fn onSidebarPosition(paned: *gtk.Paned, _: *gobject.ParamSpec, self: *Window) callconv(.c) void {
if (self.applying_position) return;
const position = paned.getPosition();
if (self.sidebar_collapsed) {
if (position >= self.sidebar_expand_at) self.applySidebarCollapsed(false);
return;
}
if (position <= minPosition(paned)) {
self.applySidebarCollapsed(true);
return;
}
self.sidebar_expanded_width = position;
}
fn minPosition(paned: *gtk.Paned) c_int {
var value = gobject.ext.Value.new(c_int);
defer value.unset();
paned.as(gobject.Object).getProperty("min-position", &value);
return gobject.ext.Value.get(&value, c_int);
}
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Settings // Settings
@@ -1472,7 +1685,11 @@ fn refreshLabel(self: *Window, tab: *Tab) void {
buf[text.len] = 0; buf[text.len] = 0;
tab.label.setText(buf[0..text.len :0]); tab.label.setText(buf[0..text.len :0]);
tab.label.as(gtk.Widget).setTooltipText(buf[0..text.len :0]);
// On the row rather than the label, so a collapsed sidebar — where the
// label is hidden and the emoji is all a row shows — still says which tab
// an emoji is when you hover it.
tab.row.as(gtk.Widget).setTooltipText(buf[0..text.len :0]);
// An emoji replaces the icon rather than joining it. The row has one slot // An emoji replaces the icon rather than joining it. The row has one slot
// for "what is this tab", and filling it twice would spend twice the width // for "what is this tab", and filling it twice would spend twice the width
@@ -1672,6 +1889,7 @@ fn perform(self: *Window, action: shortcuts.Action) bool {
.new_web => self.addPane(.web), .new_web => self.addPane(.web),
.rename_tab => if (self.activeTab()) |tab| self.beginRename(tab), .rename_tab => if (self.activeTab()) |tab| self.beginRename(tab),
.toggle_zoom => if (self.activeTab()) |tab| tab.view.toggleZoomFocused(), .toggle_zoom => if (self.activeTab()) |tab| tab.view.toggleZoomFocused(),
.toggle_sidebar => self.toggleSidebar(),
.open_settings => self.openSettings(), .open_settings => self.openSettings(),
// Only a terminal needs us to encode a paste for it. A web pane has its // Only a terminal needs us to encode a paste for it. A web pane has its
+6
View File
@@ -110,6 +110,7 @@ pub const Action = enum {
new_web, new_web,
rename_tab, rename_tab,
toggle_zoom, toggle_zoom,
toggle_sidebar,
paste, paste,
copy, copy,
find, find,
@@ -158,6 +159,7 @@ pub const defaults: []const Binding = &.{
.{ .chord = chord("ctrl+shift+r"), .action = .rename_tab }, .{ .chord = chord("ctrl+shift+r"), .action = .rename_tab },
.{ .chord = chord("ctrl+shift+z"), .action = .toggle_zoom }, .{ .chord = chord("ctrl+shift+z"), .action = .toggle_zoom },
.{ .chord = chord("ctrl+shift+f"), .action = .toggle_zoom }, .{ .chord = chord("ctrl+shift+f"), .action = .toggle_zoom },
.{ .chord = chord("ctrl+shift+s"), .action = .toggle_sidebar },
.{ .chord = chord("ctrl+shift+v"), .action = .paste }, .{ .chord = chord("ctrl+shift+v"), .action = .paste },
.{ .chord = chord("ctrl+shift+c"), .action = .copy }, .{ .chord = chord("ctrl+shift+c"), .action = .copy },
.{ .chord = chord("ctrl+f"), .action = .find }, .{ .chord = chord("ctrl+f"), .action = .find },
@@ -193,6 +195,10 @@ pub const defaults: []const Binding = &.{
/// default is a build failure rather than a shortcut that quietly isn't there. /// default is a build failure rather than a shortcut that quietly isn't there.
fn chord(comptime text: []const u8) Chord { fn chord(comptime text: []const u8) Chord {
comptime { comptime {
// The whole defaults table parses in one comptime evaluation, and the
// key-name lookup walks a large enum per entry; the default quota is
// a few chords too small for the table's current size.
@setEvalBranchQuota(100_000);
const parsed = parse(text) orelse @compileError("not a chord: " ++ text); const parsed = parse(text) orelse @compileError("not a chord: " ++ text);
return parsed; return parsed;
} }
+17 -5
View File
@@ -225,12 +225,12 @@ dnd.playpen-tab-drag {
background-color: @pp_bg; background-color: @pp_bg;
} }
/* A view is the container for one tab's terminals. Even padding now that the /* A view is the container for one tab's terminals. The left side has no
sidebar is inset too — the left edge used to be flush against the sidebar's padding of its own: the root split's separator is the 6px gutter there, so
border, so the gutter around the content was open on three sides and closed the content stays evenly inset while that gap doubles as the drag handle
on the fourth. */ that resizes the sidebar. */
.playpen-view { .playpen-view {
padding: 6px; padding: 6px 6px 6px 0;
} }
/* Paned itself draws nothing; the panes inside it carry the styling. */ /* Paned itself draws nothing; the panes inside it carry the styling. */
@@ -724,6 +724,18 @@ dnd.playpen-tab-drag {
box-shadow: inset 0 0 0 1px @pp_accent; box-shadow: inset 0 0 0 1px @pp_accent;
} }
/* The divider between the sidebar and the content: the gutter that was always
there, made draggable. It paints as the backdrop so nothing looks different
until it is hovered, the same way the pane dividers behave. */
.playpen-root-split > separator {
background-color: @pp_bg;
min-width: 6px;
}
.playpen-root-split > separator:hover {
background-color: @pp_accent_muted;
}
/* Divider between panes. Wide enough to grab without hunting for it. */ /* Divider between panes. Wide enough to grab without hunting for it. */
.playpen-view paned > separator { .playpen-view paned > separator {
background-color: @pp_bg; background-color: @pp_bg;