Skip to content

#448-D: GTK ShellApp migration — click-handling logic largely not working (follow-up bugs from #540) #544

Description

@JDonaghy

Follow-up to #540 (#448-C), under parent #448.

The GTK main-loop flip to ShellApp (#540) merged to develop but left several click-handling and rendering regressions. This issue tracks fixing them.

Confirmed broken: tab switching, explorer tree clicks, settings panel clicks, search sidebar clicks, git sidebar clicks — any sidebar panel click. Also: sidebar scrolling, explorer double-click (open file), explorer drag. Explorer tree shows + fallback icons instead of nerd font folder glyphs.


Root Cause Analysis

Bug 1a — Tabs: tab_slot_positions never populated in ShellApp rendering path

File: src/gtk/mod.rsrender_content() (~line 7249)

The old Relm4 path called draw::draw_tab_bar() which recovered slot hit positions via backend.tab_bar_layout() and stored them in self.tab_slot_positions. The new render_content() path draws the tab bar via:

frame.push(Surface::TabBar { rect: tb_rect, bar: &screen.tab_bar_primitive,});
frame.draw(backend);

…but never calls backend.tab_bar_layout() to recover the slot positions. So self.tab_slot_positions stays empty every frame. When you click a tab:

  • screen_zone_hit_test()ScreenZone::TabBar { local_x, … }
  • tab_bar_inner_hit_test()tab_slot_positions.get(group_id)None
  • Falls through without calling engine.goto_tab() → silent no-op

Fix: After frame.draw(backend) for the tab bar in render_content(), call:

let hits = backend.tab_bar_layout(tb_rect, &screen.tab_bar_primitive);

Then populate self.tab_slot_positions from hits. The return type is quadraui::TabBarHits; slot positions are in 0..width (bar-relative) space. The same reshaping logic that draw.rs::draw_tab_bar() does for diff_btn_map, split_btn_map, action_btn_map needs to happen here too.


Bug 1b — Tabs: coordinate mismatch for single-group tab bar

File: src/render.rsscreen_zone_hit_test() (~line 11985)

Multi-group case correctly offsets: local_x: x - b.x (subtracts group's window-absolute x). Single-group case passes local_x: x — raw window-absolute x. But tab slot positions are in 0..width (bar-relative) space.

When the sidebar is visible (e.g., activity bar = 48px, sidebar = 250px → main.x = 298), a click at window-x = 400 gives local_x = 400 but the slot lives at bar-relative x = 102. Every tab miss.

Current single-group code:

return ScreenZone::TabBar {
    group_id: active_group,
    local_x: x,   // ← wrong: window-absolute, not bar-relative
    bar_width,
};

Fix: Use local_x: x - content_x where content_x = the window x at which the content area starts. This is available without adding new parameters: layout.windows.first().map(|w| w.rect.x).unwrap_or(0.0) gives the correct origin (windows are laid out starting from main.x).


Bug 2 — All sidebar panel clicks silently dropped

File: src/gtk/mod.rsShellApp::handle() (~line 7462)

Root cause: In the old Relm4 system, each sidebar panel (explorer, settings, search, git, AI, ext) had a dedicated GTK DrawingArea widget. GDK clicks on those DAs fired specific messages: Msg::ExplorerUiEvent, Msg::ScSidebarEvent, Msg::SearchSidebarEvent, etc.

In ShellApp mode, panels are rendered by quadraui controllers (TreeController, FormController, MultiSectionView) into sidebar_content_bounds via render_content(). The old DA widgets no longer receive GDK events. All mouse events arrive via ShellApp::handle().

Flow for a sidebar click today:

  1. AppShell::handle()AppShellEvent::Ignored (shell only handles activity bar, divider, bottom-panel grip)
  2. ShellApp::handle()self.dispatch(Msg::MouseClick { x, y, width, height, … }) (always — no sidebar check)
  3. handle_mouse_click_msg()pixel_to_click_target()screen_zone_hit_test() → no editor zone matches sidebar area → ScreenZone::None → click dropped

Note: Msg::ExplorerUiEventhandle_explorer_msg()engine.explorer_tree.borrow_mut().handle(&ev, backend, rect) is already the correct backend-neutral path through TreeController. The wiring to reach it is simply absent.

Fix: In ShellApp::handle(), before dispatching Msg::MouseClick, check if position is inside ctx.layout.sidebar_content_bounds. If yes, look at the active panel and dispatch to the appropriate existing handler:

// pseudo-code in ShellApp::handle(), UiEvent::MouseDown branch
if let Some(sb_bounds) = ctx.layout.sidebar_content_bounds {
    if rect_contains(sb_bounds, position.x, position.y) {
        let panel = self.active_sidebar_panel_id();   // from engine.app_shell
        match panel.as_str() {
            PANEL_EXPLORER => self.dispatch(Msg::ExplorerUiEvent(event)),
            PANEL_SEARCH   => self.dispatch(Msg::SearchSidebarEvent(event)),
            PANEL_DEBUG    => self.dispatch(Msg::DebugSidebarEvent(event)),
            PANEL_GIT      => self.dispatch(Msg::ScSidebarEvent(event)),
            PANEL_SETTINGS => self.dispatch(Msg::SettingsUiEvent(event)),
            // PANEL_AI, ext panels …
        }
        self.draw_needed.set(true);
        return Reaction::Redraw;
    }
}
// Fall through → editor click
self.dispatch(Msg::MouseClick {});

The same routing gap applies to:

  • UiEvent::Scroll → dispatched as Msg::MouseScroll (editor-only); sidebar panels can't scroll
  • UiEvent::DoubleClick → dispatched as Msg::MouseDoubleClick (editor-only); explorer double-click to open files doesn't work
  • UiEvent::MouseMoved (left button held) → dispatched as Msg::MouseDrag (editor-only); explorer tree drag/select broken
  • UiEvent::MouseUp → dispatched as Msg::MouseUp (editor-only)

All four need the same sidebar-bounds check + panel dispatch added to ShellApp::handle().


Bug 3 — Explorer shows + fallback icons instead of nerd font folder glyphs

Files: src/gtk/mod.rs render_content(), quadraui Backend trait

Root cause: ShellApp mode has two separate GtkBackend instances:

Instance Owner set_nerd_fonts() called?
self.backend (legacy) App ✅ yes — in Msg::Resize handler
runner backend (backend: &mut dyn Backend in render_content) quadraui runner ❌ never

TreeController.render(backend, q_sb) in render_content() uses the runner's backend with nerd_fonts_enabled = false. GTK tree renderer → icon.fallback = "+" for folders (see src/icons.rs: pub const FOLDER: Icon = Icon::new("\u{f07b}", "+")).

TUI is unaffected: TUI doesn't use ShellApp yet; tui_main/mod.rs calls backend.set_nerd_fonts() at startup on its single backend instance.

Blocker: set_nerd_fonts() is a concrete method on GtkBackend and TuiBackend, not on the Backend trait. So render_content(backend: &mut dyn Backend) cannot call it without a per-backend downcast (Platform-Neutrality violation).

Fix (requires quadraui change): See quadraui#391 — add fn set_nerd_fonts(&mut self, enabled: bool) to the Backend trait with a default no-op. Once that lands, add to render_content():

backend.set_nerd_fonts(engine.settings.use_nerd_fonts);

Do NOT add a per-backend workaround in vimcode — this is a genuine quadraui infrastructure gap.


Files to change

File Change
src/gtk/mod.rsrender_content() After drawing tab bar, call backend.tab_bar_layout() and populate self.tab_slot_positions (Bug 1a). Also add backend.set_nerd_fonts() once quadraui trait is updated (Bug 3).
src/gtk/mod.rsShellApp::handle() Add sidebar-bounds check before editor click dispatch; route to panel-specific message for all four event kinds: MouseDown, Scroll, DoubleClick, MouseMoved, MouseUp (Bug 2).
src/render.rsscreen_zone_hit_test() Single-group tab bar: change local_x: x to local_x: x - content_x (Bug 1b).

Dependencies

  • Bug 3 fix depends on quadraui#391 (Backend::set_nerd_fonts on trait). File that first; Bugs 1 and 2 can land independently.

Exit criterion

  • Click any tab → tab switches
  • Click any explorer tree item → item selects / highlights
  • Double-click file in explorer → file opens in editor
  • Scroll within explorer/settings panel → panel scrolls
  • Click any settings panel item → item responds
  • Nerd font folder icons appear in GTK explorer tree (post quadraui fix)
  • cargo test --no-default-features passes
  • GTK app boots and all above interactions work

Parent: #448 · Predecessor: #540 (#448-C)

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    coordTracked by coord-tui pipelinestatus:readyRefined and ready to enter the work pipeline

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions