namespace Bliku.Tui.Cursor
/-- Scroll/selection state for list-like UIs. -/
structure State where
selected : Nat := 0
scroll : Nat := 0
totalRows : Nat := 0
viewportRows : Nat := 20
margin : Nat := 3
deriving Inhabited, Repr
def autoScrollRow
(selectedRow scrollRow totalRows : Nat)
(viewportRows : Nat := 20)
(margin : Nat := 3) : Nat :=
if totalRows == 0 || viewportRows == 0 then
0
else
let maxScroll := totalRows - 1
let lowBound := scrollRow + margin
let highBound := scrollRow + (viewportRows - 1 - min margin (viewportRows - 1))
if selectedRow < lowBound then
min (selectedRow - margin) maxScroll
else if selectedRow > highBound then
let wanted := selectedRow - (viewportRows - 1 - min margin (viewportRows - 1))
min wanted maxScroll
else
min scrollRow maxScroll
def State.clamp (s : State) : State :=
let maxIdx := if s.totalRows == 0 then 0 else s.totalRows - 1
let selected := min s.selected maxIdx
let scroll := autoScrollRow selected s.scroll s.totalRows s.viewportRows s.margin
{ s with selected := selected, scroll := scroll }
def State.moveDown (s : State) : State :=
let maxIdx := if s.totalRows == 0 then 0 else s.totalRows - 1
{ s with selected := min (s.selected + 1) maxIdx } |>.clamp
def State.moveUp (s : State) : State :=
{ s with selected := if s.selected > 0 then s.selected - 1 else 0 } |>.clamp
def State.moveTop (s : State) : State :=
{ s with selected := 0 } |>.clamp
def State.moveBottom (s : State) : State :=
let maxIdx := if s.totalRows == 0 then 0 else s.totalRows - 1
{ s with selected := maxIdx } |>.clamp
end Bliku.Tui.Cursor