145 lines
3.9 KiB
Go
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
|
|
}
|