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