sointu/tracker/gioui/draglist.go
2023-07-21 00:39:45 +03:00

262 lines
6.6 KiB
Go

package gioui
import (
"image"
"image/color"
"gioui.org/io/key"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/widget/material"
)
type DragList struct {
SelectedItem int
SelectedItem2 int
HoverItem int
List *layout.List
drag bool
dragID pointer.ID
tags []bool
swapped bool
focused bool
requestFocus bool
mainTag bool
}
type FilledDragListStyle struct {
dragList *DragList
HoverColor color.NRGBA
SelectedColor color.NRGBA
CursorColor color.NRGBA
Count int
element func(gtx C, i int) D
swap func(i, j int)
}
func FilledDragList(th *material.Theme, dragList *DragList, count int, element func(gtx C, i int) D, swap func(i, j int)) FilledDragListStyle {
return FilledDragListStyle{
dragList: dragList,
element: element,
swap: swap,
Count: count,
HoverColor: dragListHoverColor,
SelectedColor: dragListSelectedColor,
CursorColor: cursorColor,
}
}
func (d *DragList) Focus() {
d.requestFocus = true
}
func (d *DragList) Focused() bool {
return d.focused
}
func (s *FilledDragListStyle) Layout(gtx C) D {
swap := 0
defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
defer clip.Rect(image.Rect(0, 0, gtx.Constraints.Max.X, gtx.Constraints.Max.Y)).Push(gtx.Ops).Pop()
keys := key.Set("↑|↓|Ctrl-↑|Ctrl-↓|Shift-↑|Shift-↓")
if s.dragList.List.Axis == layout.Horizontal {
keys = key.Set("←|→|Ctrl-←|Ctrl-→|Shift-←|Shift-→")
}
key.InputOp{Tag: &s.dragList.mainTag, Keys: keys}.Add(gtx.Ops)
if s.dragList.List.Axis == layout.Horizontal {
gtx.Constraints.Min.X = gtx.Constraints.Max.X
} else {
gtx.Constraints.Min.Y = gtx.Constraints.Max.Y
}
if s.dragList.requestFocus {
s.dragList.requestFocus = false
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
}
if !s.dragList.focused {
s.dragList.SelectedItem2 = s.dragList.SelectedItem
}
for _, ke := range gtx.Events(&s.dragList.mainTag) {
switch ke := ke.(type) {
case key.FocusEvent:
s.dragList.focused = ke.Focus
case key.Event:
if !s.dragList.focused || ke.State != key.Press {
break
}
delta := 0
switch {
case s.dragList.List.Axis == layout.Horizontal && ke.Name == key.NameLeftArrow && s.dragList.SelectedItem > 0:
delta = -1
case s.dragList.List.Axis == layout.Horizontal && ke.Name == key.NameRightArrow && s.dragList.SelectedItem < s.Count-1:
delta = 1
case s.dragList.List.Axis == layout.Vertical && ke.Name == key.NameUpArrow && s.dragList.SelectedItem > 0:
delta = -1
case s.dragList.List.Axis == layout.Vertical && ke.Name == key.NameDownArrow && s.dragList.SelectedItem < s.Count-1:
delta = 1
}
if delta != 0 {
if ke.Modifiers.Contain(key.ModShortcut) {
swap = delta
} else {
s.dragList.SelectedItem += delta
if !ke.Modifiers.Contain(key.ModShift) {
s.dragList.SelectedItem2 = s.dragList.SelectedItem
}
}
}
}
}
listElem := func(gtx C, index int) D {
for len(s.dragList.tags) <= index {
s.dragList.tags = append(s.dragList.tags, false)
}
bg := func(gtx C) D {
var color color.NRGBA
if s.dragList.SelectedItem == index {
if s.dragList.focused {
color = s.CursorColor
} else {
color = s.SelectedColor
}
} else if between(s.dragList.SelectedItem, index, s.dragList.SelectedItem2) {
color = s.SelectedColor
} else if s.dragList.HoverItem == index {
color = s.HoverColor
}
paint.FillShape(gtx.Ops, color, clip.Rect{Max: image.Pt(gtx.Constraints.Min.X, gtx.Constraints.Min.Y)}.Op())
return D{Size: gtx.Constraints.Min}
}
inputFg := func(gtx C) D {
//defer op.Offset(image.Point{}).Push(gtx.Ops).Pop()
for _, ev := range gtx.Events(&s.dragList.tags[index]) {
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Enter:
s.dragList.HoverItem = index
case pointer.Leave:
if s.dragList.HoverItem == index {
s.dragList.HoverItem = -1
}
case pointer.Press:
if s.dragList.drag {
break
}
s.dragList.SelectedItem = index
if !e.Modifiers.Contain(key.ModShift) {
s.dragList.SelectedItem2 = index
}
key.FocusOp{Tag: &s.dragList.mainTag}.Add(gtx.Ops)
}
}
rect := image.Rect(0, 0, gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
area := clip.Rect(rect).Push(gtx.Ops)
pointer.InputOp{Tag: &s.dragList.tags[index],
Types: pointer.Press | pointer.Enter | pointer.Leave,
}.Add(gtx.Ops)
area.Pop()
if index == s.dragList.SelectedItem {
for _, ev := range gtx.Events(&s.dragList.focused) {
e, ok := ev.(pointer.Event)
if !ok {
continue
}
switch e.Type {
case pointer.Press:
s.dragList.dragID = e.PointerID
s.dragList.drag = true
case pointer.Drag:
if s.dragList.dragID != e.PointerID {
break
}
if s.dragList.List.Axis == layout.Horizontal {
if e.Position.X < 0 {
swap = -1
}
if e.Position.X > float32(gtx.Constraints.Min.X) {
swap = 1
}
} else {
if e.Position.Y < 0 {
swap = -1
}
if e.Position.Y > float32(gtx.Constraints.Min.Y) {
swap = 1
}
}
case pointer.Release:
fallthrough
case pointer.Cancel:
s.dragList.drag = false
}
}
area := clip.Rect(rect).Push(gtx.Ops)
pointer.InputOp{Tag: &s.dragList.focused,
Types: pointer.Drag | pointer.Press | pointer.Release,
Grab: s.dragList.drag,
}.Add(gtx.Ops)
pointer.CursorGrab.Add(gtx.Ops)
area.Pop()
}
return layout.Dimensions{Size: gtx.Constraints.Min}
}
return layout.Stack{Alignment: layout.W}.Layout(gtx,
layout.Expanded(bg),
layout.Expanded(inputFg),
layout.Stacked(func(gtx C) D {
return s.element(gtx, index)
}),
)
}
dims := s.dragList.List.Layout(gtx, s.Count, listElem)
a := intMin(s.dragList.SelectedItem, s.dragList.SelectedItem2)
b := intMax(s.dragList.SelectedItem, s.dragList.SelectedItem2)
if !s.dragList.swapped && swap != 0 && a+swap >= 0 && b+swap < s.Count {
if swap < 0 {
for i := a; i <= b; i++ {
s.swap(i, i+swap)
}
} else {
for i := b; i >= a; i-- {
s.swap(i, i+swap)
}
}
s.dragList.SelectedItem += swap
s.dragList.SelectedItem2 += swap
s.dragList.swapped = true
} else {
s.dragList.swapped = false
}
return dims
}
func between(a, b, c int) bool {
return (a <= b && b <= c) || (c <= b && b <= a)
}
func intMax(a, b int) int {
if a > b {
return a
}
return b
}
func intMin(a, b int) int {
if a < b {
return a
}
return b
}