Help us shape the future of Go! Take the 2026 Go Developer Survey

listsel

package
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 17, 2026 License: Apache-2.0 Imports: 1 Imported by: 0

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

Examples

Constants

View Source
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

View Source
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.

func Text

func Text[T any](items []T, format func(T) string) string

Text formats items one per line, as the clipboard takes copied lines: format gives a line (without a newline). Several lines each end in a newline, as a file's do; one line has none, to paste into a sentence or a search field.

Types

type Intent

type Intent int

Intent is what a gesture asks the app to do besides selecting.

const (
	// None: nothing to do; redraw the selection.
	None Intent = iota
	// CopyRow: copy the row clicked (a plain click), then show that it
	// was copied.
	CopyRow
)

type Model

type Model[K comparable] struct {
	// contains filtered or unexported fields
}

Model is a selection of rows by key.

func (*Model[K]) Anchor

func (m *Model[K]) Anchor() (K, bool)

Anchor returns the row a Shift-click extends from, if any.

func (*Model[K]) Cancel

func (m *Model[K]) Cancel() bool

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

func (m *Model[K]) Click(k K) Intent

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

func (m *Model[K]) ClickWith(k K, mods Mods, order []K) Intent

ClickWith is a click on row k with mods: Extend wins over Toggle, as in Finder; no modifier is Click.

func (*Model[K]) Count

func (m *Model[K]) Count() int

Count returns how many rows are selected.

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

func (m *Model[K]) DragStart(k K, add bool)

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

func (m *Model[K]) Dragging() bool

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]) Empty

func (m *Model[K]) Empty() bool

Empty reports whether no row is selected.

func (*Model[K]) IsSelected

func (m *Model[K]) IsSelected(k K) bool

IsSelected reports whether the row k is selected.

func (*Model[K]) Move

func (m *Model[K]) Move(x, y float32, over K, ok bool, order []K) bool

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

func (m *Model[K]) Press(k K, x, y float32, mods Mods)

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

func (m *Model[K]) Pressed() bool

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

func (m *Model[K]) Release(order []K) (Intent, K)

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.

type Mods

type Mods uint8

Mods are the modifier keys held as a row was pressed, as the UI maps them: Extend is Shift; Toggle is ⌘ (Command) on macOS and Ctrl elsewhere. Control-click on macOS is a secondary click (the context menu) and never reaches the Model.

const (
	Extend Mods = 1 << iota
	Toggle
)

type Wording

type Wording struct{ EN, ZH string }

Wording is a reference text in English (the default) and Simplified Chinese, with a printf verb (%d) where the number of rows goes. actnowkit does no i18n: apps copy these into their own catalogues.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL