edit.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. // Copyright 2016 The G3N Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package gui
  5. import (
  6. "strings"
  7. "time"
  8. "github.com/g3n/engine/math32"
  9. "github.com/g3n/engine/text"
  10. "github.com/g3n/engine/window"
  11. )
  12. // Edit represents a text edit box GUI element
  13. type Edit struct {
  14. Label // Embedded label
  15. MaxLength int // Maximum number of characters
  16. width int // edit width in pixels
  17. placeHolder string // place holder string
  18. text string // current edit text
  19. col int // current column
  20. selStart int // start column of selection. always < selEnd. if selStart == selEnd then nothing is selected.
  21. selEnd int // end column of selection. always > selStart. if selStart == selEnd then nothing is selected.
  22. focus bool // key focus flag
  23. cursorOver bool
  24. blinkID int
  25. caretOn bool
  26. styles *EditStyles
  27. }
  28. // EditStyle contains the styling of an Edit
  29. type EditStyle struct {
  30. Border RectBounds
  31. Paddings RectBounds
  32. BorderColor math32.Color4
  33. BgColor math32.Color4
  34. BgAlpha float32
  35. FgColor math32.Color4
  36. HolderColor math32.Color4
  37. }
  38. // EditStyles contains an EditStyle for each valid GUI state
  39. type EditStyles struct {
  40. Normal EditStyle
  41. Over EditStyle
  42. Focus EditStyle
  43. Disabled EditStyle
  44. }
  45. const (
  46. editMarginX = 4
  47. blinkTime = 1000
  48. )
  49. // NewEdit creates and returns a pointer to a new edit widget
  50. func NewEdit(width int, placeHolder string) *Edit {
  51. ed := new(Edit)
  52. ed.width = width
  53. ed.placeHolder = placeHolder
  54. ed.styles = &StyleDefault().Edit
  55. ed.text = ""
  56. ed.MaxLength = 80
  57. ed.col = 0
  58. ed.selStart = 0
  59. ed.selEnd = 0
  60. ed.focus = false
  61. ed.Label.initialize("", StyleDefault().Font)
  62. ed.Label.Subscribe(OnKeyDown, ed.onKey)
  63. ed.Label.Subscribe(OnKeyRepeat, ed.onKey)
  64. ed.Label.Subscribe(OnChar, ed.onChar)
  65. ed.Label.Subscribe(OnMouseDown, ed.onMouse)
  66. ed.Label.Subscribe(OnCursorEnter, ed.onCursor)
  67. ed.Label.Subscribe(OnCursorLeave, ed.onCursor)
  68. ed.Label.Subscribe(OnEnable, func(evname string, ev interface{}) { ed.update() })
  69. ed.Subscribe(OnFocusLost, ed.OnFocusLost)
  70. ed.update()
  71. return ed
  72. }
  73. // SetText sets this edit text
  74. func (ed *Edit) SetText(newText string) *Edit {
  75. // Remove new lines from text
  76. ed.text = strings.Replace(newText, "\n", "", -1)
  77. ed.col = text.StrCount(ed.text)
  78. ed.selStart = ed.col
  79. ed.selEnd = ed.col
  80. ed.update()
  81. return ed
  82. }
  83. // Text returns the current edited text
  84. func (ed *Edit) Text() string {
  85. return ed.text
  86. }
  87. // SelectedText returns the currently selected text
  88. // or empty string when nothing is selected
  89. func (ed *Edit) SelectedText() string {
  90. if ed.selStart == ed.selEnd {
  91. return ""
  92. }
  93. s := ""
  94. charNum := 0
  95. for _, currentRune := range ed.text {
  96. if charNum >= ed.selEnd {
  97. break
  98. }
  99. if charNum >= ed.selStart {
  100. s += string(currentRune)
  101. }
  102. charNum++
  103. }
  104. return s
  105. }
  106. // SetFontSize sets label font size (overrides Label.SetFontSize)
  107. func (ed *Edit) SetFontSize(size float64) *Edit {
  108. ed.Label.SetFontSize(size)
  109. ed.redraw(ed.focus)
  110. return ed
  111. }
  112. // SetStyles set the button styles overriding the default style
  113. func (ed *Edit) SetStyles(es *EditStyles) {
  114. ed.styles = es
  115. ed.update()
  116. }
  117. // LostKeyFocus satisfies the IPanel interface and is called by gui root
  118. // container when the panel loses the key focus
  119. func (ed *Edit) OnFocusLost(evname string, ev interface{}) {
  120. ed.focus = false
  121. ed.update()
  122. Manager().ClearTimeout(ed.blinkID)
  123. }
  124. // CursorPos sets the position of the cursor at the
  125. // specified column if possible
  126. func (ed *Edit) CursorPos(col int) {
  127. if col <= text.StrCount(ed.text) {
  128. ed.col = col
  129. ed.selStart = col
  130. ed.selEnd = col
  131. ed.redraw(ed.focus)
  132. }
  133. }
  134. // SetSelection selects the text between start and end
  135. func (ed *Edit) SetSelection(start, end int) {
  136. // make sure end is bigger than start
  137. if start > end {
  138. start, end = end, start
  139. }
  140. if start < 0 {
  141. start = 0
  142. }
  143. if end > text.StrCount(ed.text) {
  144. end = text.StrCount(ed.text)
  145. }
  146. ed.selStart = start
  147. ed.selEnd = end
  148. ed.col = end
  149. ed.redraw(ed.focus)
  150. }
  151. // CursorLeft moves the edit cursor one character left if possible
  152. // If text is selected the cursor is moved to the beginning of the selection instead
  153. // and the selection is removed
  154. func (ed *Edit) CursorLeft() {
  155. if ed.selStart == ed.selEnd {
  156. // no selection
  157. // move cursor to the left if possible
  158. if ed.col > 0 {
  159. ed.col--
  160. ed.selStart = ed.col
  161. ed.selEnd = ed.col
  162. ed.redraw(ed.focus)
  163. }
  164. } else {
  165. // reset selection and move cursor to start of selection
  166. ed.col = ed.selStart
  167. ed.selStart = ed.col
  168. ed.selEnd = ed.col
  169. ed.redraw(ed.focus)
  170. }
  171. }
  172. // CursorRight moves the edit cursor one character right if possible
  173. // If text is selected the cursor is moved to the end of the selection instead
  174. // and the selection is removed
  175. func (ed *Edit) CursorRight() {
  176. if ed.selStart == ed.selEnd {
  177. // no selection
  178. // move cursor to the right if possible
  179. if ed.col < text.StrCount(ed.text) {
  180. ed.col++
  181. ed.selStart = ed.col
  182. ed.selEnd = ed.col
  183. ed.redraw(ed.focus)
  184. }
  185. } else {
  186. // reset selection and move cursor to end of selection
  187. ed.col = ed.selEnd
  188. ed.selStart = ed.col
  189. ed.selEnd = ed.col
  190. ed.redraw(ed.focus)
  191. }
  192. }
  193. // SelectLeft expands/shrinks the selection to the left if possible
  194. func (ed *Edit) SelectLeft() {
  195. if ed.col > 0 {
  196. if ed.col == ed.selStart {
  197. // cursor is at the start of selection
  198. // expand selection to the left
  199. ed.col--
  200. ed.selStart = ed.col
  201. ed.redraw(ed.focus)
  202. } else {
  203. // cursor is at the end of selection:
  204. // remove selection from the end
  205. ed.col--
  206. ed.selEnd = ed.col
  207. ed.redraw(ed.focus)
  208. }
  209. }
  210. }
  211. // SelectRight expands/shrinks the selection to the right if possible
  212. func (ed *Edit) SelectRight() {
  213. if ed.col < text.StrCount(ed.text) {
  214. if ed.col == ed.selEnd {
  215. // cursor is at the end of selection:
  216. // expand selection to the right if possible
  217. ed.col++
  218. ed.selEnd = ed.col
  219. ed.redraw(ed.focus)
  220. } else {
  221. // cursor is at the start of selection:
  222. // remove selection from the start
  223. ed.col++
  224. ed.selStart = ed.col
  225. ed.redraw(ed.focus)
  226. }
  227. }
  228. }
  229. // SelectHome expands the selection to the left to the beginning of the text
  230. func (ed *Edit) SelectHome() {
  231. if ed.selStart < ed.col {
  232. ed.selEnd = ed.selStart
  233. }
  234. ed.col = 0
  235. ed.selStart = 0
  236. ed.redraw(ed.focus)
  237. }
  238. // SelectEnd expands the selection to the right to the end of the text
  239. func (ed *Edit) SelectEnd() {
  240. if ed.selEnd > ed.col {
  241. ed.selStart = ed.selEnd
  242. }
  243. ed.col = text.StrCount(ed.text)
  244. ed.selEnd = ed.col
  245. ed.redraw(ed.focus)
  246. }
  247. // SelectAll selects all text
  248. func (ed *Edit) SelectAll() {
  249. ed.selStart = 0
  250. ed.selEnd = text.StrCount(ed.text)
  251. ed.col = ed.selEnd
  252. ed.redraw(ed.focus)
  253. }
  254. // CursorBack either deletes the character at left of the cursor if possible
  255. // Or if text is selected the selected text is removed all at once
  256. func (ed *Edit) CursorBack() {
  257. if ed.selStart == ed.selEnd {
  258. if ed.col > 0 {
  259. ed.col--
  260. ed.selStart = ed.col
  261. ed.selEnd = ed.col
  262. ed.text = text.StrRemove(ed.text, ed.col)
  263. ed.redraw(ed.focus)
  264. ed.Dispatch(OnChange, nil)
  265. }
  266. } else {
  267. ed.DeleteSelection()
  268. }
  269. }
  270. // CursorHome moves the edit cursor to the beginning of the text
  271. func (ed *Edit) CursorHome() {
  272. ed.col = 0
  273. ed.selStart = ed.col
  274. ed.selEnd = ed.col
  275. ed.redraw(ed.focus)
  276. }
  277. // CursorEnd moves the edit cursor to the end of the text
  278. func (ed *Edit) CursorEnd() {
  279. ed.col = text.StrCount(ed.text)
  280. ed.selStart = ed.col
  281. ed.selEnd = ed.col
  282. ed.redraw(ed.focus)
  283. }
  284. // CursorDelete either deletes the character at the right of the cursor if possible
  285. // Or if text is selected the selected text is removed all at once
  286. func (ed *Edit) CursorDelete() {
  287. if ed.selStart == ed.selEnd {
  288. if ed.col < text.StrCount(ed.text) {
  289. ed.text = text.StrRemove(ed.text, ed.col)
  290. ed.redraw(ed.focus)
  291. ed.Dispatch(OnChange, nil)
  292. }
  293. } else {
  294. ed.DeleteSelection()
  295. }
  296. }
  297. // DeleteSelection deletes the selected characters. Does nothing if nothing is selected.
  298. func (ed *Edit) DeleteSelection() {
  299. if ed.selStart == ed.selEnd {
  300. return
  301. }
  302. changed := false
  303. ed.col = ed.selStart
  304. for ed.selEnd > ed.selStart {
  305. if ed.col < text.StrCount(ed.text) {
  306. changed = true
  307. ed.text = text.StrRemove(ed.text, ed.col)
  308. ed.selEnd--
  309. }
  310. }
  311. if changed {
  312. ed.Dispatch(OnChange, nil)
  313. ed.redraw(ed.focus)
  314. }
  315. }
  316. // CursorInput inserts the specified string at the current cursor position
  317. // If text is selected the selected text gets overwritten
  318. func (ed *Edit) CursorInput(s string) {
  319. if ed.selStart != ed.selEnd {
  320. ed.DeleteSelection()
  321. }
  322. if text.StrCount(ed.text) >= ed.MaxLength {
  323. return
  324. }
  325. // Set new text with included input
  326. var newText string
  327. if ed.col < text.StrCount(ed.text) {
  328. newText = text.StrInsert(ed.text, s, ed.col)
  329. } else {
  330. newText = ed.text + s
  331. }
  332. // Checks if new text exceeds edit width
  333. width, _ := ed.Label.font.MeasureText(newText)
  334. if float32(width)+editMarginX+float32(1) >= ed.Label.ContentWidth() {
  335. return
  336. }
  337. ed.text = newText
  338. ed.col++
  339. ed.selStart = ed.col
  340. ed.selEnd = ed.col
  341. ed.Dispatch(OnChange, nil)
  342. ed.redraw(ed.focus)
  343. }
  344. // redraw redraws the text showing the caret if specified
  345. // the selection caret is always shown (when text is selected)
  346. func (ed *Edit) redraw(caret bool) {
  347. line := 0
  348. ed.Label.setTextCaret(ed.text, editMarginX, ed.width, caret, line, ed.col, ed.selStart, ed.selEnd)
  349. }
  350. // onKey receives subscribed key events
  351. func (ed *Edit) onKey(evname string, ev interface{}) {
  352. kev := ev.(*window.KeyEvent)
  353. if kev.Mods != window.ModShift && kev.Mods != window.ModControl {
  354. switch kev.Key {
  355. case window.KeyLeft:
  356. ed.CursorLeft()
  357. case window.KeyRight:
  358. ed.CursorRight()
  359. case window.KeyHome:
  360. ed.CursorHome()
  361. case window.KeyEnd:
  362. ed.CursorEnd()
  363. case window.KeyBackspace:
  364. ed.CursorBack()
  365. case window.KeyDelete:
  366. ed.CursorDelete()
  367. default:
  368. return
  369. }
  370. } else if kev.Mods == window.ModShift {
  371. switch kev.Key {
  372. case window.KeyLeft:
  373. ed.SelectLeft()
  374. case window.KeyRight:
  375. ed.SelectRight()
  376. case window.KeyHome:
  377. ed.SelectHome()
  378. case window.KeyEnd:
  379. ed.SelectEnd()
  380. case window.KeyBackspace:
  381. ed.CursorBack()
  382. case window.KeyDelete:
  383. ed.SelectAll()
  384. ed.DeleteSelection()
  385. default:
  386. return
  387. }
  388. } else if kev.Mods == window.ModControl {
  389. switch kev.Key {
  390. case window.KeyA:
  391. ed.SelectAll()
  392. }
  393. }
  394. }
  395. // onChar receives subscribed char events
  396. func (ed *Edit) onChar(evname string, ev interface{}) {
  397. cev := ev.(*window.CharEvent)
  398. ed.CursorInput(string(cev.Char))
  399. }
  400. // onMouseEvent receives subscribed mouse down events
  401. func (ed *Edit) onMouse(evname string, ev interface{}) {
  402. e := ev.(*window.MouseEvent)
  403. if e.Button != window.MouseButtonLeft {
  404. return
  405. }
  406. // Find clicked column
  407. var nchars int
  408. for nchars = 1; nchars <= text.StrCount(ed.text); nchars++ {
  409. width, _ := ed.Label.font.MeasureText(text.StrPrefix(ed.text, nchars))
  410. posx := e.Xpos - ed.pospix.X
  411. if posx < editMarginX+float32(width) {
  412. break
  413. }
  414. }
  415. if !ed.focus {
  416. ed.focus = true
  417. ed.blinkID = Manager().SetInterval(750*time.Millisecond, nil, ed.blink)
  418. }
  419. ed.CursorPos(nchars - 1)
  420. // Set key focus to this panel
  421. // Set the focus AFTER the mouse selection is handled
  422. // Otherwise the OnFocus event would fire before the cursor is set.
  423. // That way the OnFocus handler could NOT influence the selection
  424. // Because it would be overridden/cleared directly afterwards.
  425. Manager().SetKeyFocus(ed)
  426. }
  427. // onCursor receives subscribed cursor events
  428. func (ed *Edit) onCursor(evname string, ev interface{}) {
  429. if evname == OnCursorEnter {
  430. window.Get().SetCursor(window.IBeamCursor)
  431. ed.cursorOver = true
  432. ed.update()
  433. return
  434. }
  435. if evname == OnCursorLeave {
  436. window.Get().SetCursor(window.ArrowCursor)
  437. ed.cursorOver = false
  438. ed.update()
  439. return
  440. }
  441. }
  442. // blink blinks the caret
  443. func (ed *Edit) blink(arg interface{}) {
  444. if !ed.focus {
  445. return
  446. }
  447. if !ed.caretOn {
  448. ed.caretOn = true
  449. } else {
  450. ed.caretOn = false
  451. }
  452. ed.redraw(ed.caretOn)
  453. }
  454. // update updates the visual state
  455. func (ed *Edit) update() {
  456. if !ed.Enabled() {
  457. ed.applyStyle(&ed.styles.Disabled)
  458. return
  459. }
  460. if ed.cursorOver {
  461. ed.applyStyle(&ed.styles.Over)
  462. return
  463. }
  464. if ed.focus {
  465. ed.applyStyle(&ed.styles.Focus)
  466. return
  467. }
  468. ed.applyStyle(&ed.styles.Normal)
  469. }
  470. // applyStyle applies the specified style
  471. func (ed *Edit) applyStyle(s *EditStyle) {
  472. ed.SetBordersFrom(&s.Border)
  473. ed.SetBordersColor4(&s.BorderColor)
  474. ed.SetPaddingsFrom(&s.Paddings)
  475. ed.Label.SetColor4(&s.FgColor)
  476. ed.Label.SetBgColor4(&s.BgColor)
  477. //ed.Label.SetBgAlpha(s.BgAlpha)
  478. if !ed.focus && len(ed.text) == 0 && len(ed.placeHolder) > 0 {
  479. ed.Label.SetColor4(&s.HolderColor)
  480. ed.Label.setTextCaret(ed.placeHolder, editMarginX, ed.width, false, -1, ed.col, ed.selStart, ed.selEnd)
  481. } else {
  482. ed.Label.SetColor4(&s.FgColor)
  483. ed.redraw(ed.focus)
  484. }
  485. }