Fix icon sizing.
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user