~whereswaldon/gio-x

gio-x/outlay/table.go -rw-r--r-- 3.5 KiB
24857b8dChris Waldon component: document tooltip in README 20 hours ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package outlay

import (
	"image"

	"gioui.org/f32"
	"gioui.org/io/event"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/unit"
)

// Cell lays out the Table cell located at column x and row y.
type Cell func(gtx layout.Context, x, y int) layout.Dimensions

// Table lays out cells by their coordinates.
// All cells within a column have the same width, and
// the same height within a row.
type Table struct {
	// CellSize returns the size for the cell located at column x and row y.
	CellSize     func(m unit.Metric, x, y int) image.Point
	xList, yList layout.List
	x, y         int
}

func (t *Table) Layout(gtx layout.Context, xn, yn int, el Cell) layout.Dimensions {
	defer op.Save(gtx.Ops).Load()
	csMax := gtx.Constraints.Max

	// In order to deliver the same scroll events for both lists,
	// they are collected by a dedicated InputOp and dispatched to
	// both lists by a dedicated queue. This ensures that only the
	// lists receive the pointer events that are collected for scrolling
	// and the widgets displayed within cells are not polluted by them.

	// Collect the scrolling events for the Table. See the dedicated InputOp below.
	scrollEvents := gtx.Events(t)
	// For each list, use a copy of the context with a dedicated queue that only returns
	// the scroll events.
	listContext := gtx
	listContext.Queue = queue(scrollEvents)

	t.xList.Axis = layout.Horizontal
	t.xList.Layout(listContext, xn, func(gtx layout.Context, x int) layout.Dimensions {
		sz := t.CellSize(gtx.Metric, x, t.y)
		return layout.Dimensions{Size: image.Point{X: sz.X, Y: csMax.Y}}
	})
	t.x = t.xList.Position.First

	t.yList.Axis = layout.Vertical
	t.yList.Layout(listContext, yn, func(gtx layout.Context, y int) layout.Dimensions {
		sz := t.CellSize(gtx.Metric, t.x, y)
		return layout.Dimensions{Size: image.Point{X: csMax.X, Y: sz.Y}}
	})
	t.y = t.yList.Position.First

	// Grab all scroll events for the lists.
	xMin, xMax := listScrollBounds(t.xList.Position, xn)
	yMin, yMax := listScrollBounds(t.yList.Position, yn)
	pointer.Rect(image.Rectangle{Max: csMax}).Add(gtx.Ops)
	pointer.InputOp{
		Tag:   t,
		Types: pointer.Press | pointer.Drag | pointer.Release | pointer.Scroll,
		ScrollBounds: image.Rectangle{
			Min: image.Pt(xMin, yMin),
			Max: image.Pt(xMax, yMax),
		},
	}.Add(gtx.Ops)

	// Offset the start position for truncated last columns and rows.
	clip.Rect(image.Rectangle{Max: csMax}).Add(gtx.Ops)
	p := image.Point{
		X: -t.xList.Position.Offset,
		Y: -t.yList.Position.Offset,
	}
	op.Offset(layout.FPt(p)).Add(gtx.Ops)

	gtx.Constraints.Min = image.Point{}
	var yy int
	for y := t.y; y < yn; y++ {
		var xx int
		var sz image.Point
		for x := t.x; x < xn; x++ {
			sz = t.CellSize(gtx.Metric, x, y)
			gtx.Constraints.Max = sz
			// For cells, use the supplied context and its queue.
			el(gtx, x, y)
			if xx >= csMax.X {
				yy += sz.Y
				break
			}
			xx += sz.X
			op.Offset(f32.Point{X: float32(sz.X)}).Add(gtx.Ops)
		}
		if yy >= csMax.Y {
			break
		}
		pt := image.Point{X: -xx, Y: sz.Y}
		op.Offset(layout.FPt(pt)).Add(gtx.Ops)
	}
	return layout.Dimensions{Size: csMax}
}

// queue provides an event queue delivering the same events for any tag.
type queue []event.Event

func (q queue) Events(_ event.Tag) []event.Event { return q }

func listScrollBounds(pos layout.Position, n int) (min, max int) {
	const inf = 1e10
	if o := pos.Offset; o > 0 {
		min = -o
	} else if pos.First > 0 {
		min = -inf
	}
	if o := pos.OffsetLast; o < 0 {
		max = -o
	} else if pos.First+pos.Count < n {
		max = inf
	}
	return
}