Documentation
¶
Overview ¶
Package listsel is the row selection of the ActNow apps' log views (and any list of lines to copy): which rows are selected, the anchor a Shift-click extends from, and the pointer gesture that tells a click from a drag. It has no UI: the app reports presses, moves and releases with the key of the row under the pointer and draws what the Model says.
The rules, the same in every app:
- A plain click (no modifier, no drag) copies its row (Intent CopyRow), clears the selection and makes the row the anchor, without showing it selected.
- Shift-click selects the rows from the anchor to the row clicked, in display order, added to what was selected before the anchor was set.
- Toggle-click (⌘ on macOS, Ctrl elsewhere) adds the row or takes it out, and makes it the anchor.
- Pressing and moving the pointer more than DragSlop before releasing selects the rows from the one pressed to the one under the pointer, up or down; with Toggle held it adds them to the selection.
- SelectAll (⌘A) selects every row shown, Clear (Esc, and after a copy) empties the selection and keeps the anchor.
Rows are named by stable keys (a log line's sequence number, an id), not by their place in the list: new lines and other filters do not move the selection to other rows. Functions taking order want the keys of the rows shown, in display order; Prune drops the keys no longer shown.
A Model is not safe for concurrent use: keep it with the view's state, on the UI thread. Its zero value is an empty selection.
Example ¶
A log view: the pointer's events come with the row under it; a plain click copies its line, a drag selects, ⌘C copies the selection.
package main
import (
"fmt"
"github.com/hehehai/actnowkit/listsel"
)
type logLine struct {
Seq uint64 // stable: the log's sequence number
Msg string
}
// A log view: the pointer's events come with the row under it; a plain
// click copies its line, a drag selects, ⌘C copies the selection.
func main() {
lines := []logLine{{10, "start"}, {11, "load model"}, {12, "ready"}, {13, "dictation"}}
order := make([]uint64, len(lines))
for i, l := range lines {
order[i] = l.Seq
}
var sel listsel.Model[uint64]
// A click on "load model".
sel.Press(11, 5, 20, 0)
if in, k := sel.Release(order); in == listsel.CopyRow {
fmt.Println("copy row", k)
}
// A drag from "ready" up to "start".
sel.Press(12, 5, 40, 0)
sel.Move(5, 30, 11, true, order)
sel.Move(5, 5, 10, true, order)
sel.Release(order)
fmt.Println("selected", sel.Count(), sel.Selected(order))
// ⌘C: copy them, then clear.
picked := listsel.Items(&sel, lines, func(l logLine) uint64 { return l.Seq })
fmt.Print(listsel.Text(picked, func(l logLine) string { return l.Msg }))
sel.Clear()
fmt.Println("after copy", sel.Count())
}
Output: copy row 11 selected 3 [10 11 12] start load model ready after copy 0
Index ¶
- Constants
- Variables
- func Items[K comparable, T any](m *Model[K], items []T, key func(T) K) []T
- func Text[T any](items []T, format func(T) string) string
- type Intent
- type Model
- func (m *Model[K]) Anchor() (K, bool)
- func (m *Model[K]) Cancel() bool
- func (m *Model[K]) Clear()
- func (m *Model[K]) Click(k K) Intent
- func (m *Model[K]) ClickWith(k K, mods Mods, order []K) Intent
- func (m *Model[K]) Count() int
- func (m *Model[K]) DragEnd()
- func (m *Model[K]) DragOver(k K, order []K)
- func (m *Model[K]) DragStart(k K, add bool)
- func (m *Model[K]) Dragging() bool
- func (m *Model[K]) Empty() bool
- func (m *Model[K]) IsSelected(k K) bool
- func (m *Model[K]) Move(x, y float32, over K, ok bool, order []K) bool
- func (m *Model[K]) Press(k K, x, y float32, mods Mods)
- func (m *Model[K]) Pressed() bool
- func (m *Model[K]) Prune(visible []K)
- func (m *Model[K]) Release(order []K) (Intent, K)
- func (m *Model[K]) Reset()
- func (m *Model[K]) SelectAll(keys []K)
- func (m *Model[K]) Selected(order []K) []K
- func (m *Model[K]) ShiftClick(k K, order []K)
- func (m *Model[K]) ToggleClick(k K)
- type Mods
- type Wording
Examples ¶
Constants ¶
const DragSlop float32 = 4
DragSlop is how far, in DIPs (points), the pointer moves away from where it pressed before the press is a drag that selects rather than a click that copies.
Variables ¶
var ( // TextSelected: the header while rows are selected. TextSelected = Wording{EN: "%d lines selected", ZH: "已选中 %d 行"} // TextCopy: the header's button that copies the rows selected. TextCopy = Wording{EN: "Copy", ZH: "复制"} // TextDeselect: the header's button that clears the selection. TextDeselect = Wording{EN: "Deselect", ZH: "取消选择"} // TextCopyLine: the context menu's item for the row clicked. TextCopyLine = Wording{EN: "Copy this line", ZH: "复制这一行"} // TextCopySelected: the context menu's item for the rows selected. TextCopySelected = Wording{EN: "Copy %d selected lines", ZH: "复制选中的 %d 行"} // TextCopyShown: the context menu's item (and the header's button // without a selection) for every row shown. TextCopyShown = Wording{EN: "Copy all lines shown", ZH: "复制全部显示的行"} // TextCopied: the toast after copying. TextCopied = Wording{EN: "Copied %d lines", ZH: "已复制 %d 行"} )
The wordings of the log views' selection (notices/log-selection-spec.md in the ActNow project). The English plurals (%d line / %d lines) are the app's catalogue's concern.
Functions ¶
func Items ¶
func Items[K comparable, T any](m *Model[K], items []T, key func(T) K) []T
Items returns the items of items whose key is selected, in their order: the selected lines of the lines shown, to copy.
Types ¶
type Model ¶
type Model[K comparable] struct { // contains filtered or unexported fields }
Model is a selection of rows by key.
func (*Model[K]) Cancel ¶
Cancel gives up the press (Esc while dragging, the pointer lost): a drag puts back the selection it started from. It reports whether there was a press to give up.
func (*Model[K]) Clear ¶
func (m *Model[K]) Clear()
Clear empties the selection (Esc, after a copy) and keeps the anchor.
func (*Model[K]) Click ¶
Click is a plain click on row k: the selection empties, k becomes the anchor, and the app copies the row (CopyRow).
func (*Model[K]) ClickWith ¶
ClickWith is a click on row k with mods: Extend wins over Toggle, as in Finder; no modifier is Click.
func (*Model[K]) DragEnd ¶
func (m *Model[K]) DragEnd()
DragEnd ends a drag; the rows stay selected, and a Shift-click extends from the drag's first row.
func (*Model[K]) DragOver ¶
func (m *Model[K]) DragOver(k K, order []K)
DragOver is the pointer over row k while dragging: the rows from the drag's first row to k are selected, up or down, besides the selection the drag added to.
func (*Model[K]) DragStart ¶
DragStart starts selecting by dragging from row k, which becomes the anchor and is selected; add keeps the selection and adds the rows dragged over to it (⌘ held), else they replace it.
func (*Model[K]) Dragging ¶
Dragging reports whether a drag is selecting rows: the pointer pressed a row and moved more than DragSlop, and has not been released.
func (*Model[K]) IsSelected ¶
IsSelected reports whether the row k is selected.
func (*Model[K]) Move ¶
Move is the pointer at (x, y) while the button is held, over row over when ok (not over a row: outside the list, or between rows). Once it is more than DragSlop away from the press, the press is a drag: from the row pressed to the row under the pointer, the rows are selected (added to the selection with Toggle held). Move reports whether the selection may have changed: the view should draw again.
func (*Model[K]) Press ¶
Press is the primary button going down on row k at (x, y), in any fixed coordinates (DIPs), with mods held. Nothing changes until the pointer moves (Move) or is released (Release).
func (*Model[K]) Pressed ¶
Pressed reports whether a press is in progress (a click or a drag to be).
func (*Model[K]) Prune ¶
func (m *Model[K]) Prune(visible []K)
Prune keeps only the rows of visible, as the filters or the lines shown change: the selection, the anchor and the drag's first row of rows no longer shown are dropped (a drag whose first row went ends).
func (*Model[K]) Release ¶
Release is the button going up. After a drag, the rows stay selected (None). Else it was a click on the row pressed, with the modifiers held as it was pressed (ClickWith): a plain click returns CopyRow and the row to copy.
func (*Model[K]) Reset ¶
func (m *Model[K]) Reset()
Reset forgets everything: the selection, the anchor and any gesture.
func (*Model[K]) SelectAll ¶
func (m *Model[K]) SelectAll(keys []K)
SelectAll selects the rows of keys (⌘A): those shown.
func (*Model[K]) Selected ¶
func (m *Model[K]) Selected(order []K) []K
Selected returns the selected rows of order, in its order: the rows shown, as they are shown. Selected keys missing from order are left out.
func (*Model[K]) ShiftClick ¶
func (m *Model[K]) ShiftClick(k K, order []K)
ShiftClick is a Shift-click on row k: the rows from the anchor to k, in order, are selected, besides what was selected as the anchor was set. Without an anchor shown in order, k alone is selected and becomes the anchor.
func (*Model[K]) ToggleClick ¶
func (m *Model[K]) ToggleClick(k K)
ToggleClick is a ⌘-click on row k: k is added to the selection or taken out of it, and becomes the anchor.