~pierrec/giox

ref: 8b5b6cd9a4f1a665581b251b751ffc22205812fa giox/widgetx/materialx/modal.go -rw-r--r-- 4.8 KiB
8b5b6cd9pierre widgetx: replaced Modal ReleaseKeys with Keys as []string 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
179
180
package materialx

import (
	"image"
	"image/color"

	"gioui.org/f32"
	"gioui.org/io/key"
	"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),
			Keys:       []string{key.NameEscape},
		},
		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
}