-
Notifications
You must be signed in to change notification settings - Fork 6
/
modal_layer.go
98 lines (86 loc) · 2.41 KB
/
modal_layer.go
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
package widget
import (
"image"
"time"
"gioui.org/io/event"
"gioui.org/io/pointer"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/clip"
"gioui.org/widget/material"
"gioui.org/x/component"
)
// ModalLayer is a widget drawn on top of the normal UI that can be populated
// by other components with dismissble modal dialogs.
type ModalLayer struct {
component.VisibilityAnimation
Widget func(gtx layout.Context, th *material.Theme, anim *component.VisibilityAnimation) layout.Dimensions
}
const defaultModalAnimationDuration = time.Millisecond * 250
// NewModal creates an initializes a modal layer.
func NewModal() *ModalLayer {
m := ModalLayer{}
m.VisibilityAnimation.State = component.Invisible
m.VisibilityAnimation.Duration = defaultModalAnimationDuration
return &m
}
// Layout renders the modal layer. Unless a modal widget has been triggered,
// this will do nothing.
func (m *ModalLayer) Layout(gtx layout.Context, th *material.Theme) layout.Dimensions {
m.update(gtx)
if !m.Visible() {
return D{}
}
// Lay out a transparent scrim to block input to things beneath the
// contextual widget.
suppressionScrim := func() op.CallOp {
macro := op.Record(gtx.Ops)
pr := clip.Rect(image.Rectangle{Min: image.Point{-1e6, -1e6}, Max: image.Point{1e6, 1e6}})
stack := pr.Push(gtx.Ops)
event.Op(gtx.Ops, m)
stack.Pop()
return macro.Stop()
}()
op.Defer(gtx.Ops, suppressionScrim)
gtx.Constraints.Min = gtx.Constraints.Max
if m.Widget != nil {
macro := op.Record(gtx.Ops)
dims := m.Widget(gtx, th, &m.VisibilityAnimation)
contentOps := macro.Stop()
modalAreaOps := func() op.CallOp {
macro := op.Record(gtx.Ops)
var modalArea clip.Rect
if m.Animating() {
revealed := m.Revealed(gtx)
modalArea = clip.Rect{Max: image.Point{dims.Size.X, int(float32(dims.Size.Y) * revealed)}}
} else {
modalArea = clip.Rect{Max: image.Point{dims.Size.X, dims.Size.Y}}
}
stack := modalArea.Push(gtx.Ops)
contentOps.Add(gtx.Ops)
stack.Pop()
return macro.Stop()
}()
op.Defer(gtx.Ops, modalAreaOps)
}
return layout.Dimensions{Size: gtx.Constraints.Max}
}
func (m *ModalLayer) update(gtx C) {
// Dismiss the contextual widget if the user clicked outside of it.
for {
ev, ok := gtx.Event(pointer.Filter{
Target: m,
Kinds: pointer.Press,
})
if !ok {
break
}
e, ok := ev.(pointer.Event)
if !ok {
continue
}
if e.Kind == pointer.Press {
m.Disappear(gtx.Now)
}
}
}