Skip to content

navigation: window buttons, window drag and wheel tab switching on Linux and Windows - #1917

Open
daniarjabagin wants to merge 1 commit into
raphamorim:mainfrom
daniarjabagin:linux-windows-tab-bar-controls
Open

daniarjabagin wants to merge 1 commit into
raphamorim:mainfrom
daniarjabagin:linux-windows-tab-bar-controls

Conversation

@daniarjabagin

@daniarjabagin daniarjabagin commented Sep 1, 2026

Copy link
Copy Markdown

Closes #1916

What

Under window.decorations = "Disabled" the rio-rendered tab strip is the only chrome a Linux or Windows window has, but outside macOS it offered none of a title bar's controls. This adds three opt-in [navigation] keys:

[window]
decorations = "Disabled"

[navigation]
window-buttons = true   # ⎯ ☐ ✕ at the right end of the strip
drag-window = true      # left-drag on empty strip area moves the window
scroll-tabs = true      # mouse wheel over the strip switches tabs

All three default to false. macOS ignores window-buttons (the traffic lights already occupy the strip) and keeps allowing the drag, exactly as before.

hero

How

window-buttons (renderer/island.rs)

  • New WindowButton enum and geometry helpers (window_buttons_width, window_button_hit). Buttons are 36 logical px each, anchored to the right edge — the same anchoring the tab close × uses.
  • tab_strip_layout gains a reserved_right argument; slots and the lone-tab title budget / centring subtract it, so tabs shrink and a floating (dragged) tab never covers the buttons.
  • Drawn with sugarloaf.line / rounded_rect like the existing close ×. Hover backdrop comes from island_fills so it adapts to light and dark backgrounds; close hovers red with a white glyph.
  • Click routing (screen/mod.rs::handle_island_click): the button hit test runs before the tab-slot logic, because the buttons live past the last slot where a press would otherwise start a window drag. The pressed button is stored as pending_window_button and consumed in application.rs, which owns the router / event loop a close needs. Close follows the CloseRequested contract: confirm_quit() when confirm-before-quit is set, otherwise remove the route and exit once none remain.
  • Hover is tracked next to the close-× hover in update_close_button_hover (both are evaluated so neither update is skipped).

drag-window (screen/mod.rs)

  • start_window_drag loses its macOS cfg; on_chrome_press calls it when navigation.allows_window_drag() — always on macOS, opt-in elsewhere. drag_window() is winit's xdg_toplevel.move / _NET_WM_MOVERESIZE / WM_NCLBUTTONDOWN, so no platform code is added.
  • Double-click → toggle maximize was already cross-platform and is unchanged.

scroll-tabs (application.rs, screen/mod.rs)

  • MouseWheel over the visible strip goes to Screen::scroll_tabs_over_strip before terminal scrolling. Line deltas switch per notch; pixel deltas (touchpads) accumulate, one tab per WHEEL_PX_PER_TAB (40 logical px). Switching reuses the same switch_to_next/prev + switch_context_visibility sequence as the palette actions.

Config (rio-backend/src/config/navigation.rs)

  • Three bool fields with serde defaults; draws_window_buttons() / allows_window_drag() encode the platform rules in one place.

Tests

  • tab_strip_layout_reserves_window_buttons — slots shrink by the reserved width, tab region ends before the buttons, negative reservation is ignored.
  • window_button_hit_maps_thirds_left_to_right — hit test per button, right margin and disabled state.
  • single_title_stays_clear_of_window_buttons — lone-tab title never runs under the buttons; budget shrinks by the reservation.
  • Existing layout / title tests updated for the new argument.

Locally: cargo fmt -- --check, cargo clippy --all-targets --all-features (no warnings in touched files), cargo test --features wgpu (all green).

Tested on

  • Linux · Wayland (GNOME 50, Vulkan backend): buttons, hover, minimize / maximize / close, drag, double-click maximize, wheel switching, tab drag-reorder and close × unaffected, single-tab title clear of the buttons.
  • Linux · X11 (XWayland): same as above.
  • Windows: builds in CI (msys2-build-test, windows-latest); drag_window() / set_minimized / set_maximized are winit's existing Windows paths. Not hand-tested — happy to adjust if someone can try it.
  • macOS: behaviour unchanged (window-buttons is a no-op, drag stays always-on); verified by reading the cfg paths, no hardware to test.

Docs

Companion PR to rioterm.com: raphamorim/rioterm.com#3

controls.mp4
single-tab before-after

…nux and Windows

Under window.decorations = "Disabled" the rio-rendered tab strip is the
only chrome a Linux or Windows window has, but outside macOS it offered
none of a title bar's controls: no window buttons, and no way to move
the window by its strip. Three opt-in [navigation] keys close that gap.

window-buttons draws minimize / maximize / close at the right end of the
strip, with a hover backdrop (red for close, as every desktop that draws
its own buttons does). Tab slots and the lone-tab title shrink to make
room, so a floating (dragged) tab never covers the buttons. Close follows
the CloseRequested contract, so confirm-before-quit still applies.

drag-window lets a left press on empty strip area start a window move
through drag_window(), the path macOS already used; the cfg gate around
it is replaced by the option. Double-click keeps toggling maximize.

scroll-tabs switches tabs on wheel travel over the strip. Touchpad pixel
deltas accumulate, one tab per 40 logical px.

All three default to false. macOS ignores window-buttons (the traffic
lights already occupy the strip) and keeps allowing the drag, as before.
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.

Linux/Windows: window controls on the tab strip when decorations are disabled

1 participant