~whereswaldon/gio-x

gio-x/outlay/fan.go -rw-r--r-- 3.8 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package outlay

import (
	"fmt"
	"math"

	"gioui.org/f32"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/unit"
)

type Fan struct {
	itemsCache        []cacheItem
	last              fanParams
	animatedLastFrame bool
	Animation

	// The width, in radians, of the full arc that items should occupy.
	// If zero, math.Pi/2 will be used (1/4 of a full circle).
	WidthRadians float32

	// The offset, in radians, above the X axis to apply before rendering the
	// arc. This can be used with a value of Pi/4 to center an arc of width
	// Pi/2. If zero, math.Pi/4 will be used (1/8 of a full circle). To get the
	// equivalent of specifying zero, specify a value of 2*math.Pi.
	OffsetRadians float32

	// The radius of the hollow circle at the center of the fan. Leave nil to
	// use the default heuristic of half the width of the widest item.
	HollowRadius *unit.Value
}

type fanParams struct {
	arc    float32
	radius float32
	len    int
}

func (f fanParams) String() string {
	return fmt.Sprintf("arc: %v radus: %v len: %v", f.arc, f.radius, f.len)
}

type cacheItem struct {
	elevated bool
	op.CallOp
	layout.Dimensions
}

type FanItem struct {
	W       layout.Widget
	Elevate bool
}

func Item(evelvate bool, w layout.Widget) FanItem {
	return FanItem{
		W:       w,
		Elevate: evelvate,
	}
}

func (f *Fan) fullWidthRadians() float32 {
	if f.WidthRadians == 0 {
		return math.Pi / 2
	}
	return f.WidthRadians
}

func (f *Fan) offsetRadians() float32 {
	if f.OffsetRadians == 0 {
		return math.Pi / 4
	}
	return f.OffsetRadians
}

func (f *Fan) Layout(gtx layout.Context, items ...FanItem) layout.Dimensions {
	defer op.Save(gtx.Ops).Load()
	op.Offset(f32.Point{
		X: float32(gtx.Constraints.Max.X / 2),
		Y: float32(gtx.Constraints.Max.Y / 2),
	}).Add(gtx.Ops)
	f.itemsCache = f.itemsCache[:0]
	maxWidth := 0
	for i := range items {
		item := items[i]
		macro := op.Record(gtx.Ops)
		dims := item.W(gtx)
		if dims.Size.X > maxWidth {
			maxWidth = dims.Size.X
		}
		f.itemsCache = append(f.itemsCache, cacheItem{
			CallOp:     macro.Stop(),
			Dimensions: dims,
			elevated:   item.Elevate,
		})
	}
	var current fanParams
	current.len = len(items)
	if f.HollowRadius == nil {
		current.radius = float32(maxWidth * 2.0)
	} else {
		current.radius = float32(gtx.Px(*f.HollowRadius))
	}
	var itemArcFraction float32
	if len(items) > 1 {
		itemArcFraction = float32(1) / float32(len(items)-1)
	} else {
		itemArcFraction = 1
	}
	current.arc = f.fullWidthRadians() * itemArcFraction

	var empty fanParams
	if f.last == empty {
		f.last = current
	} else if f.last != current {

		if !f.animatedLastFrame {
			f.Start(gtx.Now)
		}
		progress := f.Progress(gtx)
		if f.animatedLastFrame && progress >= 1 {
			f.last = current
		}
		f.animatedLastFrame = false
		if f.Animating(gtx) {
			f.animatedLastFrame = true
			op.InvalidateOp{}.Add(gtx.Ops)
		}
		current.arc = f.last.arc - (f.last.arc-current.arc)*progress
		current.radius = f.last.radius - (f.last.radius-current.radius)*progress
	}

	visible := f.itemsCache[:min(f.last.len, current.len)]
	for i := range visible {
		if !f.itemsCache[i].elevated {
			f.layoutItem(gtx, i, current)
		}
	}
	for i := range visible {
		if f.itemsCache[i].elevated {
			f.layoutItem(gtx, i, current)
		}
	}
	return layout.Dimensions{
		Size: gtx.Constraints.Max,
	}

}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func (f *Fan) layoutItem(gtx layout.Context, index int, params fanParams) layout.Dimensions {
	defer op.Save(gtx.Ops).Load()
	arc := params.arc
	radius := params.radius
	arc = arc*float32(index) + f.offsetRadians()
	var transform f32.Affine2D
	transform = transform.Rotate(f32.Point{}, -math.Pi/2).
		Offset(f32.Pt(-radius, float32(f.itemsCache[index].Dimensions.Size.X/2))).
		Rotate(f32.Point{}, arc)
	op.Affine(transform).Add(gtx.Ops)
	f.itemsCache[index].Add(gtx.Ops)
	return layout.Dimensions{}
}