~eliasnaur/gio-example

ref: 8a3a65c8c0340253beaba1c2e5687a000fa74789 gio-example/tabs/slider.go -rw-r--r-- 2.4 KiB
8a3a65c8Chris Waldon gio-extras/*,x: rename gio-extras demos and change imports 9 months 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
// SPDX-License-Identifier: Unlicense OR MIT

package main

import (
	"time"

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

const defaultDuration = 300 * time.Millisecond

// Slider implements sliding between old/new widget values.
type Slider struct {
	Duration time.Duration

	push int

	next *op.Ops

	nextCall op.CallOp
	lastCall op.CallOp

	t0     time.Time
	offset float32
}

// PushLeft pushes the existing widget to the left.
func (s *Slider) PushLeft() { s.push = 1 }

// PushRight pushes the existing widget to the right.
func (s *Slider) PushRight() { s.push = -1 }

// Layout lays out widget that can be pushed.
func (s *Slider) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
	if s.push != 0 {
		s.next = nil
		s.lastCall = s.nextCall
		s.offset = float32(s.push)
		s.t0 = gtx.Now
		s.push = 0
	}

	var delta time.Duration
	if !s.t0.IsZero() {
		now := gtx.Now
		delta = now.Sub(s.t0)
		s.t0 = now
	}

	if s.offset != 0 {
		duration := s.Duration
		if duration == 0 {
			duration = defaultDuration
		}
		movement := float32(delta.Seconds()) / float32(duration.Seconds())
		if s.offset < 0 {
			s.offset += movement
			if s.offset >= 0 {
				s.offset = 0
			}
		} else {
			s.offset -= movement
			if s.offset <= 0 {
				s.offset = 0
			}
		}

		op.InvalidateOp{}.Add(gtx.Ops)
	}

	var dims layout.Dimensions
	{
		if s.next == nil {
			s.next = new(op.Ops)
		}
		gtx := gtx
		gtx.Ops = s.next
		gtx.Ops.Reset()
		m := op.Record(gtx.Ops)
		dims = w(gtx)
		s.nextCall = m.Stop()
	}

	if s.offset == 0 {
		s.nextCall.Add(gtx.Ops)
		return dims
	}

	defer op.Push(gtx.Ops).Pop()

	offset := smooth(s.offset)

	if s.offset > 0 {
		op.Offset(f32.Point{
			X: float32(dims.Size.X) * (offset - 1),
		}).Add(gtx.Ops)
		s.lastCall.Add(gtx.Ops)

		op.Offset(f32.Point{
			X: float32(dims.Size.X),
		}).Add(gtx.Ops)
		s.nextCall.Add(gtx.Ops)
	} else {
		op.Offset(f32.Point{
			X: float32(dims.Size.X) * (offset + 1),
		}).Add(gtx.Ops)
		s.lastCall.Add(gtx.Ops)

		op.Offset(f32.Point{
			X: float32(-dims.Size.X),
		}).Add(gtx.Ops)
		s.nextCall.Add(gtx.Ops)
	}
	return dims
}

// smooth handles -1 to 1 with ease-in-out cubic easing func.
func smooth(t float32) float32 {
	if t < 0 {
		return -easeInOutCubic(-t)
	}
	return easeInOutCubic(t)
}

// easeInOutCubic maps a linear value to a ease-in-out-cubic easing function.
func easeInOutCubic(t float32) float32 {
	if t < 0.5 {
		return 4 * t * t * t
	}
	return (t-1)*(2*t-2)*(2*t-2) + 1
}