entry.go 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939
  1. package widget
  2. import (
  3. "image/color"
  4. "math"
  5. "runtime"
  6. "strings"
  7. "time"
  8. "unicode"
  9. "fyne.io/fyne/v2"
  10. "fyne.io/fyne/v2/canvas"
  11. "fyne.io/fyne/v2/data/binding"
  12. "fyne.io/fyne/v2/driver/desktop"
  13. "fyne.io/fyne/v2/driver/mobile"
  14. "fyne.io/fyne/v2/internal/cache"
  15. "fyne.io/fyne/v2/internal/widget"
  16. "fyne.io/fyne/v2/theme"
  17. )
  18. const (
  19. bindIgnoreDelay = time.Millisecond * 100 // ignore incoming DataItem fire after we have called Set
  20. multiLineRows = 3
  21. wordSeparator = "`~!@#$%^&*()-=+[{]}\\|;:'\",.<>/?"
  22. )
  23. // Declare conformity with interfaces
  24. var _ fyne.Disableable = (*Entry)(nil)
  25. var _ fyne.Draggable = (*Entry)(nil)
  26. var _ fyne.Focusable = (*Entry)(nil)
  27. var _ fyne.Tappable = (*Entry)(nil)
  28. var _ fyne.Widget = (*Entry)(nil)
  29. var _ desktop.Mouseable = (*Entry)(nil)
  30. var _ desktop.Keyable = (*Entry)(nil)
  31. var _ mobile.Keyboardable = (*Entry)(nil)
  32. var _ mobile.Touchable = (*Entry)(nil)
  33. var _ fyne.Tabbable = (*Entry)(nil)
  34. // Entry widget allows simple text to be input when focused.
  35. type Entry struct {
  36. DisableableWidget
  37. shortcut fyne.ShortcutHandler
  38. Text string
  39. // Since: 2.0
  40. TextStyle fyne.TextStyle
  41. PlaceHolder string
  42. OnChanged func(string) `json:"-"`
  43. // Since: 2.0
  44. OnSubmitted func(string) `json:"-"`
  45. Password bool
  46. MultiLine bool
  47. Wrapping fyne.TextWrap
  48. // Scroll can be used to turn off the scrolling of our entry when Wrapping is WrapNone.
  49. //
  50. // Since: 2.4
  51. Scroll widget.ScrollDirection
  52. // Set a validator that this entry will check against
  53. // Since: 1.4
  54. Validator fyne.StringValidator `json:"-"`
  55. validationStatus *validationStatus
  56. onValidationChanged func(error)
  57. validationError error
  58. CursorRow, CursorColumn int
  59. OnCursorChanged func() `json:"-"`
  60. cursorAnim *entryCursorAnimation
  61. dirty bool
  62. focused bool
  63. text *RichText
  64. placeholder *RichText
  65. content *entryContent
  66. scroll *widget.Scroll
  67. // useful for Form validation (as the error text should only be shown when
  68. // the entry is unfocused)
  69. onFocusChanged func(bool)
  70. // selectRow and selectColumn represent the selection start location
  71. // The selection will span from selectRow/Column to CursorRow/Column -- note that the cursor
  72. // position may occur before or after the select start position in the text.
  73. selectRow, selectColumn int
  74. // selectKeyDown indicates whether left shift or right shift is currently held down
  75. selectKeyDown bool
  76. // selecting indicates whether the cursor has moved since it was at the selection start location
  77. selecting bool
  78. popUp *PopUpMenu
  79. // TODO: Add OnSelectChanged
  80. // ActionItem is a small item which is displayed at the outer right of the entry (like a password revealer)
  81. ActionItem fyne.CanvasObject `json:"-"`
  82. binder basicBinder
  83. conversionError error
  84. lastChange time.Time
  85. multiLineRows int // override global default number of visible lines
  86. }
  87. // NewEntry creates a new single line entry widget.
  88. func NewEntry() *Entry {
  89. e := &Entry{Wrapping: fyne.TextTruncate}
  90. e.ExtendBaseWidget(e)
  91. return e
  92. }
  93. // NewEntryWithData returns an Entry widget connected to the specified data source.
  94. //
  95. // Since: 2.0
  96. func NewEntryWithData(data binding.String) *Entry {
  97. entry := NewEntry()
  98. entry.Bind(data)
  99. return entry
  100. }
  101. // NewMultiLineEntry creates a new entry that allows multiple lines
  102. func NewMultiLineEntry() *Entry {
  103. e := &Entry{MultiLine: true, Wrapping: fyne.TextTruncate}
  104. e.ExtendBaseWidget(e)
  105. return e
  106. }
  107. // NewPasswordEntry creates a new entry password widget
  108. func NewPasswordEntry() *Entry {
  109. e := &Entry{Password: true, Wrapping: fyne.TextTruncate}
  110. e.ExtendBaseWidget(e)
  111. e.ActionItem = newPasswordRevealer(e)
  112. return e
  113. }
  114. // AcceptsTab returns if Entry accepts the Tab key or not.
  115. //
  116. // Implements: fyne.Tabbable
  117. //
  118. // Since: 2.1
  119. func (e *Entry) AcceptsTab() bool {
  120. return e.MultiLine
  121. }
  122. // Bind connects the specified data source to this Entry.
  123. // The current value will be displayed and any changes in the data will cause the widget to update.
  124. // User interactions with this Entry will set the value into the data source.
  125. //
  126. // Since: 2.0
  127. func (e *Entry) Bind(data binding.String) {
  128. e.binder.SetCallback(e.updateFromData)
  129. e.binder.Bind(data)
  130. e.Validator = func(string) error {
  131. return e.conversionError
  132. }
  133. }
  134. // CreateRenderer is a private method to Fyne which links this widget to its renderer
  135. //
  136. // Implements: fyne.Widget
  137. func (e *Entry) CreateRenderer() fyne.WidgetRenderer {
  138. e.ExtendBaseWidget(e)
  139. // initialise
  140. e.textProvider()
  141. e.placeholderProvider()
  142. box := canvas.NewRectangle(theme.InputBackgroundColor())
  143. box.CornerRadius = theme.InputRadiusSize()
  144. border := canvas.NewRectangle(color.Transparent)
  145. border.StrokeWidth = theme.InputBorderSize()
  146. border.StrokeColor = theme.InputBorderColor()
  147. border.CornerRadius = theme.InputRadiusSize()
  148. cursor := canvas.NewRectangle(color.Transparent)
  149. cursor.Hide()
  150. e.cursorAnim = newEntryCursorAnimation(cursor)
  151. e.content = &entryContent{entry: e}
  152. e.scroll = widget.NewScroll(nil)
  153. objects := []fyne.CanvasObject{box, border}
  154. if e.Wrapping != fyne.TextWrapOff || e.Scroll != widget.ScrollNone {
  155. e.scroll.Content = e.content
  156. objects = append(objects, e.scroll)
  157. } else {
  158. e.scroll.Hide()
  159. objects = append(objects, e.content)
  160. }
  161. e.content.scroll = e.scroll
  162. if e.Password && e.ActionItem == nil {
  163. // An entry widget has been created via struct setting manually
  164. // the Password field to true. Going to enable the password revealer.
  165. e.ActionItem = newPasswordRevealer(e)
  166. }
  167. if e.ActionItem != nil {
  168. objects = append(objects, e.ActionItem)
  169. }
  170. return &entryRenderer{box, border, e.scroll, objects, e}
  171. }
  172. // Cursor returns the cursor type of this widget
  173. //
  174. // Implements: desktop.Cursorable
  175. func (e *Entry) Cursor() desktop.Cursor {
  176. return desktop.TextCursor
  177. }
  178. // Disable this widget so that it cannot be interacted with, updating any style appropriately.
  179. //
  180. // Implements: fyne.Disableable
  181. func (e *Entry) Disable() {
  182. e.DisableableWidget.Disable()
  183. }
  184. // Disabled returns whether the entry is disabled or read-only.
  185. //
  186. // Implements: fyne.Disableable
  187. func (e *Entry) Disabled() bool {
  188. return e.DisableableWidget.disabled
  189. }
  190. // DoubleTapped is called when this entry has been double tapped so we should select text below the pointer
  191. //
  192. // Implements: fyne.DoubleTappable
  193. func (e *Entry) DoubleTapped(p *fyne.PointEvent) {
  194. row := e.textProvider().row(e.CursorRow)
  195. start, end := getTextWhitespaceRegion(row, e.CursorColumn, false)
  196. if start == -1 || end == -1 {
  197. return
  198. }
  199. e.setFieldsAndRefresh(func() {
  200. if !e.selectKeyDown {
  201. e.selectRow = e.CursorRow
  202. e.selectColumn = start
  203. }
  204. // Always aim to maximise the selected region
  205. if e.selectRow > e.CursorRow || (e.selectRow == e.CursorRow && e.selectColumn > e.CursorColumn) {
  206. e.CursorColumn = start
  207. } else {
  208. e.CursorColumn = end
  209. }
  210. e.selecting = true
  211. })
  212. }
  213. // DragEnd is called at end of a drag event.
  214. //
  215. // Implements: fyne.Draggable
  216. func (e *Entry) DragEnd() {
  217. e.propertyLock.Lock()
  218. if e.CursorColumn == e.selectColumn && e.CursorRow == e.selectRow {
  219. e.selecting = false
  220. }
  221. shouldRefresh := !e.selecting
  222. e.propertyLock.Unlock()
  223. if shouldRefresh {
  224. e.Refresh()
  225. }
  226. }
  227. // Dragged is called when the pointer moves while a button is held down.
  228. // It updates the selection accordingly.
  229. //
  230. // Implements: fyne.Draggable
  231. func (e *Entry) Dragged(d *fyne.DragEvent) {
  232. pos := d.Position.Subtract(e.scroll.Offset).Add(fyne.NewPos(0, theme.InputBorderSize()))
  233. if !e.selecting {
  234. startPos := pos.Subtract(d.Dragged)
  235. e.selectRow, e.selectColumn = e.getRowCol(startPos)
  236. e.selecting = true
  237. }
  238. e.updateMousePointer(pos, false)
  239. }
  240. // Enable this widget, updating any style or features appropriately.
  241. //
  242. // Implements: fyne.Disableable
  243. func (e *Entry) Enable() {
  244. e.DisableableWidget.Enable()
  245. }
  246. // ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality.
  247. func (e *Entry) ExtendBaseWidget(wid fyne.Widget) {
  248. impl := e.super()
  249. if impl != nil {
  250. return
  251. }
  252. e.propertyLock.Lock()
  253. defer e.propertyLock.Unlock()
  254. e.BaseWidget.impl = wid
  255. e.registerShortcut()
  256. }
  257. // FocusGained is called when the Entry has been given focus.
  258. //
  259. // Implements: fyne.Focusable
  260. func (e *Entry) FocusGained() {
  261. e.setFieldsAndRefresh(func() {
  262. e.dirty = true
  263. e.focused = true
  264. })
  265. if e.onFocusChanged != nil {
  266. e.onFocusChanged(true)
  267. }
  268. }
  269. // FocusLost is called when the Entry has had focus removed.
  270. //
  271. // Implements: fyne.Focusable
  272. func (e *Entry) FocusLost() {
  273. e.setFieldsAndRefresh(func() {
  274. e.focused = false
  275. e.selectKeyDown = false
  276. })
  277. if e.onFocusChanged != nil {
  278. e.onFocusChanged(false)
  279. }
  280. }
  281. // Hide hides the entry.
  282. //
  283. // Implements: fyne.Widget
  284. func (e *Entry) Hide() {
  285. if e.popUp != nil {
  286. e.popUp.Hide()
  287. e.popUp = nil
  288. }
  289. e.DisableableWidget.Hide()
  290. }
  291. // Keyboard implements the Keyboardable interface
  292. //
  293. // Implements: mobile.Keyboardable
  294. func (e *Entry) Keyboard() mobile.KeyboardType {
  295. e.propertyLock.RLock()
  296. defer e.propertyLock.RUnlock()
  297. if e.MultiLine {
  298. return mobile.DefaultKeyboard
  299. } else if e.Password {
  300. return mobile.PasswordKeyboard
  301. }
  302. return mobile.SingleLineKeyboard
  303. }
  304. // KeyDown handler for keypress events - used to store shift modifier state for text selection
  305. //
  306. // Implements: desktop.Keyable
  307. func (e *Entry) KeyDown(key *fyne.KeyEvent) {
  308. if e.Disabled() {
  309. return
  310. }
  311. // For keyboard cursor controlled selection we now need to store shift key state and selection "start"
  312. // Note: selection start is where the highlight started (if the user moves the selection up or left then
  313. // the selectRow/Column will not match SelectionStart)
  314. if key.Name == desktop.KeyShiftLeft || key.Name == desktop.KeyShiftRight {
  315. if !e.selecting {
  316. e.selectRow = e.CursorRow
  317. e.selectColumn = e.CursorColumn
  318. }
  319. e.selectKeyDown = true
  320. }
  321. }
  322. // KeyUp handler for key release events - used to reset shift modifier state for text selection
  323. //
  324. // Implements: desktop.Keyable
  325. func (e *Entry) KeyUp(key *fyne.KeyEvent) {
  326. if e.Disabled() {
  327. return
  328. }
  329. // Handle shift release for keyboard selection
  330. // Note: if shift is released then the user may repress it without moving to adjust their old selection
  331. if key.Name == desktop.KeyShiftLeft || key.Name == desktop.KeyShiftRight {
  332. e.selectKeyDown = false
  333. }
  334. }
  335. // MinSize returns the size that this widget should not shrink below.
  336. //
  337. // Implements: fyne.Widget
  338. func (e *Entry) MinSize() fyne.Size {
  339. e.ExtendBaseWidget(e)
  340. min := e.BaseWidget.MinSize()
  341. if e.ActionItem != nil {
  342. min = min.Add(fyne.NewSize(theme.IconInlineSize()+theme.LineSpacing(), 0))
  343. }
  344. if e.Validator != nil {
  345. min = min.Add(fyne.NewSize(theme.IconInlineSize()+theme.LineSpacing(), 0))
  346. }
  347. return min
  348. }
  349. // MouseDown called on mouse click, this triggers a mouse click which can move the cursor,
  350. // update the existing selection (if shift is held), or start a selection dragging operation.
  351. //
  352. // Implements: desktop.Mouseable
  353. func (e *Entry) MouseDown(m *desktop.MouseEvent) {
  354. e.propertyLock.Lock()
  355. if e.selectKeyDown {
  356. e.selecting = true
  357. }
  358. if e.selecting && !e.selectKeyDown && m.Button == desktop.MouseButtonPrimary {
  359. e.selecting = false
  360. }
  361. e.propertyLock.Unlock()
  362. e.updateMousePointer(m.Position, m.Button == desktop.MouseButtonSecondary)
  363. if !e.Disabled() {
  364. e.requestFocus()
  365. }
  366. }
  367. // MouseUp called on mouse release
  368. // If a mouse drag event has completed then check to see if it has resulted in an empty selection,
  369. // if so, and if a text select key isn't held, then disable selecting
  370. //
  371. // Implements: desktop.Mouseable
  372. func (e *Entry) MouseUp(m *desktop.MouseEvent) {
  373. e.propertyLock.Lock()
  374. defer e.propertyLock.Unlock()
  375. start, _ := e.selection()
  376. if start == -1 && e.selecting && !e.selectKeyDown {
  377. e.selecting = false
  378. }
  379. }
  380. // SelectedText returns the text currently selected in this Entry.
  381. // If there is no selection it will return the empty string.
  382. func (e *Entry) SelectedText() string {
  383. e.propertyLock.RLock()
  384. defer e.propertyLock.RUnlock()
  385. if !e.selecting {
  386. return ""
  387. }
  388. start, stop := e.selection()
  389. if start == stop {
  390. return ""
  391. }
  392. r := ([]rune)(e.textProvider().String())
  393. return string(r[start:stop])
  394. }
  395. // SetMinRowsVisible forces a multi-line entry to show `count` number of rows without scrolling.
  396. // This is not a validation or requirement, it just impacts the minimum visible size.
  397. // Use this carefully as Fyne apps can run on small screens so you may wish to add a scroll container if
  398. // this number is high. Default is 3.
  399. //
  400. // Since: 2.2
  401. func (e *Entry) SetMinRowsVisible(count int) {
  402. e.multiLineRows = count
  403. }
  404. // SetPlaceHolder sets the text that will be displayed if the entry is otherwise empty
  405. func (e *Entry) SetPlaceHolder(text string) {
  406. e.propertyLock.Lock()
  407. e.PlaceHolder = text
  408. e.propertyLock.Unlock()
  409. e.placeholderProvider().Segments[0].(*TextSegment).Text = text
  410. e.placeholder.updateRowBounds()
  411. e.placeholderProvider().Refresh()
  412. }
  413. // SetText manually sets the text of the Entry to the given text value.
  414. func (e *Entry) SetText(text string) {
  415. e.updateTextAndRefresh(text)
  416. e.updateCursorAndSelection()
  417. }
  418. // Appends the text to the end of the entry
  419. //
  420. // Since: 2.4
  421. func (e *Entry) Append(text string) {
  422. e.propertyLock.Lock()
  423. provider := e.textProvider()
  424. provider.insertAt(provider.len(), text)
  425. content := provider.String()
  426. changed := e.updateText(content)
  427. e.propertyLock.Unlock()
  428. if changed {
  429. e.Validate()
  430. if e.OnChanged != nil {
  431. e.OnChanged(content)
  432. }
  433. }
  434. e.Refresh()
  435. }
  436. // Tapped is called when this entry has been tapped. We update the cursor position in
  437. // device-specific callbacks (MouseDown() and TouchDown()).
  438. //
  439. // Implements: fyne.Tappable
  440. func (e *Entry) Tapped(ev *fyne.PointEvent) {
  441. if fyne.CurrentDevice().IsMobile() && e.selecting {
  442. e.selecting = false
  443. }
  444. }
  445. // TappedSecondary is called when right or alternative tap is invoked.
  446. //
  447. // Opens the PopUpMenu with `Paste` item to paste text from the clipboard.
  448. //
  449. // Implements: fyne.SecondaryTappable
  450. func (e *Entry) TappedSecondary(pe *fyne.PointEvent) {
  451. if e.Disabled() && e.Password {
  452. return // no popup options for a disabled concealed field
  453. }
  454. e.requestFocus()
  455. clipboard := fyne.CurrentApp().Driver().AllWindows()[0].Clipboard()
  456. super := e.super()
  457. cutItem := fyne.NewMenuItem("Cut", func() {
  458. super.(fyne.Shortcutable).TypedShortcut(&fyne.ShortcutCut{Clipboard: clipboard})
  459. })
  460. copyItem := fyne.NewMenuItem("Copy", func() {
  461. super.(fyne.Shortcutable).TypedShortcut(&fyne.ShortcutCopy{Clipboard: clipboard})
  462. })
  463. pasteItem := fyne.NewMenuItem("Paste", func() {
  464. super.(fyne.Shortcutable).TypedShortcut(&fyne.ShortcutPaste{Clipboard: clipboard})
  465. })
  466. selectAllItem := fyne.NewMenuItem("Select all", e.selectAll)
  467. entryPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(super)
  468. popUpPos := entryPos.Add(fyne.NewPos(pe.Position.X, pe.Position.Y))
  469. c := fyne.CurrentApp().Driver().CanvasForObject(super)
  470. var menu *fyne.Menu
  471. if e.Disabled() {
  472. menu = fyne.NewMenu("", copyItem, selectAllItem)
  473. } else if e.Password {
  474. menu = fyne.NewMenu("", pasteItem, selectAllItem)
  475. } else {
  476. menu = fyne.NewMenu("", cutItem, copyItem, pasteItem, selectAllItem)
  477. }
  478. e.popUp = NewPopUpMenu(menu, c)
  479. e.popUp.ShowAtPosition(popUpPos)
  480. }
  481. // TouchDown is called when this entry gets a touch down event on mobile device, we ensure we have focus.
  482. //
  483. // Since: 2.1
  484. //
  485. // Implements: mobile.Touchable
  486. func (e *Entry) TouchDown(ev *mobile.TouchEvent) {
  487. if !e.Disabled() {
  488. e.requestFocus()
  489. }
  490. e.updateMousePointer(ev.Position, false)
  491. }
  492. // TouchUp is called when this entry gets a touch up event on mobile device.
  493. //
  494. // Since: 2.1
  495. //
  496. // Implements: mobile.Touchable
  497. func (e *Entry) TouchUp(*mobile.TouchEvent) {
  498. }
  499. // TouchCancel is called when this entry gets a touch cancel event on mobile device (app was removed from focus).
  500. //
  501. // Since: 2.1
  502. //
  503. // Implements: mobile.Touchable
  504. func (e *Entry) TouchCancel(*mobile.TouchEvent) {
  505. }
  506. // TypedKey receives key input events when the Entry widget is focused.
  507. //
  508. // Implements: fyne.Focusable
  509. func (e *Entry) TypedKey(key *fyne.KeyEvent) {
  510. if e.Disabled() {
  511. return
  512. }
  513. if e.cursorAnim != nil {
  514. e.cursorAnim.interrupt()
  515. }
  516. e.propertyLock.RLock()
  517. provider := e.textProvider()
  518. multiLine := e.MultiLine
  519. e.propertyLock.RUnlock()
  520. if e.selectKeyDown || e.selecting {
  521. if e.selectingKeyHandler(key) {
  522. e.Refresh()
  523. return
  524. }
  525. }
  526. switch key.Name {
  527. case fyne.KeyBackspace:
  528. e.propertyLock.RLock()
  529. isEmpty := provider.len() == 0 || (e.CursorColumn == 0 && e.CursorRow == 0)
  530. e.propertyLock.RUnlock()
  531. if isEmpty {
  532. return
  533. }
  534. e.propertyLock.Lock()
  535. pos := e.cursorTextPos()
  536. provider.deleteFromTo(pos-1, pos)
  537. e.CursorRow, e.CursorColumn = e.rowColFromTextPos(pos - 1)
  538. e.propertyLock.Unlock()
  539. case fyne.KeyDelete:
  540. pos := e.cursorTextPos()
  541. if provider.len() == 0 || pos == provider.len() {
  542. return
  543. }
  544. e.propertyLock.Lock()
  545. provider.deleteFromTo(pos, pos+1)
  546. e.propertyLock.Unlock()
  547. case fyne.KeyReturn, fyne.KeyEnter:
  548. e.typedKeyReturn(provider, multiLine)
  549. case fyne.KeyTab:
  550. e.TypedRune('\t')
  551. case fyne.KeyUp:
  552. e.typedKeyUp(provider)
  553. case fyne.KeyDown:
  554. e.typedKeyDown(provider)
  555. case fyne.KeyLeft:
  556. e.typedKeyLeft(provider)
  557. case fyne.KeyRight:
  558. e.typedKeyRight(provider)
  559. case fyne.KeyEnd:
  560. e.typedKeyEnd(provider)
  561. case fyne.KeyHome:
  562. e.typedKeyHome()
  563. case fyne.KeyPageUp:
  564. e.propertyLock.Lock()
  565. if e.MultiLine {
  566. e.CursorRow = 0
  567. }
  568. e.CursorColumn = 0
  569. e.propertyLock.Unlock()
  570. case fyne.KeyPageDown:
  571. e.propertyLock.Lock()
  572. if e.MultiLine {
  573. e.CursorRow = provider.rows() - 1
  574. e.CursorColumn = provider.rowLength(e.CursorRow)
  575. } else {
  576. e.CursorColumn = provider.len()
  577. }
  578. e.propertyLock.Unlock()
  579. default:
  580. return
  581. }
  582. e.propertyLock.Lock()
  583. content := provider.String()
  584. changed := e.updateText(content)
  585. if e.CursorRow == e.selectRow && e.CursorColumn == e.selectColumn {
  586. e.selecting = false
  587. }
  588. e.propertyLock.Unlock()
  589. if changed {
  590. e.Validate()
  591. if e.OnChanged != nil {
  592. e.OnChanged(content)
  593. }
  594. }
  595. e.Refresh()
  596. }
  597. func (e *Entry) typedKeyUp(provider *RichText) {
  598. e.propertyLock.Lock()
  599. if e.CursorRow > 0 {
  600. e.CursorRow--
  601. } else {
  602. e.CursorColumn = 0
  603. }
  604. rowLength := provider.rowLength(e.CursorRow)
  605. if e.CursorColumn > rowLength {
  606. e.CursorColumn = rowLength
  607. }
  608. e.propertyLock.Unlock()
  609. }
  610. func (e *Entry) typedKeyDown(provider *RichText) {
  611. e.propertyLock.Lock()
  612. rowLength := provider.rowLength(e.CursorRow)
  613. if e.CursorRow < provider.rows()-1 {
  614. e.CursorRow++
  615. rowLength = provider.rowLength(e.CursorRow)
  616. } else {
  617. e.CursorColumn = rowLength
  618. }
  619. if e.CursorColumn > rowLength {
  620. e.CursorColumn = rowLength
  621. }
  622. e.propertyLock.Unlock()
  623. }
  624. func (e *Entry) typedKeyLeft(provider *RichText) {
  625. e.propertyLock.Lock()
  626. if e.CursorColumn > 0 {
  627. e.CursorColumn--
  628. } else if e.MultiLine && e.CursorRow > 0 {
  629. e.CursorRow--
  630. e.CursorColumn = provider.rowLength(e.CursorRow)
  631. }
  632. e.propertyLock.Unlock()
  633. }
  634. func (e *Entry) typedKeyRight(provider *RichText) {
  635. e.propertyLock.Lock()
  636. if e.MultiLine {
  637. rowLength := provider.rowLength(e.CursorRow)
  638. if e.CursorColumn < rowLength {
  639. e.CursorColumn++
  640. } else if e.CursorRow < provider.rows()-1 {
  641. e.CursorRow++
  642. e.CursorColumn = 0
  643. }
  644. } else if e.CursorColumn < provider.len() {
  645. e.CursorColumn++
  646. }
  647. e.propertyLock.Unlock()
  648. }
  649. func (e *Entry) typedKeyHome() {
  650. e.propertyLock.Lock()
  651. e.CursorColumn = 0
  652. e.propertyLock.Unlock()
  653. }
  654. func (e *Entry) typedKeyEnd(provider *RichText) {
  655. e.propertyLock.Lock()
  656. if e.MultiLine {
  657. e.CursorColumn = provider.rowLength(e.CursorRow)
  658. } else {
  659. e.CursorColumn = provider.len()
  660. }
  661. e.propertyLock.Unlock()
  662. }
  663. // TypedRune receives text input events when the Entry widget is focused.
  664. //
  665. // Implements: fyne.Focusable
  666. func (e *Entry) TypedRune(r rune) {
  667. if e.Disabled() {
  668. return
  669. }
  670. e.propertyLock.Lock()
  671. if e.popUp != nil {
  672. e.popUp.Hide()
  673. }
  674. // if we've typed a character and we're selecting then replace the selection with the character
  675. cb := e.OnChanged
  676. if e.selecting {
  677. e.OnChanged = nil // don't propagate this change to binding etc
  678. e.eraseSelection()
  679. e.OnChanged = cb // the change later will then trigger callback
  680. }
  681. provider := e.textProvider()
  682. e.selecting = false
  683. runes := []rune{r}
  684. pos := e.cursorTextPos()
  685. provider.insertAt(pos, string(runes))
  686. content := provider.String()
  687. e.updateText(content)
  688. e.CursorRow, e.CursorColumn = e.rowColFromTextPos(pos + len(runes))
  689. e.propertyLock.Unlock()
  690. e.Validate()
  691. if cb != nil {
  692. cb(content)
  693. }
  694. e.Refresh()
  695. }
  696. // TypedShortcut implements the Shortcutable interface
  697. //
  698. // Implements: fyne.Shortcutable
  699. func (e *Entry) TypedShortcut(shortcut fyne.Shortcut) {
  700. e.shortcut.TypedShortcut(shortcut)
  701. }
  702. // Unbind disconnects any configured data source from this Entry.
  703. // The current value will remain at the last value of the data source.
  704. //
  705. // Since: 2.0
  706. func (e *Entry) Unbind() {
  707. e.Validator = nil
  708. e.binder.Unbind()
  709. }
  710. // copyToClipboard copies the current selection to a given clipboard.
  711. // This does nothing if it is a concealed entry.
  712. func (e *Entry) copyToClipboard(clipboard fyne.Clipboard) {
  713. if !e.selecting || e.Password {
  714. return
  715. }
  716. clipboard.SetContent(e.SelectedText())
  717. }
  718. func (e *Entry) cursorColAt(text []rune, pos fyne.Position) int {
  719. for i := 0; i < len(text); i++ {
  720. str := string(text[0:i])
  721. wid := fyne.MeasureText(str, theme.TextSize(), e.TextStyle).Width
  722. charWid := fyne.MeasureText(string(text[i]), theme.TextSize(), e.TextStyle).Width
  723. if pos.X < theme.InnerPadding()+wid+(charWid/2) {
  724. return i
  725. }
  726. }
  727. return len(text)
  728. }
  729. func (e *Entry) cursorTextPos() (pos int) {
  730. return e.textPosFromRowCol(e.CursorRow, e.CursorColumn)
  731. }
  732. // copyToClipboard copies the current selection to a given clipboard and then removes the selected text.
  733. // This does nothing if it is a concealed entry.
  734. func (e *Entry) cutToClipboard(clipboard fyne.Clipboard) {
  735. if !e.selecting || e.Password {
  736. return
  737. }
  738. e.copyToClipboard(clipboard)
  739. e.setFieldsAndRefresh(e.eraseSelection)
  740. e.propertyLock.Lock()
  741. if e.OnChanged != nil {
  742. e.OnChanged(e.Text)
  743. }
  744. e.propertyLock.Unlock()
  745. e.Validate()
  746. }
  747. // eraseSelection removes the current selected region and moves the cursor
  748. func (e *Entry) eraseSelection() {
  749. if e.Disabled() {
  750. return
  751. }
  752. provider := e.textProvider()
  753. posA, posB := e.selection()
  754. if posA == posB {
  755. return
  756. }
  757. provider.deleteFromTo(posA, posB)
  758. e.CursorRow, e.CursorColumn = e.rowColFromTextPos(posA)
  759. e.selectRow, e.selectColumn = e.CursorRow, e.CursorColumn
  760. e.selecting = false
  761. e.updateText(provider.String())
  762. }
  763. func (e *Entry) getRowCol(p fyne.Position) (int, int) {
  764. e.propertyLock.RLock()
  765. defer e.propertyLock.RUnlock()
  766. rowHeight := e.textProvider().charMinSize(e.Password, e.TextStyle).Height
  767. row := int(math.Floor(float64(p.Y+e.scroll.Offset.Y-theme.LineSpacing()) / float64(rowHeight)))
  768. col := 0
  769. if row < 0 {
  770. row = 0
  771. } else if row >= e.textProvider().rows() {
  772. row = e.textProvider().rows() - 1
  773. col = e.textProvider().rowLength(row)
  774. } else {
  775. col = e.cursorColAt(e.textProvider().row(row), p.Add(e.scroll.Offset))
  776. }
  777. return row, col
  778. }
  779. // pasteFromClipboard inserts text from the clipboard content,
  780. // starting from the cursor position.
  781. func (e *Entry) pasteFromClipboard(clipboard fyne.Clipboard) {
  782. if e.selecting {
  783. e.setFieldsAndRefresh(e.eraseSelection)
  784. }
  785. text := clipboard.Content()
  786. if !e.MultiLine {
  787. // format clipboard content to be compatible with single line entry
  788. text = strings.Replace(text, "\n", " ", -1)
  789. }
  790. provider := e.textProvider()
  791. runes := []rune(text)
  792. pos := e.cursorTextPos()
  793. provider.insertAt(pos, text)
  794. e.updateTextAndRefresh(provider.String())
  795. e.CursorRow, e.CursorColumn = e.rowColFromTextPos(pos + len(runes))
  796. }
  797. // placeholderProvider returns the placeholder text handler for this entry
  798. func (e *Entry) placeholderProvider() *RichText {
  799. if e.placeholder != nil {
  800. return e.placeholder
  801. }
  802. style := RichTextStyleInline
  803. style.ColorName = theme.ColorNamePlaceHolder
  804. style.TextStyle = e.TextStyle
  805. text := NewRichText(&TextSegment{
  806. Style: style,
  807. Text: e.PlaceHolder,
  808. })
  809. text.ExtendBaseWidget(text)
  810. text.inset = fyne.NewSize(0, theme.InputBorderSize())
  811. e.placeholder = text
  812. return e.placeholder
  813. }
  814. func (e *Entry) registerShortcut() {
  815. e.shortcut.AddShortcut(&fyne.ShortcutCut{}, func(se fyne.Shortcut) {
  816. cut := se.(*fyne.ShortcutCut)
  817. e.cutToClipboard(cut.Clipboard)
  818. })
  819. e.shortcut.AddShortcut(&fyne.ShortcutCopy{}, func(se fyne.Shortcut) {
  820. cpy := se.(*fyne.ShortcutCopy)
  821. e.copyToClipboard(cpy.Clipboard)
  822. })
  823. e.shortcut.AddShortcut(&fyne.ShortcutPaste{}, func(se fyne.Shortcut) {
  824. paste := se.(*fyne.ShortcutPaste)
  825. e.pasteFromClipboard(paste.Clipboard)
  826. })
  827. e.shortcut.AddShortcut(&fyne.ShortcutSelectAll{}, func(se fyne.Shortcut) {
  828. e.selectAll()
  829. })
  830. moveWord := func(s fyne.Shortcut) {
  831. row := e.textProvider().row(e.CursorRow)
  832. start, end := getTextWhitespaceRegion(row, e.CursorColumn, true)
  833. if start == -1 || end == -1 {
  834. return
  835. }
  836. e.setFieldsAndRefresh(func() {
  837. if s.(*desktop.CustomShortcut).KeyName == fyne.KeyLeft {
  838. if e.CursorColumn == 0 {
  839. if e.CursorRow > 0 {
  840. e.CursorRow--
  841. e.CursorColumn = len(e.textProvider().row(e.CursorRow))
  842. }
  843. } else {
  844. e.CursorColumn = start
  845. }
  846. } else {
  847. if e.CursorColumn == len(e.textProvider().row(e.CursorRow)) {
  848. if e.CursorRow < e.textProvider().rows()-1 {
  849. e.CursorRow++
  850. e.CursorColumn = 0
  851. }
  852. } else {
  853. e.CursorColumn = end
  854. }
  855. }
  856. })
  857. }
  858. selectMoveWord := func(se fyne.Shortcut) {
  859. if !e.selecting {
  860. e.selectColumn = e.CursorColumn
  861. e.selectRow = e.CursorRow
  862. e.selecting = true
  863. }
  864. moveWord(se)
  865. }
  866. unselectMoveWord := func(se fyne.Shortcut) {
  867. e.selecting = false
  868. moveWord(se)
  869. }
  870. moveWordModifier := fyne.KeyModifierShortcutDefault
  871. if runtime.GOOS == "darwin" {
  872. moveWordModifier = fyne.KeyModifierAlt
  873. // Cmd+left, Cmd+right shortcuts behave like Home and End keys on Mac OS
  874. shortcutHomeEnd := func(s fyne.Shortcut) {
  875. e.selecting = false
  876. if s.(*desktop.CustomShortcut).KeyName == fyne.KeyLeft {
  877. e.typedKeyHome()
  878. } else {
  879. e.propertyLock.RLock()
  880. provider := e.textProvider()
  881. e.propertyLock.RUnlock()
  882. e.typedKeyEnd(provider)
  883. }
  884. e.Refresh()
  885. }
  886. e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyLeft, Modifier: fyne.KeyModifierSuper}, shortcutHomeEnd)
  887. e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyRight, Modifier: fyne.KeyModifierSuper}, shortcutHomeEnd)
  888. }
  889. e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyLeft, Modifier: moveWordModifier}, unselectMoveWord)
  890. e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyLeft, Modifier: moveWordModifier | fyne.KeyModifierShift}, selectMoveWord)
  891. e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyRight, Modifier: moveWordModifier}, unselectMoveWord)
  892. e.shortcut.AddShortcut(&desktop.CustomShortcut{KeyName: fyne.KeyRight, Modifier: moveWordModifier | fyne.KeyModifierShift}, selectMoveWord)
  893. }
  894. func (e *Entry) requestFocus() {
  895. impl := e.super()
  896. if c := fyne.CurrentApp().Driver().CanvasForObject(impl); c != nil {
  897. c.Focus(impl.(fyne.Focusable))
  898. }
  899. }
  900. // Obtains row,col from a given textual position
  901. // expects a read or write lock to be held by the caller
  902. func (e *Entry) rowColFromTextPos(pos int) (row int, col int) {
  903. provider := e.textProvider()
  904. canWrap := e.Wrapping == fyne.TextWrapBreak || e.Wrapping == fyne.TextWrapWord
  905. totalRows := provider.rows()
  906. for i := 0; i < totalRows; i++ {
  907. b := provider.rowBoundary(i)
  908. if b == nil {
  909. continue
  910. }
  911. if b.begin <= pos {
  912. if b.end < pos {
  913. row++
  914. }
  915. col = pos - b.begin
  916. // if this gap is at `pos` and is a line wrap, increment (safe to access boundary i-1)
  917. if canWrap && b.begin == pos && pos != 0 && provider.rowBoundary(i-1).end == b.begin && row < (totalRows-1) {
  918. row++
  919. }
  920. } else {
  921. break
  922. }
  923. }
  924. return
  925. }
  926. // selectAll selects all text in entry
  927. func (e *Entry) selectAll() {
  928. if e.textProvider().len() == 0 {
  929. return
  930. }
  931. e.setFieldsAndRefresh(func() {
  932. e.selectRow = 0
  933. e.selectColumn = 0
  934. lastRow := e.textProvider().rows() - 1
  935. e.CursorColumn = e.textProvider().rowLength(lastRow)
  936. e.CursorRow = lastRow
  937. e.selecting = true
  938. })
  939. }
  940. // selectingKeyHandler performs keypress action in the scenario that a selection
  941. // is either a) in progress or b) about to start
  942. // returns true if the keypress has been fully handled
  943. func (e *Entry) selectingKeyHandler(key *fyne.KeyEvent) bool {
  944. if e.selectKeyDown && !e.selecting {
  945. switch key.Name {
  946. case fyne.KeyUp, fyne.KeyDown,
  947. fyne.KeyLeft, fyne.KeyRight,
  948. fyne.KeyEnd, fyne.KeyHome,
  949. fyne.KeyPageUp, fyne.KeyPageDown:
  950. e.selecting = true
  951. }
  952. }
  953. if !e.selecting {
  954. return false
  955. }
  956. switch key.Name {
  957. case fyne.KeyBackspace, fyne.KeyDelete:
  958. // clears the selection -- return handled
  959. e.setFieldsAndRefresh(e.eraseSelection)
  960. e.propertyLock.Lock()
  961. if e.OnChanged != nil {
  962. e.OnChanged(e.Text)
  963. }
  964. e.propertyLock.Unlock()
  965. e.Validate()
  966. return true
  967. case fyne.KeyReturn, fyne.KeyEnter:
  968. if e.MultiLine {
  969. // clear the selection -- return unhandled to add the newline
  970. e.setFieldsAndRefresh(e.eraseSelection)
  971. }
  972. return false
  973. }
  974. if !e.selectKeyDown {
  975. switch key.Name {
  976. case fyne.KeyLeft:
  977. // seek to the start of the selection -- return handled
  978. e.propertyLock.Lock()
  979. selectStart, _ := e.selection()
  980. e.CursorRow, e.CursorColumn = e.rowColFromTextPos(selectStart)
  981. e.selecting = false
  982. e.propertyLock.Unlock()
  983. return true
  984. case fyne.KeyRight:
  985. // seek to the end of the selection -- return handled
  986. _, selectEnd := e.selection()
  987. e.propertyLock.Lock()
  988. e.CursorRow, e.CursorColumn = e.rowColFromTextPos(selectEnd)
  989. e.selecting = false
  990. e.propertyLock.Unlock()
  991. return true
  992. case fyne.KeyUp, fyne.KeyDown, fyne.KeyEnd, fyne.KeyHome, fyne.KeyPageUp, fyne.KeyPageDown:
  993. // cursor movement without left or right shift -- clear selection and return unhandled
  994. e.selecting = false
  995. return false
  996. }
  997. }
  998. return false
  999. }
  1000. // selection returns the start and end text positions for the selected span of text
  1001. // Note: this functionality depends on the relationship between the selection start row/col and
  1002. // the current cursor row/column.
  1003. // eg: (whitespace for clarity, '_' denotes cursor)
  1004. //
  1005. // "T e s [t i]_n g" == 3, 5
  1006. // "T e s_[t i] n g" == 3, 5
  1007. // "T e_[s t i] n g" == 2, 5
  1008. func (e *Entry) selection() (int, int) {
  1009. noSelection := !e.selecting || (e.CursorRow == e.selectRow && e.CursorColumn == e.selectColumn)
  1010. if noSelection {
  1011. return -1, -1
  1012. }
  1013. // Find the selection start
  1014. rowA, colA := e.CursorRow, e.CursorColumn
  1015. rowB, colB := e.selectRow, e.selectColumn
  1016. // Reposition if the cursors row is more than select start row, or if the row is the same and
  1017. // the cursors col is more that the select start column
  1018. if rowA > e.selectRow || (rowA == e.selectRow && colA > e.selectColumn) {
  1019. rowA, colA = e.selectRow, e.selectColumn
  1020. rowB, colB = e.CursorRow, e.CursorColumn
  1021. }
  1022. return e.textPosFromRowCol(rowA, colA), e.textPosFromRowCol(rowB, colB)
  1023. }
  1024. // Obtains textual position from a given row and col
  1025. // expects a read or write lock to be held by the caller
  1026. func (e *Entry) textPosFromRowCol(row, col int) int {
  1027. b := e.textProvider().rowBoundary(row)
  1028. if b == nil {
  1029. return col
  1030. }
  1031. return b.begin + col
  1032. }
  1033. func (e *Entry) syncSegments() {
  1034. colName := theme.ColorNameForeground
  1035. wrap := e.textWrap()
  1036. if e.disabled {
  1037. colName = theme.ColorNameDisabled
  1038. }
  1039. e.textProvider().Wrapping = wrap
  1040. style := RichTextStyle{
  1041. Alignment: fyne.TextAlignLeading,
  1042. ColorName: colName,
  1043. TextStyle: e.TextStyle,
  1044. }
  1045. if e.Password {
  1046. style = RichTextStylePassword
  1047. style.ColorName = colName
  1048. style.TextStyle = e.TextStyle
  1049. }
  1050. e.textProvider().Segments = []RichTextSegment{&TextSegment{
  1051. Style: style,
  1052. Text: e.Text,
  1053. }}
  1054. colName = theme.ColorNamePlaceHolder
  1055. if e.disabled {
  1056. colName = theme.ColorNameDisabled
  1057. }
  1058. e.placeholderProvider().Wrapping = wrap
  1059. e.placeholderProvider().Segments = []RichTextSegment{&TextSegment{
  1060. Style: RichTextStyle{
  1061. Alignment: fyne.TextAlignLeading,
  1062. ColorName: colName,
  1063. TextStyle: e.TextStyle,
  1064. },
  1065. Text: e.PlaceHolder,
  1066. }}
  1067. }
  1068. // textProvider returns the text handler for this entry
  1069. func (e *Entry) textProvider() *RichText {
  1070. if e.text != nil {
  1071. return e.text
  1072. }
  1073. if e.Text != "" {
  1074. e.dirty = true
  1075. }
  1076. text := NewRichTextWithText(e.Text)
  1077. text.ExtendBaseWidget(text)
  1078. text.inset = fyne.NewSize(0, theme.InputBorderSize())
  1079. e.text = text
  1080. return e.text
  1081. }
  1082. // textWrap calculates the wrapping that we should apply.
  1083. func (e *Entry) textWrap() fyne.TextWrap {
  1084. if e.Wrapping == fyne.TextTruncate { // this is now the default - but we scroll around this large content
  1085. return fyne.TextWrapOff
  1086. }
  1087. if !e.MultiLine && (e.Wrapping == fyne.TextWrapBreak || e.Wrapping == fyne.TextWrapWord) {
  1088. fyne.LogError("Entry cannot wrap single line", nil)
  1089. e.Wrapping = fyne.TextTruncate
  1090. return fyne.TextWrapOff
  1091. }
  1092. return e.Wrapping
  1093. }
  1094. func (e *Entry) updateCursorAndSelection() {
  1095. e.propertyLock.Lock()
  1096. defer e.propertyLock.Unlock()
  1097. e.CursorRow, e.CursorColumn = e.truncatePosition(e.CursorRow, e.CursorColumn)
  1098. e.selectRow, e.selectColumn = e.truncatePosition(e.selectRow, e.selectColumn)
  1099. }
  1100. func (e *Entry) updateFromData(data binding.DataItem) {
  1101. if data == nil || e.lastChange.After(time.Now().Add(-bindIgnoreDelay)) {
  1102. return
  1103. }
  1104. textSource, ok := data.(binding.String)
  1105. if !ok {
  1106. return
  1107. }
  1108. val, err := textSource.Get()
  1109. e.conversionError = err
  1110. e.Validate()
  1111. if err != nil {
  1112. return
  1113. }
  1114. e.SetText(val)
  1115. }
  1116. func (e *Entry) truncatePosition(row, col int) (int, int) {
  1117. if e.Text == "" {
  1118. return 0, 0
  1119. }
  1120. newRow := row
  1121. newCol := col
  1122. if row >= e.textProvider().rows() {
  1123. newRow = e.textProvider().rows() - 1
  1124. }
  1125. rowLength := e.textProvider().rowLength(newRow)
  1126. if (newCol >= rowLength) || (newRow < row) {
  1127. newCol = rowLength
  1128. }
  1129. return newRow, newCol
  1130. }
  1131. func (e *Entry) updateMousePointer(p fyne.Position, rightClick bool) {
  1132. row, col := e.getRowCol(p)
  1133. e.propertyLock.Lock()
  1134. if !rightClick || !e.selecting {
  1135. e.CursorRow = row
  1136. e.CursorColumn = col
  1137. }
  1138. if !e.selecting {
  1139. e.selectRow = row
  1140. e.selectColumn = col
  1141. }
  1142. e.propertyLock.Unlock()
  1143. r := cache.Renderer(e.content)
  1144. if r != nil {
  1145. r.(*entryContentRenderer).moveCursor()
  1146. }
  1147. }
  1148. // updateText updates the internal text to the given value.
  1149. // It assumes that a lock exists on the widget.
  1150. func (e *Entry) updateText(text string) bool {
  1151. changed := e.Text != text
  1152. e.Text = text
  1153. e.syncSegments()
  1154. e.text.updateRowBounds()
  1155. if e.Text != "" {
  1156. e.dirty = true
  1157. }
  1158. e.lastChange = time.Now()
  1159. if changed {
  1160. if e.binder.dataListenerPair.listener != nil {
  1161. e.binder.SetCallback(nil)
  1162. e.binder.CallWithData(e.writeData)
  1163. e.binder.SetCallback(e.updateFromData)
  1164. }
  1165. }
  1166. return changed
  1167. }
  1168. // updateTextAndRefresh updates the internal text to the given value then refreshes it.
  1169. // This should not be called under a property lock
  1170. func (e *Entry) updateTextAndRefresh(text string) {
  1171. var callback func(string)
  1172. e.setFieldsAndRefresh(func() {
  1173. changed := e.updateText(text)
  1174. if changed {
  1175. callback = e.OnChanged
  1176. }
  1177. })
  1178. e.Validate()
  1179. if callback != nil {
  1180. callback(text)
  1181. }
  1182. }
  1183. func (e *Entry) writeData(data binding.DataItem) {
  1184. if data == nil {
  1185. return
  1186. }
  1187. textTarget, ok := data.(binding.String)
  1188. if !ok {
  1189. return
  1190. }
  1191. curValue, err := textTarget.Get()
  1192. if err == nil && curValue == e.Text {
  1193. e.conversionError = nil
  1194. return
  1195. }
  1196. e.conversionError = textTarget.Set(e.Text)
  1197. }
  1198. func (e *Entry) typedKeyReturn(provider *RichText, multiLine bool) {
  1199. e.propertyLock.RLock()
  1200. onSubmitted := e.OnSubmitted
  1201. selectDown := e.selectKeyDown
  1202. text := e.Text
  1203. e.propertyLock.RUnlock()
  1204. if !multiLine {
  1205. // Single line doesn't support newline.
  1206. // Call submitted callback, if any.
  1207. if onSubmitted != nil {
  1208. onSubmitted(text)
  1209. }
  1210. return
  1211. } else if selectDown && onSubmitted != nil {
  1212. // Multiline supports newline, unless shift is held and OnSubmitted is set.
  1213. onSubmitted(text)
  1214. return
  1215. }
  1216. e.propertyLock.Lock()
  1217. provider.insertAt(e.cursorTextPos(), "\n")
  1218. e.CursorColumn = 0
  1219. e.CursorRow++
  1220. e.propertyLock.Unlock()
  1221. }
  1222. var _ fyne.WidgetRenderer = (*entryRenderer)(nil)
  1223. type entryRenderer struct {
  1224. box, border *canvas.Rectangle
  1225. scroll *widget.Scroll
  1226. objects []fyne.CanvasObject
  1227. entry *Entry
  1228. }
  1229. func (r *entryRenderer) Destroy() {
  1230. }
  1231. func (r *entryRenderer) trailingInset() float32 {
  1232. xInset := float32(0)
  1233. if r.entry.ActionItem != nil {
  1234. xInset = theme.IconInlineSize() + theme.LineSpacing()
  1235. }
  1236. if r.entry.Validator != nil {
  1237. if r.entry.ActionItem == nil {
  1238. xInset = theme.IconInlineSize() + theme.LineSpacing()
  1239. } else {
  1240. xInset += theme.IconInlineSize() + theme.LineSpacing()
  1241. }
  1242. }
  1243. return xInset
  1244. }
  1245. func (r *entryRenderer) Layout(size fyne.Size) {
  1246. // 0.5 is removed so on low DPI it rounds down on the trailing edge
  1247. r.border.Resize(fyne.NewSize(size.Width-theme.InputBorderSize()-.5, size.Height-theme.InputBorderSize()-.5))
  1248. r.border.StrokeWidth = theme.InputBorderSize()
  1249. r.border.Move(fyne.NewSquareOffsetPos(theme.InputBorderSize() / 2))
  1250. r.box.Resize(size.Subtract(fyne.NewSquareSize(theme.InputBorderSize() * 2)))
  1251. r.box.Move(fyne.NewSquareOffsetPos(theme.InputBorderSize()))
  1252. actionIconSize := fyne.NewSize(0, 0)
  1253. if r.entry.ActionItem != nil {
  1254. actionIconSize = fyne.NewSquareSize(theme.IconInlineSize())
  1255. r.entry.ActionItem.Resize(actionIconSize)
  1256. r.entry.ActionItem.Move(fyne.NewPos(size.Width-actionIconSize.Width-theme.InnerPadding(), theme.InnerPadding()))
  1257. }
  1258. validatorIconSize := fyne.NewSize(0, 0)
  1259. if r.entry.Validator != nil {
  1260. validatorIconSize = fyne.NewSquareSize(theme.IconInlineSize())
  1261. r.ensureValidationSetup()
  1262. r.entry.validationStatus.Resize(validatorIconSize)
  1263. if r.entry.ActionItem == nil {
  1264. r.entry.validationStatus.Move(fyne.NewPos(size.Width-validatorIconSize.Width-theme.InnerPadding(), theme.InnerPadding()))
  1265. } else {
  1266. r.entry.validationStatus.Move(fyne.NewPos(size.Width-validatorIconSize.Width-actionIconSize.Width-theme.InnerPadding()-theme.LineSpacing(), theme.InnerPadding()))
  1267. }
  1268. }
  1269. r.entry.textProvider().inset = fyne.NewSize(0, theme.InputBorderSize())
  1270. r.entry.placeholderProvider().inset = fyne.NewSize(0, theme.InputBorderSize())
  1271. entrySize := size.Subtract(fyne.NewSize(r.trailingInset(), theme.InputBorderSize()*2))
  1272. entryPos := fyne.NewPos(0, theme.InputBorderSize())
  1273. r.entry.propertyLock.Lock()
  1274. textPos := r.entry.textPosFromRowCol(r.entry.CursorRow, r.entry.CursorColumn)
  1275. selectPos := r.entry.textPosFromRowCol(r.entry.selectRow, r.entry.selectColumn)
  1276. r.entry.propertyLock.Unlock()
  1277. if r.entry.Wrapping == fyne.TextWrapOff && r.entry.Scroll == widget.ScrollNone {
  1278. r.entry.content.Resize(entrySize)
  1279. r.entry.content.Move(entryPos)
  1280. } else {
  1281. r.scroll.Resize(entrySize)
  1282. r.scroll.Move(entryPos)
  1283. }
  1284. r.entry.propertyLock.Lock()
  1285. resizedTextPos := r.entry.textPosFromRowCol(r.entry.CursorRow, r.entry.CursorColumn)
  1286. r.entry.propertyLock.Unlock()
  1287. if textPos != resizedTextPos {
  1288. r.entry.setFieldsAndRefresh(func() {
  1289. r.entry.CursorRow, r.entry.CursorColumn = r.entry.rowColFromTextPos(textPos)
  1290. if r.entry.selecting {
  1291. r.entry.selectRow, r.entry.selectColumn = r.entry.rowColFromTextPos(selectPos)
  1292. }
  1293. })
  1294. }
  1295. }
  1296. // MinSize calculates the minimum size of an entry widget.
  1297. // This is based on the contained text with a standard amount of padding added.
  1298. // If MultiLine is true then we will reserve space for at leasts 3 lines
  1299. func (r *entryRenderer) MinSize() fyne.Size {
  1300. if rend := cache.Renderer(r.entry.content); rend != nil {
  1301. rend.(*entryContentRenderer).updateScrollDirections()
  1302. }
  1303. if r.scroll.Direction == widget.ScrollNone {
  1304. return r.entry.content.MinSize().Add(fyne.NewSize(0, theme.InputBorderSize()*2))
  1305. }
  1306. innerPadding := theme.InnerPadding()
  1307. charMin := r.entry.placeholderProvider().charMinSize(r.entry.Password, r.entry.TextStyle)
  1308. minSize := charMin.Add(fyne.NewSquareSize(innerPadding))
  1309. if r.entry.MultiLine {
  1310. count := r.entry.multiLineRows
  1311. if count <= 0 {
  1312. count = multiLineRows
  1313. }
  1314. minSize.Height = charMin.Height*float32(count) + innerPadding
  1315. }
  1316. return minSize.Add(fyne.NewSize(innerPadding*2, innerPadding))
  1317. }
  1318. func (r *entryRenderer) Objects() []fyne.CanvasObject {
  1319. r.entry.propertyLock.RLock()
  1320. defer r.entry.propertyLock.RUnlock()
  1321. return r.objects
  1322. }
  1323. func (r *entryRenderer) Refresh() {
  1324. r.entry.propertyLock.RLock()
  1325. content := r.entry.content
  1326. focusedAppearance := r.entry.focused && !r.entry.disabled
  1327. scroll := r.entry.Scroll
  1328. size := r.entry.size
  1329. wrapping := r.entry.Wrapping
  1330. r.entry.propertyLock.RUnlock()
  1331. r.entry.syncSegments()
  1332. r.entry.text.updateRowBounds()
  1333. r.entry.placeholder.updateRowBounds()
  1334. r.entry.text.Refresh()
  1335. r.entry.placeholder.Refresh()
  1336. // correct our scroll wrappers if the wrap mode changed
  1337. entrySize := size.Subtract(fyne.NewSize(r.trailingInset(), theme.InputBorderSize()*2))
  1338. if wrapping == fyne.TextWrapOff && scroll == widget.ScrollNone && r.scroll.Content != nil {
  1339. r.scroll.Hide()
  1340. r.scroll.Content = nil
  1341. content.Move(fyne.NewPos(0, theme.InputBorderSize()))
  1342. content.Resize(entrySize)
  1343. for i, o := range r.objects {
  1344. if o == r.scroll {
  1345. r.objects[i] = content
  1346. break
  1347. }
  1348. }
  1349. } else if (wrapping != fyne.TextWrapOff || scroll != widget.ScrollNone) && r.scroll.Content == nil {
  1350. r.scroll.Content = content
  1351. content.Move(fyne.NewPos(0, 0))
  1352. r.scroll.Move(fyne.NewPos(0, theme.InputBorderSize()))
  1353. r.scroll.Resize(entrySize)
  1354. r.scroll.Show()
  1355. for i, o := range r.objects {
  1356. if o == content {
  1357. r.objects[i] = r.scroll
  1358. break
  1359. }
  1360. }
  1361. }
  1362. r.entry.updateCursorAndSelection()
  1363. r.box.FillColor = theme.InputBackgroundColor()
  1364. r.box.CornerRadius = theme.InputRadiusSize()
  1365. r.border.CornerRadius = theme.InputRadiusSize()
  1366. if focusedAppearance {
  1367. r.border.StrokeColor = theme.PrimaryColor()
  1368. } else {
  1369. if r.entry.Disabled() {
  1370. r.border.StrokeColor = theme.DisabledColor()
  1371. } else {
  1372. r.border.StrokeColor = theme.InputBorderColor()
  1373. }
  1374. }
  1375. if r.entry.ActionItem != nil {
  1376. r.entry.ActionItem.Refresh()
  1377. }
  1378. if r.entry.Validator != nil {
  1379. if !r.entry.focused && !r.entry.Disabled() && r.entry.dirty && r.entry.validationError != nil {
  1380. r.border.StrokeColor = theme.ErrorColor()
  1381. }
  1382. r.ensureValidationSetup()
  1383. r.entry.validationStatus.Refresh()
  1384. } else if r.entry.validationStatus != nil {
  1385. r.entry.validationStatus.Hide()
  1386. }
  1387. cache.Renderer(r.entry.content).Refresh()
  1388. canvas.Refresh(r.entry.super())
  1389. }
  1390. func (r *entryRenderer) ensureValidationSetup() {
  1391. if r.entry.validationStatus == nil {
  1392. r.entry.validationStatus = newValidationStatus(r.entry)
  1393. r.objects = append(r.objects, r.entry.validationStatus)
  1394. r.Layout(r.entry.size)
  1395. r.entry.Validate()
  1396. r.Refresh()
  1397. }
  1398. }
  1399. var _ fyne.Widget = (*entryContent)(nil)
  1400. type entryContent struct {
  1401. BaseWidget
  1402. entry *Entry
  1403. scroll *widget.Scroll
  1404. }
  1405. func (e *entryContent) CreateRenderer() fyne.WidgetRenderer {
  1406. e.ExtendBaseWidget(e)
  1407. e.entry.propertyLock.Lock()
  1408. defer e.entry.propertyLock.Unlock()
  1409. provider := e.entry.textProvider()
  1410. placeholder := e.entry.placeholderProvider()
  1411. if provider.len() != 0 {
  1412. placeholder.Hide()
  1413. }
  1414. objects := []fyne.CanvasObject{placeholder, provider, e.entry.cursorAnim.cursor}
  1415. r := &entryContentRenderer{e.entry.cursorAnim.cursor, []fyne.CanvasObject{}, objects,
  1416. provider, placeholder, e}
  1417. r.updateScrollDirections()
  1418. r.Layout(e.size)
  1419. return r
  1420. }
  1421. // DragEnd is called at end of a drag event.
  1422. //
  1423. // Implements: fyne.Draggable
  1424. func (e *entryContent) DragEnd() {
  1425. // we need to propagate the focus, top level widget handles focus APIs
  1426. e.entry.requestFocus()
  1427. e.entry.DragEnd()
  1428. }
  1429. // Dragged is called when the pointer moves while a button is held down.
  1430. // It updates the selection accordingly.
  1431. //
  1432. // Implements: fyne.Draggable
  1433. func (e *entryContent) Dragged(d *fyne.DragEvent) {
  1434. e.entry.Dragged(d)
  1435. }
  1436. var _ fyne.WidgetRenderer = (*entryContentRenderer)(nil)
  1437. type entryContentRenderer struct {
  1438. cursor *canvas.Rectangle
  1439. selection []fyne.CanvasObject
  1440. objects []fyne.CanvasObject
  1441. provider, placeholder *RichText
  1442. content *entryContent
  1443. }
  1444. func (r *entryContentRenderer) Destroy() {
  1445. r.content.entry.cursorAnim.stop()
  1446. }
  1447. func (r *entryContentRenderer) Layout(size fyne.Size) {
  1448. r.provider.Resize(size)
  1449. r.placeholder.Resize(size)
  1450. }
  1451. func (r *entryContentRenderer) MinSize() fyne.Size {
  1452. minSize := r.content.entry.placeholderProvider().MinSize()
  1453. if r.content.entry.textProvider().len() > 0 {
  1454. minSize = r.content.entry.text.MinSize()
  1455. }
  1456. return minSize
  1457. }
  1458. func (r *entryContentRenderer) Objects() []fyne.CanvasObject {
  1459. r.content.entry.propertyLock.RLock()
  1460. defer r.content.entry.propertyLock.RUnlock()
  1461. // Objects are generated dynamically force selection rectangles to appear underneath the text
  1462. if r.content.entry.selecting {
  1463. objs := make([]fyne.CanvasObject, 0, len(r.selection)+len(r.objects))
  1464. objs = append(objs, r.selection...)
  1465. return append(objs, r.objects...)
  1466. }
  1467. return r.objects
  1468. }
  1469. func (r *entryContentRenderer) Refresh() {
  1470. r.content.entry.propertyLock.RLock()
  1471. provider := r.content.entry.textProvider()
  1472. placeholder := r.content.entry.placeholderProvider()
  1473. focusedAppearance := r.content.entry.focused && !r.content.entry.disabled
  1474. selections := r.selection
  1475. r.updateScrollDirections()
  1476. r.content.entry.propertyLock.RUnlock()
  1477. if provider.len() == 0 {
  1478. placeholder.Show()
  1479. } else if placeholder.Visible() {
  1480. placeholder.Hide()
  1481. }
  1482. if focusedAppearance {
  1483. r.cursor.Show()
  1484. if fyne.CurrentApp().Settings().ShowAnimations() {
  1485. r.content.entry.cursorAnim.start()
  1486. }
  1487. } else {
  1488. r.content.entry.cursorAnim.stop()
  1489. r.cursor.Hide()
  1490. }
  1491. r.moveCursor()
  1492. for _, selection := range selections {
  1493. selection.(*canvas.Rectangle).Hidden = !r.content.entry.focused
  1494. selection.(*canvas.Rectangle).FillColor = theme.SelectionColor()
  1495. }
  1496. canvas.Refresh(r.content)
  1497. }
  1498. // This process builds a slice of rectangles:
  1499. // - one entry per row of text
  1500. // - ordered by row order as they occur in multiline text
  1501. // This process could be optimized in the scenario where the user is selecting upwards:
  1502. // If the upwards case instead produces an order-reversed slice then only the newest rectangle would
  1503. // require movement and resizing. The existing solution creates a new rectangle and then moves/resizes
  1504. // all rectangles to comply with the occurrence order as stated above.
  1505. func (r *entryContentRenderer) buildSelection() {
  1506. r.content.entry.propertyLock.RLock()
  1507. cursorRow, cursorCol := r.content.entry.CursorRow, r.content.entry.CursorColumn
  1508. selectRow, selectCol := -1, -1
  1509. if r.content.entry.selecting {
  1510. selectRow = r.content.entry.selectRow
  1511. selectCol = r.content.entry.selectColumn
  1512. }
  1513. r.content.entry.propertyLock.RUnlock()
  1514. if selectRow == -1 || (cursorRow == selectRow && cursorCol == selectCol) {
  1515. r.selection = r.selection[:0]
  1516. return
  1517. }
  1518. provider := r.content.entry.textProvider()
  1519. // Convert column, row into x,y
  1520. getCoordinates := func(column int, row int) (float32, float32) {
  1521. sz := provider.lineSizeToColumn(column, row)
  1522. return sz.Width, sz.Height*float32(row) - theme.InputBorderSize() + theme.InnerPadding()
  1523. }
  1524. lineHeight := r.content.entry.text.charMinSize(r.content.entry.Password, r.content.entry.TextStyle).Height
  1525. minmax := func(a, b int) (int, int) {
  1526. if a < b {
  1527. return a, b
  1528. }
  1529. return b, a
  1530. }
  1531. // The remainder of the function calculates the set of boxes and add them to r.selection
  1532. selectStartRow, selectEndRow := minmax(selectRow, cursorRow)
  1533. selectStartCol, selectEndCol := minmax(selectCol, cursorCol)
  1534. if selectRow < cursorRow {
  1535. selectStartCol, selectEndCol = selectCol, cursorCol
  1536. }
  1537. if selectRow > cursorRow {
  1538. selectStartCol, selectEndCol = cursorCol, selectCol
  1539. }
  1540. rowCount := selectEndRow - selectStartRow + 1
  1541. // trim r.selection to remove unwanted old rectangles
  1542. if len(r.selection) > rowCount {
  1543. r.selection = r.selection[:rowCount]
  1544. }
  1545. r.content.entry.propertyLock.Lock()
  1546. defer r.content.entry.propertyLock.Unlock()
  1547. // build a rectangle for each row and add it to r.selection
  1548. for i := 0; i < rowCount; i++ {
  1549. if len(r.selection) <= i {
  1550. box := canvas.NewRectangle(theme.SelectionColor())
  1551. r.selection = append(r.selection, box)
  1552. }
  1553. // determine starting/ending columns for this rectangle
  1554. row := selectStartRow + i
  1555. startCol, endCol := selectStartCol, selectEndCol
  1556. if selectStartRow < row {
  1557. startCol = 0
  1558. }
  1559. if selectEndRow > row {
  1560. endCol = provider.rowLength(row)
  1561. }
  1562. // translate columns and row into draw coordinates
  1563. x1, y1 := getCoordinates(startCol, row)
  1564. x2, _ := getCoordinates(endCol, row)
  1565. // resize and reposition each rectangle
  1566. r.selection[i].Resize(fyne.NewSize(x2-x1+1, lineHeight))
  1567. r.selection[i].Move(fyne.NewPos(x1-1, y1))
  1568. }
  1569. }
  1570. func (r *entryContentRenderer) ensureCursorVisible() {
  1571. letter := fyne.MeasureText("e", theme.TextSize(), r.content.entry.TextStyle)
  1572. padX := letter.Width*2 + theme.LineSpacing()
  1573. padY := letter.Height - theme.LineSpacing()
  1574. cx := r.cursor.Position().X
  1575. cy := r.cursor.Position().Y
  1576. cx1 := cx - padX
  1577. cy1 := cy - padY
  1578. cx2 := cx + r.cursor.Size().Width + padX
  1579. cy2 := cy + r.cursor.Size().Height + padY
  1580. offset := r.content.scroll.Offset
  1581. size := r.content.scroll.Size()
  1582. if offset.X <= cx1 && cx2 < offset.X+size.Width &&
  1583. offset.Y <= cy1 && cy2 < offset.Y+size.Height {
  1584. return
  1585. }
  1586. move := fyne.NewDelta(0, 0)
  1587. if cx1 < offset.X {
  1588. move.DX -= offset.X - cx1
  1589. } else if cx2 >= offset.X+size.Width {
  1590. move.DX += cx2 - (offset.X + size.Width)
  1591. }
  1592. if cy1 < offset.Y {
  1593. move.DY -= offset.Y - cy1
  1594. } else if cy2 >= offset.Y+size.Height {
  1595. move.DY += cy2 - (offset.Y + size.Height)
  1596. }
  1597. if r.content.scroll.Content != nil {
  1598. r.content.scroll.Offset = r.content.scroll.Offset.Add(move)
  1599. r.content.scroll.Refresh()
  1600. }
  1601. }
  1602. func (r *entryContentRenderer) moveCursor() {
  1603. // build r.selection[] if the user has made a selection
  1604. r.buildSelection()
  1605. r.content.entry.propertyLock.RLock()
  1606. provider := r.content.entry.textProvider()
  1607. size := provider.lineSizeToColumn(r.content.entry.CursorColumn, r.content.entry.CursorRow)
  1608. xPos := size.Width
  1609. yPos := size.Height * float32(r.content.entry.CursorRow)
  1610. r.content.entry.propertyLock.RUnlock()
  1611. r.content.entry.propertyLock.Lock()
  1612. lineHeight := r.content.entry.text.charMinSize(r.content.entry.Password, r.content.entry.TextStyle).Height
  1613. r.cursor.Resize(fyne.NewSize(theme.InputBorderSize(), lineHeight))
  1614. r.cursor.Move(fyne.NewPos(xPos-(theme.InputBorderSize()/2), yPos+theme.InnerPadding()-theme.InputBorderSize()))
  1615. callback := r.content.entry.OnCursorChanged
  1616. r.content.entry.propertyLock.Unlock()
  1617. r.ensureCursorVisible()
  1618. if callback != nil {
  1619. callback()
  1620. }
  1621. }
  1622. func (r *entryContentRenderer) updateScrollDirections() {
  1623. if r.content.scroll == nil { // not scrolling
  1624. return
  1625. }
  1626. switch r.content.entry.Wrapping {
  1627. case fyne.TextWrapOff:
  1628. r.content.scroll.Direction = r.content.entry.Scroll
  1629. case fyne.TextTruncate: // this is now the default - but we scroll
  1630. r.content.scroll.Direction = widget.ScrollBoth
  1631. default: // fyne.TextWrapBreak, fyne.TextWrapWord
  1632. r.content.scroll.Direction = widget.ScrollVerticalOnly
  1633. }
  1634. }
  1635. // getTextWhitespaceRegion returns the start/end markers for selection highlight on starting from col
  1636. // and expanding to the start and end of the whitespace or text underneath the specified position.
  1637. // Pass `true` for `expand` if you want whitespace selection to extend to the neighboring words.
  1638. func getTextWhitespaceRegion(row []rune, col int, expand bool) (int, int) {
  1639. if len(row) == 0 || col < 0 {
  1640. return -1, -1
  1641. }
  1642. // If the click position exceeds the length of text then snap it to the end
  1643. if col >= len(row) {
  1644. col = len(row) - 1
  1645. }
  1646. // maps: " fi-sh 日本語本語日 \t "
  1647. // into: " -- -- ------ "
  1648. space := func(r rune) rune {
  1649. if unicode.IsSpace(r) {
  1650. return ' '
  1651. }
  1652. // If this rune is a typical word separator then classify it as whitespace
  1653. if strings.ContainsRune(wordSeparator, r) {
  1654. return ' '
  1655. }
  1656. return '-'
  1657. }
  1658. toks := strings.Map(space, string(row))
  1659. c := byte(' ')
  1660. startCheck := col
  1661. endCheck := col
  1662. if expand {
  1663. if col > 0 && toks[col-1] == ' ' { // ignore the prior whitespace then count
  1664. startCheck = strings.LastIndexByte(toks[:startCheck], '-')
  1665. if startCheck == -1 {
  1666. startCheck = 0
  1667. }
  1668. }
  1669. if toks[col] == ' ' { // ignore the current whitespace then count
  1670. endCheck = col + strings.IndexByte(toks[endCheck:], '-')
  1671. }
  1672. } else if toks[col] == ' ' {
  1673. c = byte('-')
  1674. }
  1675. // LastIndexByte + 1 ensures that the position of the unwanted character ' ' is excluded
  1676. // +1 also has the added side effect whereby if ' ' isn't found then -1 is snapped to 0
  1677. start := strings.LastIndexByte(toks[:startCheck], c) + 1
  1678. // IndexByte will find the position of the next unwanted character, this is to be the end
  1679. // marker for the selection
  1680. end := strings.IndexByte(toks[endCheck:], c)
  1681. if end == -1 {
  1682. end = len(toks) // snap end to len(toks) if it results in -1
  1683. } else {
  1684. end += endCheck // otherwise include the text slice position
  1685. }
  1686. return start, end
  1687. }