multi.go raw
1 package gel
2
3 import (
4 l "github.com/p9c/gio/layout"
5 "golang.org/x/exp/shiny/materialdesign/icons"
6 )
7
8 type Multi struct {
9 *Window
10 lines *[]string
11 clickables []*Clickable
12 buttons []*ButtonLayout
13 input *Input
14 inputLocation int
15 addClickable *Clickable
16 removeClickables []*Clickable
17 removeButtons []*IconButton
18 handle func(txt []string)
19 }
20
21 func (w *Window) Multiline(
22 txt *[]string,
23 borderColorFocused, borderColorUnfocused, backgroundColor string,
24 size float32,
25 handle func(txt []string),
26 ) (m *Multi) {
27 if handle == nil {
28 handle = func(txt []string) {
29 D.Ln(txt)
30 }
31 }
32 addClickable := w.Clickable()
33 m = &Multi{
34 Window: w,
35 lines: txt,
36 inputLocation: -1,
37 addClickable: addClickable,
38 handle: handle,
39 }
40 handleChange := func(txt string) {
41 D.Ln("handleChange", m.inputLocation)
42 (*m.lines)[m.inputLocation] = txt
43 // after submit clear the editor
44 m.inputLocation = -1
45 m.handle(*m.lines)
46 }
47 m.input = w.Input("", "", borderColorFocused, borderColorUnfocused, backgroundColor, handleChange, nil)
48 m.clickables = append(m.clickables, (*Clickable)(nil))
49 // m.buttons = append(m.buttons, (*ButtonLayout)(nil))
50 m.removeClickables = append(m.removeClickables, (*Clickable)(nil))
51 m.removeButtons = append(m.removeButtons, (*IconButton)(nil))
52 for i := range *m.lines {
53 // D.Ln("making clickables")
54 x := i
55 clickable := m.Clickable().SetClick(
56 func() {
57 m.inputLocation = x
58 D.Ln("button clicked", x, m.inputLocation)
59 })
60 if len(*m.lines) > len(m.clickables) {
61 m.clickables = append(m.clickables, clickable)
62 } else {
63 m.clickables[i] = clickable
64 }
65 // D.Ln("making button")
66 btn := m.ButtonLayout(clickable).CornerRadius(0).Background(
67 backgroundColor).
68 Embed(
69 m.Theme.Flex().AlignStart().
70 Flexed(1,
71 m.Fill("Primary", l.Center, m.TextSize.V, 0, m.Inset(0.25,
72 m.Body2((*m.lines)[i]).Color("DocText").Fn,
73 ).Fn).Fn,
74 ).Fn,
75 )
76 if len(*m.lines) > len(m.buttons) {
77 m.buttons = append(m.buttons, btn)
78 } else {
79 m.buttons[i] = btn
80 }
81 // D.Ln("making clickables")
82 removeClickable := m.Clickable()
83 if len(*m.lines) > len(m.removeClickables) {
84 m.removeClickables = append(m.removeClickables, removeClickable)
85 } else {
86 m.removeClickables[i] = removeClickable
87 }
88 // D.Ln("making remove button")
89 y := i
90 removeBtn := m.IconButton(removeClickable).
91 Icon(
92 m.Icon().Scale(1.5).Color("DocText").Src(&icons.ActionDelete),
93 ).
94 Background("").
95 SetClick(func() {
96 D.Ln("remove button", y, "clicked", len(*m.lines))
97 m.inputLocation = -1
98 if len(*m.lines)-1 == y {
99 *m.lines = (*m.lines)[:len(*m.lines)-1]
100 } else if len(*m.lines)-2 == y {
101 *m.lines = (*m.lines)[:len(*m.lines)-2]
102 } else {
103 *m.lines = append((*m.lines)[:y+1], (*m.lines)[y+2:]...)
104 }
105 m.handle(*m.lines)
106 // D.Ln("remove button", i, "clicked")
107 // m.inputLocation = -1
108 // ll := len(*m.lines)-1
109 // if i == ll {
110 // *m.lines = (*m.lines)[:len(*m.lines)-1]
111 // m.clickables = m.clickables[:len(m.clickables)-1]
112 // m.buttons = m.buttons[:len(m.buttons)-1]
113 // m.removeClickables = m.removeClickables[:len(m.removeClickables)-1]
114 // m.removeButtons = m.removeButtons[:len(m.removeButtons)-1]
115 // } else {
116 // if len(*m.lines)-1 < i {
117 // return
118 // }
119 // *m.lines = append((*m.lines)[:i], (*m.lines)[i+1:]...)
120 // m.clickables = append(m.clickables[:i], m.clickables[i+1:]...)
121 // m.buttons = append(m.buttons[:i], m.buttons[i+1:]...)
122 // m.removeClickables = append(m.removeClickables[:i], m.removeClickables[i+1:]...)
123 // m.removeButtons = append(m.removeButtons[:i], m.removeButtons[i+1:]...)
124 // }
125 })
126 if len(*m.lines) > len(m.removeButtons) {
127 m.removeButtons = append(m.removeButtons, removeBtn)
128 } else {
129 m.removeButtons[x] = removeBtn
130 }
131 }
132 return m
133 }
134
135 func (m *Multi) UpdateWidgets() *Multi {
136 if len(m.clickables) < len(*m.lines) {
137 D.Ln("allocating new clickables")
138 m.clickables = append(m.clickables, (*Clickable)(nil))
139 }
140 if len(m.buttons) < len(*m.lines) {
141 D.Ln("allocating new buttons")
142 m.buttons = append(m.buttons, (*ButtonLayout)(nil))
143 }
144 if len(m.removeClickables) < len(*m.lines) {
145 D.Ln("allocating new removeClickables")
146 m.removeClickables = append(m.clickables, (*Clickable)(nil))
147 }
148 if len(m.removeButtons) < len(*m.lines) {
149 D.Ln("allocating new removeButtons")
150 m.removeButtons = append(m.removeButtons, (*IconButton)(nil))
151 }
152 return m
153 }
154
155 func (m *Multi) PopulateWidgets() *Multi {
156 added := false
157 for i := range *m.lines {
158 if m.clickables[i] == nil {
159 added = true
160 D.Ln("making clickables", i)
161 x := i
162 m.clickables[i] = m.Clickable().SetClick(
163 func() {
164 D.Ln("clicked", x, m.inputLocation)
165 m.inputLocation = x
166 m.input.editor.SetText((*m.lines)[x])
167 m.input.editor.Focus()
168 // m.input.editor.SetFocus(func(is bool) {
169 // if !is {
170 // m.inputLocation = -1
171 // }
172 // })
173 })
174 }
175 // m.clickables[i]
176 if m.buttons[i] == nil {
177 added = true
178 btn := m.ButtonLayout(m.clickables[i]).CornerRadius(0).Background("Transparent")
179 m.buttons[i] = btn
180 }
181 m.buttons[i].Embed(
182 m.Theme.Flex().
183 Rigid(
184 m.Inset(0.25,
185 m.Body2((*m.lines)[i]).Color("DocText").Fn,
186 ).Fn,
187 ).Fn,
188 )
189 if m.removeClickables[i] == nil {
190 added = true
191 removeClickable := m.Clickable()
192 m.removeClickables[i] = removeClickable
193 }
194 if m.removeButtons[i] == nil {
195 added = true
196 D.Ln("making remove button", i)
197 x := i
198 m.removeButtons[i] = m.IconButton(m.removeClickables[i].
199 SetClick(func() {
200 D.Ln("remove button", x, "clicked", len(*m.lines))
201 m.inputLocation = -1
202 if len(*m.lines)-1 == i {
203 *m.lines = (*m.lines)[:len(*m.lines)-1]
204 } else {
205 *m.lines = append((*m.lines)[:x], (*m.lines)[x+1:]...)
206 }
207 m.handle(*m.lines)
208 })).
209 Icon(
210 m.Icon().Scale(1.5).Color("DocText").Src(&icons.ActionDelete),
211 ).
212 Background("")
213 }
214 }
215 if added {
216 D.Ln("clearing editor")
217 m.input.editor.SetText("")
218 m.input.editor.Focus()
219 }
220 return m
221 }
222
223 func (m *Multi) Fn(gtx l.Context) l.Dimensions {
224 m.UpdateWidgets()
225 m.PopulateWidgets()
226 addButton := m.IconButton(m.addClickable).Icon(
227 m.Icon().Scale(1.5).Color("Primary").Src(&icons.ContentAdd),
228 )
229 var widgets []l.Widget
230 if m.inputLocation > 0 && m.inputLocation < len(*m.lines) {
231 m.input.Editor().SetText((*m.lines)[m.inputLocation])
232 }
233 for i := range *m.lines {
234 if m.buttons[i] == nil {
235 x := i
236 btn := m.ButtonLayout(m.clickables[i].SetClick(
237 func() {
238 D.Ln("button pressed", (*m.lines)[x], x, m.inputLocation)
239 m.inputLocation = x
240 m.input.editor.SetText((*m.lines)[x])
241 m.input.editor.Focus()
242 })).CornerRadius(0).Background("Transparent").
243 Embed(
244 m.Theme.Flex().
245 Rigid(
246 m.Inset(0.25,
247 m.Body2((*m.lines)[x]).Color("DocText").Fn,
248 ).Fn,
249 ).Fn,
250 )
251 m.buttons[i] = btn
252 }
253 if i == m.inputLocation {
254 m.input.Editor().SetText((*m.lines)[i])
255 input := m.Flex().
256 Rigid(
257 m.removeButtons[i].Fn,
258 ).
259 Flexed(1,
260 m.input.Fn,
261 ).
262 Fn
263 widgets = append(widgets, input)
264 } else {
265 x := i
266 m.clickables[i].SetClick(
267 func() {
268 D.Ln("setting", x, m.inputLocation)
269 m.inputLocation = x
270 m.input.editor.SetText((*m.lines)[x])
271 m.input.editor.Focus()
272 })
273 button := m.Flex().AlignStart().
274 Rigid(
275 m.removeButtons[i].Fn,
276 ).
277 Flexed(1,
278 m.buttons[i].Fn,
279 ).
280 Fn
281 widgets = append(widgets, button)
282 }
283 }
284 widgets = append(widgets, addButton.SetClick(func() {
285 D.Ln("clicked add")
286 *m.lines = append(*m.lines, "")
287 m.inputLocation = len(*m.lines) - 1
288 D.S([]string(*m.lines))
289 m.UpdateWidgets()
290 m.PopulateWidgets()
291 m.input.editor.SetText("")
292 m.input.editor.Focus()
293 }).Background("").Fn)
294 // m.UpdateWidgets()
295 // m.PopulateWidgets()
296 // D.Ln(m.inputLocation)
297 // if m.inputLocation > 0 {
298 // m.input.Editor().Focus()
299 // }
300 out := m.Theme.VFlex()
301 for i := range widgets {
302 out.Rigid(widgets[i])
303 }
304 return out.Fn(gtx)
305 }
306
307 func (m *Multi) Widgets() (widgets []l.Widget) {
308 m.UpdateWidgets()
309 m.PopulateWidgets()
310 if m.inputLocation > 0 && m.inputLocation < len(*m.lines) {
311 m.input.Editor().SetText((*m.lines)[m.inputLocation])
312 }
313 focusFunc := func(is bool) {
314 mi := m.inputLocation
315 D.Ln("editor", "is focused", is)
316 // debug.PrintStack()
317 if !is {
318 m.input.borderColor = m.input.borderColorUnfocused
319 // submit the current edit if any
320 txt := m.input.editor.Text()
321 cur := (*m.lines)[m.inputLocation]
322 if txt != cur {
323 D.Ln("changed text")
324 // run submit hook
325 m.input.editor.submitHook(txt)
326 } else {
327 D.Ln("text not changed")
328 // When a new item is added this unfocus event occurs and this makes it behave correctly
329 // Normally the editor would not be rendered if not focused so setting it to focus does no harm in the
330 // case of switching to another
331 }
332 // m.inputLocation = -1
333 } else {
334 m.input.borderColor = m.input.borderColorFocused
335 m.inputLocation = mi
336 // m.input.editor.Focus()
337 }
338 }
339 m.input.editor.SetFocus(focusFunc)
340 for ii := range *m.lines {
341 i := ii
342 // D.Ln("iterating lines", i, len(*m.lines))
343 if m.buttons[i] == nil {
344 D.Ln("making new button layout")
345 btn := m.ButtonLayout(m.clickables[i].SetClick(
346 func() {
347 D.Ln("button pressed", (*m.lines)[i], i, m.inputLocation)
348 m.UpdateWidgets()
349 m.PopulateWidgets()
350 m.inputLocation = i
351 m.input.editor.SetText((*m.lines)[i])
352 m.input.editor.Focus()
353 })).CornerRadius(0).Background("").
354 Embed(
355 func(gtx l.Context) l.Dimensions {
356 return m.Theme.Flex().
357 Flexed(1,
358 m.Inset(0.25,
359 m.Body2((*m.lines)[i]).Color("DocText").Fn,
360 ).Fn,
361 ).Fn(gtx)
362 },
363 )
364 m.buttons[i] = btn
365 }
366 if i == m.inputLocation {
367 // x := i
368 // D.Ln("rendering editor", x)
369
370 input := func(gtx l.Context) l.Dimensions {
371 return m.Inset(0.25,
372 m.Flex().
373 Rigid(
374 m.removeButtons[i].Fn,
375 ).
376 Flexed(1,
377 m.input.Fn,
378 ).
379 Fn,
380 ).
381 Fn(gtx)
382 }
383 widgets = append(widgets, input)
384 } else {
385 // D.Ln("rendering button", i)
386 m.clickables[i].SetClick(
387 func() {
388 m.UpdateWidgets()
389 m.PopulateWidgets()
390 m.inputLocation = i
391 m.input.editor.SetText((*m.lines)[i])
392 m.input.editor.Focus()
393 D.Ln("setting", i, m.inputLocation)
394 })
395 button := func(gtx l.Context) l.Dimensions {
396 return m.Inset(0.25,
397 m.Flex().AlignStart().
398 Rigid(
399 m.removeButtons[i].Fn,
400 ).
401 Rigid(
402 m.buttons[i].Fn,
403 ).
404 Flexed(1, EmptyMaxWidth()).
405 Fn,
406 ).Fn(gtx)
407 }
408 widgets = append(widgets, button)
409 }
410 }
411 // D.Ln("widgets", widgets)
412 addButton := func(gtx l.Context) l.Dimensions {
413 addb :=
414 m.Inset(0.25,
415 m.Theme.Flex().AlignStart().
416 Rigid(
417 m.IconButton(
418 m.addClickable).
419 Icon(
420 m.Icon().Scale(1.5).Color("Primary").Src(&icons.ContentAdd),
421 ).
422 SetClick(func() {
423 D.Ln("clicked add")
424 m.inputLocation = len(*m.lines)
425 *m.lines = append(*m.lines, "")
426 m.input.editor.SetText("")
427 D.S([]string(*m.lines))
428 m.UpdateWidgets()
429 m.PopulateWidgets()
430 m.input.editor.Focus()
431 }).
432 Background("Transparent").
433 Fn,
434 ).
435 Flexed(1, EmptyMaxWidth()).
436 Fn,
437 ).Fn
438 widgets = append(widgets, addb)
439 return addb(gtx)
440 }
441 widgets = append(widgets, addButton)
442 return
443 }
444