ai-agent/internal/tui/mouse.go
admin 8dc496b626
Some checks failed
CI / test (push) Has been cancelled
Release / release (push) Failing after 4m36s
first commit
2026-03-08 15:40:34 +07:00

145 lines
3.9 KiB
Go

package tui
import (
"charm.land/lipgloss/v2"
tea "charm.land/bubbletea/v2"
)
// MouseHandler provides enhanced mouse interaction handling.
type MouseHandler struct {
isDark bool
styles MouseHandlerStyles
resizer *PanelResizer
lastClickX int
lastClickY int
lastClickTime int64
clickCount int
}
// MouseHandlerStyles holds styling.
type MouseHandlerStyles struct {
Hover lipgloss.Style
Selected lipgloss.Style
ResizeHint lipgloss.Style
}
// DefaultMouseHandlerStyles returns default styles.
func DefaultMouseHandlerStyles(isDark bool) MouseHandlerStyles {
if isDark {
return MouseHandlerStyles{
Hover: lipgloss.NewStyle().Background(lipgloss.Color("#3b4252")),
Selected: lipgloss.NewStyle().Background(lipgloss.Color("#4c566a")),
ResizeHint: lipgloss.NewStyle().Foreground(lipgloss.Color("#88c0d0")),
}
}
return MouseHandlerStyles{
Hover: lipgloss.NewStyle().Background(lipgloss.Color("#e5e9f0")),
Selected: lipgloss.NewStyle().Background(lipgloss.Color("#d8dee9")),
ResizeHint: lipgloss.NewStyle().Foreground(lipgloss.Color("#4f8f8f")),
}
}
// NewMouseHandler creates a new mouse handler.
func NewMouseHandler(isDark bool, panelMinWidth, panelMaxWidth int) *MouseHandler {
return &MouseHandler{
isDark: isDark,
styles: DefaultMouseHandlerStyles(isDark),
resizer: NewPanelResizer(panelMinWidth, panelMaxWidth, isDark),
}
}
// SetDark updates theme.
func (mh *MouseHandler) SetDark(isDark bool) {
mh.isDark = isDark
mh.styles = DefaultMouseHandlerStyles(isDark)
}
// ResizePanel handles resize operations.
func (mh *MouseHandler) ResizePanel() *PanelResizer {
return mh.resizer
}
// HandleClick processes a mouse click at the given coordinates.
// Returns an action describing what happened.
func (mh *MouseHandler) HandleClick(msg tea.MouseClickMsg, panelWidth, panelDividerX int) MouseAction {
x, y := int(msg.X), int(msg.Y)
// Check for double-click
isDoubleClick := mh.isDoubleClick(x, y)
if isDoubleClick {
mh.clickCount++
} else {
mh.clickCount = 1
}
mh.lastClickX = x
mh.lastClickY = y
// Check if clicking on resize handle (within 3 chars of panel divider)
if panelWidth > 0 && mh.resizer.CanResizeAt(x, panelDividerX) {
if msg.Button == tea.MouseLeft {
mh.resizer.StartResize(x, panelWidth)
return MouseAction{Type: ResizeStart}
}
}
// Check for right-click (context menu)
if msg.Button == tea.MouseRight {
return MouseAction{
Type: ContextMenu,
X: x,
Y: y,
Context: mh.getClickContext(x, y, panelWidth),
}
}
return MouseAction{Type: None}
}
// HandleRelease handles mouse release events.
func (mh *MouseHandler) HandleRelease() {
mh.resizer.EndResize()
}
// isDoubleClick checks if this is a double-click.
func (mh *MouseHandler) isDoubleClick(x, y int) bool {
// Simple double-click detection: same position
dist := abs(x-mh.lastClickX) + abs(y-mh.lastClickY)
return dist < 2 && mh.clickCount > 1
}
// getClickContext returns context information about the click location.
func (mh *MouseHandler) getClickContext(x, y, panelWidth int) string {
// Determine what was clicked based on coordinates
if panelWidth > 0 && x < panelWidth {
return "sidepanel"
}
return "main"
}
// MouseAction describes a mouse action.
type MouseAction struct {
Type MouseActionType
X, Y int
Context string
}
// MouseActionType describes the type of mouse action.
type MouseActionType int
const (
None MouseActionType = iota
ResizeStart
ContextMenu
SelectEntry
ToggleCollapse
CopyText
)
// abs returns the absolute value.
func abs(n int) int {
if n < 0 {
return -n
}
return n
}