edit.go 14 KB

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