Skip to content

Redesign top bar + sidebar, add settings (#5) - #62

Open
mvbmir wants to merge 4 commits into
am-will:mainfrom
mvbmir:mvbmir/redesign-top-bar-clean
Open

Redesign top bar + sidebar, add settings (#5)#62
mvbmir wants to merge 4 commits into
am-will:mainfrom
mvbmir:mvbmir/redesign-top-bar-clean

Conversation

@mvbmir

@mvbmir mvbmir commented Apr 17, 2026

Copy link
Copy Markdown
Contributor

Summary

Implements the cmux-style top bar and sidebar redesign plus three related settings and the split-ratio fix. Single commit against main.

Top bar

Custom top bar at the top of the window: sidebar toggle, settings cog, "+" new-workspace button, a row of workspace indicator pills, and the window controls (minimize/maximize/close). Empty space drags the window via gtk::WindowHandle. The window controls are plain gtk::Buttons rather than gtk::WindowControls so their hover shape and sizing match the rest of the icons.

Pane header

  • Empty space between the last tab and the action icons is a WindowHandle filler — drags the window without conflicting with the tabs' DragSource.
  • Middle-click on a tab closes it.
  • Settings cog moved out of the per-pane action row and into the top bar, freeing horizontal tab space.

Sidebar

  • Each row has a close X on the top-right (replaces the old star slot). Star moved below next to the folder path; both are hover-to-reveal 20×20 buttons.
  • Double-click a workspace row to inline-rename.
  • Accent-tinted selected-row background; first-row top margin matches the between-workspace gap.
  • No more "WORKSPACES" title or big bottom "+" button.

New-workspace UX

  • Clicking "+" clones the active workspace's folder. The folder picker only appears on first run / when no workspace exists.

Settings → General (three new rows)

  • Top bar (switch) — when off, the top bar is removed entirely. Its buttons relocate into a new sidebar header (sidebar open) or onto the active workspace's leading pane (sidebar collapsed). Sidebar-header layout puts the app buttons on one end and the window controls on the other with an hexpand spacer between.
  • Window controls side (Left / Right) — moves minimize/maximize/close between the two ends of whichever header is active.
  • Workspace indicators on the top bar (switch) — hides the pills without hiding the top bar. Implementation hides each pill individually so indicator_box keeps its hexpand spacer role.
  • All three persist under interface.* in settings.json. Defaults: show_top_bar = true, show_workspace_indicators = true, window_controls_side = Right — upgrading users see the familiar layout.

Split ratio fix

  • shrink_start_child / shrink_end_child on every gtk::Paned so the saved ratio wins over larger child minimums (e.g. a wide tab strip).
  • position-notify now tracks last_size: width changed → auto-adjust (don't touch ratio); width unchanged → real user drag (update ratio). Fixes the silent 0.5 → 0.6 drift that happened when the sidebar toggled or the window resized — GtkPaned::position is absolute pixels, so the old handler computed a different ratio each time the paned's width changed.
  • Per-frame tick callback observes the paned's actual width and re-applies position = ratio × new_width on size changes. GtkWidget::width / height don't reliably emit notify across GTK 4.x versions, so polling is intentional; the check is O(1) with early return.
  • Startup uses a one-shot tick callback to apply the ratio once the paned first has a non-zero allocation.

Structural

  • handle_config_change() dedupes the appearance + top-bar side-effect + save/revert logic that ran from two config-change sites.
  • apply_top_bar_mode() is the single source of truth for how the dock toggle, settings cog, +, indicator pills, and window controls are laid out — respects both the persistent show_top_bar setting and the transient keyboard toggle.
  • Widget tree walkers (is_pane_widget, find_leaf_focused_pane) unwrap gtk::WindowHandle so the dock-parked-on-pane layout still resolves panes correctly.

Closes #5 (notifications explicitly out of scope per the issue).

Notes for reviewers

  • The add_tick_callback polling in split_tree.rs is intentional. GTK 4.x doesn't reliably emit notify on width/height, so the signal-driven approach silently misses resize events; the tick loop catches them at the cost of one integer compare per frame.
  • gtk::WindowControls is replaced by three plain gtk::Buttons wired to window.minimize() / maximize() / close(). Adwaita's default 24 px circular styling couldn't be cleanly overridden to match the other top-bar icons.

Test plan

  • cargo fmt clean
  • cargo clippy --release -- -D warnings clean
  • cargo test — all 181 unit tests pass
  • Manual: top bar on/off, sidebar open/closed, left/right controls — all combinations
  • Manual: workspace indicators on/off toggles the pills; window controls stay at the right edge regardless
  • Manual: middle-click tab close, last tab closes pane, last pane closes workspace
  • Manual: split ratio stable across sidebar toggles and window resizes
  • Manual: double-click inline rename, folder clone on "+"

@mvbmir

mvbmir commented Apr 17, 2026

Copy link
Copy Markdown
Contributor Author

@am-will Apologies for the big PR, adding a top bar is opinionated design wise, considering you don't have one, but since I really felt the need for it, I added it as an optional UI part.

So, for people who like the no top bar, they can keep the dock only, with slight improvements.

There's also some other quality of life things like closing tabs with middle mouse button, making the pane bars drag the window also, some button designs, etc, so if you see anything you'd like me to separate into a different PR let me know.

@mvbmir
mvbmir marked this pull request as ready for review April 17, 2026 16:03
@am-will

am-will commented Apr 17, 2026

Copy link
Copy Markdown
Owner

I will take a look when I can ty

@mvbmir

mvbmir commented May 5, 2026

Copy link
Copy Markdown
Contributor Author

Hi @am-will, did you end up taking a look?

@am-will

am-will commented May 5, 2026 via email

Copy link
Copy Markdown
Owner

@am-will

am-will commented May 10, 2026

Copy link
Copy Markdown
Owner

let me merge some other issues and i'll get to this. looks interesting for sure

Comment thread rust/limux-host-linux/src/split_tree.rs
Comment thread rust/limux-host-linux/src/window.rs
Comment thread rust/limux-host-linux/src/window.rs

@bvolpato bvolpato left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great redesign and implementation quality. I ran local verification after fixing the missing ghostty dependency artifact (git submodule update --init --recursive ghostty and cd ghostty && zig build -Dapp-runtime=none -Doptimize=ReleaseFast), then reran gates: cargo test and cargo clippy --release -- -D warnings, both clean. Left 3 inline non-blocking suggestions for maintainability/resilience. Approving.

@mvbmir
mvbmir force-pushed the mvbmir/redesign-top-bar-clean branch from 0202f70 to dfdff7d Compare June 5, 2026 13:10
@mvbmir

mvbmir commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

@bvolpato Thanks for the review!

@am-will Sorry for also disappearing for a bit, had my own fare share of health issues to deal with for a while. Let me know if this can go in with the rebase and the fixes applied, or if you'd like some changes to anything!

@bvolpato bvolpato left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strong UI direction. Config compatibility, centralized layout handling, indicator lifecycle, and settings rollback look solid.

Current head needs attention before merge:

  • split_tree.rs:463 enables child shrinking, then immediately disables both flags. Saved ratios still lose against child minimums.
  • split_tree.rs:503 consumes size changes in position-notify, preventing tick callback from reapplying ratio afterward. Resize-driven drift remains when GTK emits that notification.
  • window.rs:1771 leaves drag/drop controller on hidden sidebar button. Visible top-bar + has click handling only, breaking tab drag into new workspace and workspace drag-delete target.
  • window.rs:1694 always renders custom top bar, removing current Wayland xdg-decoration guard and causing stacked compositor/app chrome on affected desktops.

PR also conflicts with current main in config, settings, and window code. Rebase must retain newer interface-scale, terminal shortcut, browser lifecycle, session shutdown, and tab-rename fixes.

Latest GitHub quality check is green. Reviewed all five changed files; isolated Ghostty build passed. Full local gate was interrupted before completion.

Recommendation: COMMENT. Rebase and fix blockers before approval.

(Review assisted by gpt-5.6-sol)

@mvbmir
mvbmir force-pushed the mvbmir/redesign-top-bar-clean branch from 48365a8 to 04b934e Compare July 22, 2026 18:51

@bvolpato bvolpato left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest commits fix both split-ratio findings: child shrinking stays enabled and resize notifications no longer consume size changes before tick reapply. Two blockers remain:

  • window.rs:1766-1792 attaches drag/drop only to hidden sidebar new_ws_btn; visible top-bar + has click handling only, so tab-drop-to-new-workspace and workspace drag-delete lose their target
  • custom WindowHandle header is now unconditional, removing current main's Wayland zxdg_decoration_manager_v1 guard and causing duplicate app/compositor chrome where server-side decorations exist

Branch also conflicts with newer unread-tab, tab-rename, Unicode-title, and tab-drop work. Please rebase and preserve those behaviors.

Quality check is green.

(assisted by gpt-5.6-sol)

mvbmir added 4 commits August 7, 2026 23:42
Top bar:
- Custom top bar at the top of the window with the sidebar toggle,
  settings cog, "+" new-workspace button, a row of workspace indicator
  pills, and the window controls (minimize/maximize/close).
- macOS-style indicator pills: subtle hover/active backgrounds, pill
  shows a small dot + bold when the workspace has unread activity.
- Empty header space drags the window via gtk::WindowHandle.
- Custom buttons replace gtk::WindowControls so hover shape, padding,
  and border-radius match the pane action icons exactly.
- Pane action icons (new terminal, split, close pane) reworked to the
  same compact visual language; tab close X the same.
- Settings cog moved out of the per-pane action row into the top bar,
  freeing horizontal tab space.

Pane header:
- Empty space between the last tab and the action icons is a
  WindowHandle filler, so it drags the window without conflicting
  with the tabs' DragSource.
- Middle-click on a tab closes it.

Sidebar:
- Cleaner workspace rows with a close X on the top-right (replaces the
  star's old position). The favorite star moved below next to the
  folder path; both are hover-to-reveal 20×20 buttons.
- Double-click a row to inline-rename.
- Row selection paints the inner row box with an accent-tinted
  background; Adwaita's default ListBox row styling is suppressed.
- First-row top margin bumped so the gap above the first workspace
  matches the between-workspace gap.
- Drops the "WORKSPACES" title and the big "+" button at the bottom.

New-workspace behavior:
- "+" clones the active workspace's folder instead of opening the
  folder picker. The picker only appears on first run / when no
  workspace exists.

Settings (General tab):
- Top bar (switch) — when off, removes the top bar entirely and
  relocates its buttons: dock toggle, settings cog, "+", and window
  controls move into a new sidebar header (with an hexpand spacer
  between the left group and the right group). When the sidebar is
  collapsed, the dock toggle parks on the active workspace's leading
  pane via a new `leading_box` slot on every pane; the rest of the
  controls stay hidden.
- Window controls side (Left/Right) — moves minimize/maximize/close
  between the two ends of whichever header is active. Applies live.
- Workspace indicators on the top bar (switch) — hides the per-
  workspace pills without hiding the top bar. Implementation hides
  each pill individually so `indicator_box` keeps its hexpand role
  between the left group and the window controls.
- All three persist under `interface.*` in settings.json.

Split ratio fix:
- shrink_start_child / shrink_end_child on every Paned so the saved
  ratio wins over larger child minimums (e.g. wide tab strips).
- position-notify tracks `last_size`; width-changed notifies are
  auto-adjusts (skip ratio update), same-width notifies are real user
  drags (update ratio). Without this, sidebar toggles and window
  resizes silently drifted the ratio because GtkPaned's position is
  absolute pixels.
- Per-frame tick callback observes the paned's actual width and re-
  applies `position = ratio × new_width` on size changes. GtkWidget's
  width/height properties don't reliably emit notify across GTK 4.x
  versions, so polling is intentional; the check is O(1).
- Startup uses a one-shot tick callback to apply the ratio once the
  paned first has a non-zero allocation.

Structural:
- Extracted handle_config_change() to dedupe appearance + top-bar
  side-effect + save/revert logic that ran from two config-change
  sites.
- apply_top_bar_mode() is the single source of truth for how the
  dock toggle, settings cog, +, indicator pills, and window controls
  are laid out. Respects both the persistent `show_top_bar` setting
  and the transient keyboard toggle.
- Dropped the unused on_config_changed field from PaneCallbacks; the
  pane no longer opens the Settings dialog.
- Widget tree walkers (is_pane_widget, find_leaf_focused_pane) unwrap
  gtk::WindowHandle so the dock-parked-on-pane case still resolves
  panes correctly.

Defaults: `show_top_bar = true`, `show_workspace_indicators = true`,
`window_controls_side = Right`. Existing users without these fields
in settings.json get the familiar layout.

Closes #5 (notifications explicitly out of scope per the issue).
…, jitter probe

- apply_top_bar_mode: group resolved widgets into TopBarWidgets and split the
  three layout branches into layout_top_bar_visible / layout_sidebar_header /
  layout_collapsed_dock helpers (behavior unchanged).
- Collapsed-sidebar layout retries once on idle when the active workspace
  leading pane is momentarily unavailable during a rebuild, so the dock toggle
  no longer briefly disappears.
- split_tree resize tick logs a debug-only line per ratio re-apply to surface
  any size oscillation on deep split trees; compiled out in release.
- split_tree: keep shrink_start/end_child enabled (the builder set them, then
  set_shrink_*_child(false) immediately undid it), so a saved 50/50 ratio is
  honored instead of losing to the wider pane's minimum width.
- split_tree: stop position-notify from consuming width changes by writing
  last_size; only the tick callback updates it, so the tick still re-applies
  position = ratio * width after a resize instead of drifting.
…ls guard

- The workspace drag-and-drop drop target (tab drag -> new workspace,
  workspace drag -> delete) lived only on the hidden sidebar '+' button, so it
  was unreachable while the top bar was shown. Extract install_workspace_dnd_target
  and wire it onto the visible top-bar '+' too.
- Restore an xdg-decoration guard: on Wayland compositors that provide
  server-side decorations, hide the custom minimize/maximize/close so they
  don't stack on top of the compositor's window controls.
@mvbmir
mvbmir force-pushed the mvbmir/redesign-top-bar-clean branch from 603e33d to 53bc197 Compare August 7, 2026 20:46
@mvbmir

mvbmir commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@bvolpato rebased onto latest main and fixed all four blockers

  • split_tree.rs shrink flags: kept shrink_start/end_child enabled, the set_shrink_*_child(false) right after was undoing it, so a saved 50/50 ratio now holds instead of losing to the wider pane's minimum
  • split_tree.rs position-notify: it no longer writes last_size, so the per-frame tick still re-applies position = ratio * width after a resize (no more resize-driven drift)
  • top-bar + drag/drop: extracted install_workspace_dnd_target and wired it onto the visible top-bar button, so tab-drag-into-new-workspace and workspace drag-delete work again (they were stuck on the hidden sidebar button)
  • xdg-decoration: restored the guard, on Wayland compositors that provide server-side decorations the custom min/max/close are hidden so they don't stack on the compositor's controls

retained the newer interface-scale / show-workspace-path / tab-rename / etc from main, ui_scale + path visibility now live-apply through handle_config_change since settings moved to the top bar. gate green (fmt + clippy -D warnings + tests)

mind taking another look when you get a sec?

@mvbmir

mvbmir commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

@bvolpato bumping, all 4 blockers fixed + rebased onto latest main, CI green, ready for another look when you get a sec

@am-will

am-will commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Got any screenshots?

@bvolpato bvolpato left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Aug 7 update fixes all four previous blockers: split shrink flags, resize-ratio handling, top-bar workspace DnD, and the decoration guard. Exact-head quality and GTK smoke checks pass. Two current P1s remain:

  1. window.rs sync_top_bar_visibility() uses only transient top_bar_visible plus fullscreen state. It ignores config.interface.show_top_bar. With the Top bar setting disabled, leaving fullscreen can make the WindowHandle visible again without restoring its reparented controls, producing a blank or incorrect top bar. Include persisted visibility or rerun apply_top_bar_mode() on fullscreen transitions.

  2. split_tree.rs persistent tick callback is owned by paned but captures paned.clone() as paned_for_resize. That creates a strong self-cycle and leaks every old GtkPaned after split-tree rebuilds. Use the callback paned argument at apply_ratio_value(), or capture a weak reference.

Nonblocking: the three new settings controls do not resync from the effective config after save failure, so widgets can display rejected values after state and layout roll back.

After these fixes, rebase or adapt the decoration path after #142 because both branches currently conflict in window.rs. Thanks for addressing the previous review thoroughly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AUR package available: limux-bin

3 participants