~pierrec/giox

ref: f2af8f824417422c9374715fb0229001c073a3f7 giox/widgetx/materialx/modal.go -rw-r--r-- 4.8 KiB
f2af8f82pierre widgetx/materialx: added comments to ModalAlertStyle 10 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
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
170
171
172
173
174
175
176
177
178
package materialx

import (
	"image"
	"image/color"

	"gioui.org/f32"
	"gioui.org/io/pointer"
	"gioui.org/layout"
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/op/paint"
	"gioui.org/unit"
	"gioui.org/widget"
	"gioui.org/widget/material"

	"git.sr.ht/~pierrec/giox/colorx"
	"git.sr.ht/~pierrec/giox/layoutx"
	"git.sr.ht/~pierrec/giox/widgetx"
)

type ModalAlertStyle struct {
	// Active indicates whether or not the modal is displayed.
	Active     bool
	Modal      widgetx.Modal
	Background color.NRGBA
	Border     widget.Border
	// Title is displayed at the top of the modal.
	Title material.LabelStyle
	// Message is displayed after the title.
	Message material.LabelStyle
	// Actions are displayed along their axis at the bottom of the modal.
	ActionAxis   layout.Axis
	ActionStyles []material.ButtonStyle
	clicks       []widget.Clickable
	actions      layout.List
}

// ModalConfirm creates a two actions (typically OK/Cancel) alert modal.
func ModalConfirm(th *material.Theme) ModalAlertStyle {
	action := material.Button(th, nil, "")
	styles := []material.ButtonStyle{action, action}
	styles[0].Text = "OK"
	styles[1].Text = "Cancel"
	return ModalAlertStyle{
		Modal: widgetx.Modal{
			Background: colorx.MulAlpha(th.Palette.Bg, 32),
		},
		Background: th.Palette.Bg,
		Border: widget.Border{
			Color:        th.Palette.ContrastBg,
			CornerRadius: th.TextSize.Scale(0.5),
			Width:        th.TextSize.Scale(0.25),
		},
		Title:        material.H5(th, ""),
		Message:      material.Body1(th, ""),
		ActionAxis:   layout.Horizontal,
		ActionStyles: styles,
	}
}

func (m *ModalAlertStyle) init() {
	m.actions.Axis = m.ActionAxis
	switch cn, an := len(m.clicks), len(m.ActionStyles); {
	case cn < an:
		// Grow the clicks.
		m.clicks = append(m.clicks, make([]widget.Clickable, an-cn)...)
		for i := range m.clicks[cn:] {
			m.ActionStyles[cn+i].Button = &m.clicks[cn+i]
		}
	case cn > an:
		// Avoid leaks.
		for i := range m.clicks[an:] {
			m.clicks[an+i] = widget.Clickable{}
			m.ActionStyles[an+i].Button = nil
		}
		m.clicks = m.clicks[:an]
	}
}

func (m *ModalAlertStyle) Layout(gtx layout.Context, title, msg string) layout.Dimensions {
	if !m.Active {
		return layout.Dimensions{}
	}
	m.init()
	m.Modal.Layout(gtx)
	macro := op.Record(gtx.Ops)
	dims := layout.Stack{
		Alignment: layout.Center,
	}.Layout(gtx,
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			size := gtx.Constraints.Min
			// Fill the dialog background.
			r := f32.Rectangle{Max: layout.FPt(size)}
			radius := gtx.Metric.Px(m.Border.CornerRadius)
			shape := clip.UniformRRect(r, float32(radius))
			paint.FillShape(gtx.Ops, m.Background, shape.Op(gtx.Ops))
			// Disable clicks in the dialog.
			pointer.Rect(image.Rectangle{Max: size}).Add(gtx.Ops)
			pointer.InputOp{
				Tag:   m,
				Types: pointer.Press | pointer.Release | pointer.Move,
			}.Add(gtx.Ops)

			return layout.Dimensions{Size: size}
		}),
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			return m.Border.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
				flex := layoutx.Flex{
					Flex: layout.Flex{
						Axis:    layout.Vertical,
						Spacing: layout.SpaceSides,
					},
				}
				// Separator between the displayed items.
				sep := flex.RigidCross(func(gtx layout.Context) layout.Dimensions {
					col := colorx.MulAlpha(m.Background, 32)
					width := gtx.Metric.Px(m.Border.Width)
					shape := clip.Rect{Max: image.Pt(gtx.Constraints.Max.X, width)}
					paint.FillShape(gtx.Ops, col, shape.Op())
					return layout.Dimensions{Size: shape.Max}
				})
				padding := layout.UniformInset(unit.Dp(4))
				return flex.Layout(gtx,
					flex.Rigid(func(gtx layout.Context) layout.Dimensions {
						return padding.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
							style := m.Title
							style.Text = title
							return style.Layout(gtx)
						})
					}),
					sep,
					flex.Rigid(func(gtx layout.Context) layout.Dimensions {
						return layout.UniformInset(unit.Dp(16)).Layout(gtx, func(gtx layout.Context) layout.Dimensions {
							style := m.Message
							style.Text = msg
							return style.Layout(gtx)
						})
					}),
					sep,
					flex.Rigid(func(gtx layout.Context) layout.Dimensions {
						return padding.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
							return m.actions.Layout(gtx, len(m.ActionStyles), func(gtx layout.Context, idx int) layout.Dimensions {
								return m.ActionStyles[idx].Layout(gtx)
							})
						})
					}),
				)
			})
		}),
	)
	op.Defer(gtx.Ops, macro.Stop())
	return dims
}

func (m *ModalAlertStyle) Clicked() (pos int) {
	m.init()
	if m.Modal.Changed() {
		m.Active = false
		return -1
	}
	for i := range m.clicks {
		if m.clicks[i].Clicked() {
			m.Active = false
			return i
		}
	}
	return -1
}

func max(a int, b ...int) int {
	for _, x := range b {
		if x > a {
			a = x
		}
	}
	return a
}