entry.go 52 KB

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