# How Superfile Implements Mouse Wheel Support for File Navigation

> Learn how Superfile implements mouse wheel support for file navigation using Bubble Tea, intercepting tea.MouseMsg for smooth scrolling across focused panels.

- Repository: [Yorukot/superfile](https://github.com/yorukot/superfile)
- Tags: internals
- Published: 2026-07-28

---

**Superfile handles mouse wheel events through the Bubble Tea framework by intercepting `tea.MouseMsg` in [`src/internal/model.go`](https://github.com/yorukot/superfile/blob/main/src/internal/model.go) and delegating to `wheelMainAction` in [`src/internal/wheel_function.go`](https://github.com/yorukot/superfile/blob/main/src/internal/wheel_function.go), which repeats scroll commands five times per wheel tick for smooth navigation across focused panels.**

Superfile is a modern terminal file manager built with the Bubble Tea TUI framework. Understanding how it implements mouse wheel support for file navigation reveals elegant patterns for handling input events in Go-based terminal applications. The implementation spreads across three key files that handle event detection, action dispatch, and scrollSmoothing configuration.

## The Bubble Tea Message Loop Architecture

Superfile leverages the Bubble Tea framework's **message-based architecture** to capture peripheral input. When a user scrolls their mouse wheel inside the terminal window, Bubble Tea translates this into a `tea.MouseMsg` struct that flows through the application's update loop.

### Intercepting Mouse Events in model.go

The entry point for all mouse interaction resides in [`src/internal/model.go`](https://github.com/yorukot/superfile/blob/main/src/internal/model.go). The `handleMouseMsg` method inspects the string representation of the mouse message to determine if the event constitutes a wheel action:

```go
// src/internal/model.go
func (m *model) handleMouseMsg(msg tea.MouseMsg) {
    msgStr := msg.String()
    if msgStr == "wheelup" || msgStr == "wheeldown" {
        wheelMainAction(msgStr, m)
    }
}

```

This conditional check specifically filters for `"wheelup"` and `"wheeldown"` strings, ignoring other mouse events like clicks or movements. Once identified, the code immediately delegates to the wheel handling specialist function.

## Delegating Scroll Actions to wheel_function.go

The core navigation logic lives in [`src/internal/wheel_function.go`](https://github.com/yorukot/superfile/blob/main/src/internal/wheel_function.go) within the `wheelMainAction` function. This implementation uses **panel-aware routing** to ensure scrolling affects only the currently focused interface element.

### Panel-Aware Navigation Logic

Superfile maintains distinct focus states through the `m.focusPanel` variable. The `wheelMainAction` function uses a switch statement to determine which model receives the scroll command:

- **sidebarFocus**: Scrolls the directory tree via `m.sidebarModel.ListUp()` or `ListDown()`
- **processBarFocus**: Moves through active file operations via `m.processBarModel.ListUp()` or `ListDown()`
- **metadataFocus**: Navigates file metadata via `m.fileMetaData.ListUp()` or `ListDown()`
- **nonePanelFocus** (default): Scrolls the main file panel via `m.getFocusedFilePanel().ListUp()` or `ListDown()`

```go
// src/internal/wheel_function.go
func wheelMainAction(msg string, m *model) {
    var action func()
    switch msg {
    case "wheelup":
        switch m.focusPanel {
        case sidebarFocus:
            action = func() { m.sidebarModel.ListUp() }
        case processBarFocus:
            action = func() { m.processBarModel.ListUp() }
        case metadataFocus:
            action = func() { m.fileMetaData.ListUp() }
        case nonePanelFocus:
            action = func() { m.getFocusedFilePanel().ListUp() }
        }
    case "wheeldown":
        // ... corresponding ListDown() calls for each focus state
    }
    
    for range common.WheelRunTime {
        action()
    }
}

```

## Implementing Smooth Scrolling with WheelRunTime

Raw terminal mouse events often translate to single-line movements that feel jerky to users. Superfile solves this through **repeated action emission** controlled by the `WheelRunTime` constant.

In [`src/internal/common/predefined_variable.go`](https://github.com/yorukot/superfile/blob/main/src/internal/common/predefined_variable.go), the system defines:

```go
// src/internal/common/predefined_variable.go
const (
    WheelRunTime = 5 // number of repeat actions per wheel event
)

```

The `wheelMainAction` function executes the selected `ListUp` or `ListDown` method inside a `for range common.WheelRunTime` loop. This fires the movement command five times per wheel tick, creating the perception of smooth acceleration through file lists without requiring complex animation frameworks.

## Complete Code Implementation

The following implementation details show how these components integrate into the superfile source code:

**Event Detection Layer ([`src/internal/model.go`](https://github.com/yorukot/superfile/blob/main/src/internal/model.go)):**

```go
func (m *model) handleMouseMsg(msg tea.MouseMsg) {
    msgStr := msg.String()
    if msgStr == "wheelup" || msgStr == "wheeldown" {
        wheelMainAction(msgStr, m)
    }
}

```

**Action Dispatch Layer ([`src/internal/wheel_function.go`](https://github.com/yorukot/superfile/blob/main/src/internal/wheel_function.go)):**

```go
func wheelMainAction(msg string, m *model) {
    var action func()
    switch msg {
    case "wheelup":
        switch m.focusPanel {
        case sidebarFocus:
            action = func() { m.sidebarModel.ListUp() }
        case processBarFocus:
            action = func() { m.processBarModel.ListUp() }
        case metadataFocus:
            action = func() { m.fileMetaData.ListUp() }
        case nonePanelFocus:
            action = func() { m.getFocusedFilePanel().ListUp() }
        }
    case "wheeldown":
        switch m.focusPanel {
        case sidebarFocus:
            action = func() { m.sidebarModel.ListDown() }
        case processBarFocus:
            action = func() { m.processBarModel.ListDown() }
        case metadataFocus:
            action = func() { m.fileMetaData.ListDown() }
        case nonePanelFocus:
            action = func() { m.getFocusedFilePanel().ListDown() }
        }
    }
    
    for range common.WheelRunTime {
        action()
    }
}

```

**Configuration Layer ([`src/internal/common/predefined_variable.go`](https://github.com/yorukot/superfile/blob/main/src/internal/common/predefined_variable.go)):**

```go
const (
    WheelRunTime = 5
)

```

## Summary

- **Event Interception**: Superfile captures mouse wheel events through Bubble Tea's `tea.MouseMsg` in [`src/internal/model.go`](https://github.com/yorukot/superfile/blob/main/src/internal/model.go), checking for `"wheelup"` and `"wheeldown"` message strings.
- **Panel Routing**: The `wheelMainAction` function in [`src/internal/wheel_function.go`](https://github.com/yorukot/superfile/blob/main/src/internal/wheel_function.go) routes scroll commands to the appropriate panel model based on the current `focusPanel` state.
- **Smooth Motion**: Each wheel tick triggers five repeated scroll actions via the `WheelRunTime` constant, defined in [`src/internal/common/predefined_variable.go`](https://github.com/yorukot/superfile/blob/main/src/internal/common/predefined_variable.go).
- **Unified Interface**: All scrollable components implement `ListUp()` and `ListDown()` methods, allowing consistent wheel behavior across the sidebar, process bar, metadata panel, and main file browser.

## Frequently Asked Questions

### How does Superfile detect mouse wheel events in the terminal?

Superfile relies on the Bubble Tea framework's message loop to capture terminal mouse events. When the user scrolls, the terminal emulator sends escape sequences that Bubble Tea parses into `tea.MouseMsg` structs. The application checks `msg.String()` for the literal strings `"wheelup"` or `"wheeldown"` in [`src/internal/model.go`](https://github.com/yorukot/superfile/blob/main/src/internal/model.go) to identify wheel actions specifically.

### What determines which panel scrolls when using the mouse wheel?

The `m.focusPanel` variable maintains the current input focus state, which can be `sidebarFocus`, `processBarFocus`, `metadataFocus`, or `nonePanelFocus`. The `wheelMainAction` function uses this state to select which model receives the `ListUp()` or `ListDown()` call, ensuring scrolling affects only the active interface element.

### Why does Superfile repeat scroll actions multiple times per wheel event?

The `WheelRunTime` constant (set to 5) in [`src/internal/common/predefined_variable.go`](https://github.com/yorukot/superfile/blob/main/src/internal/common/predefined_variable.go) controls how many times the scroll command executes per wheel tick. This repetition creates smoother visual movement through file lists, compensating for the discrete nature of terminal-based single-line scrolling without requiring complex animation libraries.

### Can the mouse wheel scroll sensitivity be configured in Superfile?

Currently, scroll sensitivity is hardcoded via the `WheelRunTime` constant (value 5) in the predefined variables file. Users would need to modify this constant in [`src/internal/common/predefined_variable.go`](https://github.com/yorukot/superfile/blob/main/src/internal/common/predefined_variable.go) and recompile the application to change the number of lines scrolled per wheel tick.