edit.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  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) / float32(ed.Label.font.ScaleX()) + 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. scaleX, _ := window.Get().GetScale()
  352. ed.Label.setTextCaret(ed.text, editMarginX, int(float64(ed.width) * scaleX), caret, line, ed.col, ed.selStart, ed.selEnd)
  353. }
  354. // onKey receives subscribed key events
  355. func (ed *Edit) onKey(evname string, ev interface{}) {
  356. kev := ev.(*window.KeyEvent)
  357. if kev.Mods != window.ModShift && kev.Mods != window.ModControl {
  358. switch kev.Key {
  359. case window.KeyLeft:
  360. ed.CursorLeft()
  361. case window.KeyRight:
  362. ed.CursorRight()
  363. case window.KeyHome:
  364. ed.CursorHome()
  365. case window.KeyEnd:
  366. ed.CursorEnd()
  367. case window.KeyBackspace:
  368. ed.CursorBack()
  369. case window.KeyDelete:
  370. ed.CursorDelete()
  371. default:
  372. return
  373. }
  374. } else if kev.Mods == window.ModShift {
  375. switch kev.Key {
  376. case window.KeyLeft:
  377. ed.SelectLeft()
  378. case window.KeyRight:
  379. ed.SelectRight()
  380. case window.KeyHome:
  381. ed.SelectHome()
  382. case window.KeyEnd:
  383. ed.SelectEnd()
  384. case window.KeyBackspace:
  385. ed.CursorBack()
  386. case window.KeyDelete:
  387. ed.SelectAll()
  388. ed.DeleteSelection()
  389. default:
  390. return
  391. }
  392. } else if kev.Mods == window.ModControl {
  393. switch kev.Key {
  394. case window.KeyA:
  395. ed.SelectAll()
  396. }
  397. }
  398. }
  399. // onChar receives subscribed char events
  400. func (ed *Edit) onChar(evname string, ev interface{}) {
  401. cev := ev.(*window.CharEvent)
  402. ed.CursorInput(string(cev.Char))
  403. }
  404. // onMouseDown receives subscribed mouse down events
  405. func (ed *Edit) onMouseDown(evname string, ev interface{}) {
  406. e := ev.(*window.MouseEvent)
  407. if e.Button != window.MouseButtonLeft {
  408. return
  409. }
  410. // set caret to clicked position
  411. ed.handleMouse(e.Xpos, false)
  412. ed.mouseDrag = true
  413. // Set key focus to this panel
  414. // Set the focus AFTER the mouse selection is handled
  415. // Otherwise the OnFocus event would fire before the cursor is set.
  416. // That way the OnFocus handler could NOT influence the selection
  417. // Because it would be overridden/cleared directly afterwards.
  418. Manager().SetKeyFocus(ed)
  419. }
  420. // handleMouse is setting the caret when the mouse is clicked
  421. // or setting the text selection when the mouse is dragged
  422. func (ed *Edit) handleMouse(mouseX float32, dragged bool) {
  423. // Find clicked column
  424. var nchars int
  425. for nchars = 1; nchars <= text.StrCount(ed.text); nchars++ {
  426. width, _ := ed.Label.font.MeasureText(text.StrPrefix(ed.text, nchars))
  427. posx := mouseX - ed.pospix.X
  428. if posx < editMarginX + float32(float64(width) / ed.Label.font.ScaleX()) {
  429. break
  430. }
  431. }
  432. if !ed.focus {
  433. ed.focus = true
  434. ed.blinkID = Manager().SetInterval(750*time.Millisecond, nil, ed.blink)
  435. }
  436. if !dragged {
  437. ed.CursorPos(nchars - 1)
  438. } else {
  439. newPos := nchars - 1
  440. if newPos > ed.col {
  441. distance := newPos - ed.col
  442. for i := 0; i < distance; i++ {
  443. ed.SelectRight()
  444. }
  445. } else if newPos < ed.col {
  446. distance := ed.col - newPos
  447. for i := 0; i < distance; i++ {
  448. ed.SelectLeft()
  449. }
  450. }
  451. }
  452. }
  453. // onMouseEvent receives subscribed mouse up events
  454. func (ed *Edit) onMouseUp(evname string, ev interface{}) {
  455. ed.mouseDrag = false
  456. }
  457. // onCursor receives subscribed cursor events
  458. func (ed *Edit) onCursor(evname string, ev interface{}) {
  459. if evname == OnCursorEnter {
  460. window.Get().SetCursor(window.IBeamCursor)
  461. ed.cursorOver = true
  462. ed.update()
  463. return
  464. }
  465. if evname == OnCursorLeave {
  466. window.Get().SetCursor(window.ArrowCursor)
  467. ed.cursorOver = false
  468. ed.mouseDrag = false
  469. ed.update()
  470. return
  471. }
  472. if ed.mouseDrag {
  473. e := ev.(*window.CursorEvent)
  474. // select text based on mouse position
  475. ed.handleMouse(e.Xpos, true)
  476. }
  477. }
  478. // blink blinks the caret
  479. func (ed *Edit) blink(arg interface{}) {
  480. if !ed.focus {
  481. return
  482. }
  483. if !ed.caretOn {
  484. ed.caretOn = true
  485. } else {
  486. ed.caretOn = false
  487. }
  488. ed.redraw(ed.caretOn)
  489. }
  490. // update updates the visual state
  491. func (ed *Edit) update() {
  492. if !ed.Enabled() {
  493. ed.applyStyle(&ed.styles.Disabled)
  494. return
  495. }
  496. if ed.cursorOver {
  497. ed.applyStyle(&ed.styles.Over)
  498. return
  499. }
  500. if ed.focus {
  501. ed.applyStyle(&ed.styles.Focus)
  502. return
  503. }
  504. ed.applyStyle(&ed.styles.Normal)
  505. }
  506. // applyStyle applies the specified style
  507. func (ed *Edit) applyStyle(s *EditStyle) {
  508. ed.SetBordersFrom(&s.Border)
  509. ed.SetBordersColor4(&s.BorderColor)
  510. ed.SetPaddingsFrom(&s.Paddings)
  511. ed.Label.SetColor4(&s.FgColor)
  512. ed.Label.SetBgColor4(&s.BgColor)
  513. //ed.Label.SetBgAlpha(s.BgAlpha)
  514. if !ed.focus && len(ed.text) == 0 && len(ed.placeHolder) > 0 {
  515. scaleX, _ := window.Get().GetScale()
  516. ed.Label.SetColor4(&s.HolderColor)
  517. ed.Label.setTextCaret(ed.placeHolder, editMarginX, int(float64(ed.width) * scaleX), false, -1, ed.col, ed.selStart, ed.selEnd)
  518. } else {
  519. ed.Label.SetColor4(&s.FgColor)
  520. ed.redraw(ed.focus)
  521. }
  522. }