Skip to content

Commit

Permalink
Merge pull request fyne-io#4560 from andydotxyz/fix/cursorcpu
Browse files Browse the repository at this point in the history
Reduce CPU consumption of Entry cursor by around 80%
  • Loading branch information
andydotxyz authored Jan 26, 2024
2 parents 77c3702 + 42ab4dd commit caf50f6
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 8 deletions.
15 changes: 15 additions & 0 deletions widget/entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"fyne.io/fyne/v2/data/binding"
"fyne.io/fyne/v2/driver/desktop"
"fyne.io/fyne/v2/driver/mobile"
"fyne.io/fyne/v2/internal/async"
"fyne.io/fyne/v2/internal/cache"
"fyne.io/fyne/v2/internal/widget"
"fyne.io/fyne/v2/theme"
Expand Down Expand Up @@ -96,6 +97,7 @@ type Entry struct {
binder basicBinder
conversionError error
lastChange time.Time
minCache async.Size
multiLineRows int // override global default number of visible lines

// undoStack stores the data necessary for undo/redo functionality
Expand Down Expand Up @@ -399,6 +401,11 @@ func (e *Entry) KeyUp(key *fyne.KeyEvent) {
//
// Implements: fyne.Widget
func (e *Entry) MinSize() fyne.Size {
cached := e.minCache.Load()
if !cached.IsZero() {
return cached
}

e.ExtendBaseWidget(e)

min := e.BaseWidget.MinSize()
Expand All @@ -409,6 +416,7 @@ func (e *Entry) MinSize() fyne.Size {
min = min.Add(fyne.NewSize(theme.IconInlineSize()+theme.LineSpacing(), 0))
}

e.minCache.Store(min)
return min
}

Expand Down Expand Up @@ -474,6 +482,12 @@ func (e *Entry) Redo() {
e.Refresh()
}

func (e *Entry) Refresh() {
e.minCache.Store(fyne.Size{})

e.BaseWidget.Refresh()
}

// SelectedText returns the text currently selected in this Entry.
// If there is no selection it will return the empty string.
func (e *Entry) SelectedText() string {
Expand All @@ -500,6 +514,7 @@ func (e *Entry) SelectedText() string {
// Since: 2.2
func (e *Entry) SetMinRowsVisible(count int) {
e.multiLineRows = count
e.Refresh()
}

// SetPlaceHolder sets the text that will be displayed if the entry is otherwise empty
Expand Down
49 changes: 42 additions & 7 deletions widget/entry_cursor_anim.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import (
"fyne.io/fyne/v2/theme"
)

const cursorInterruptTime = 300 * time.Millisecond
const (
cursorInterruptTime = 300 * time.Millisecond
cursorFadeAlpha = uint8(0x16)
cursorFadeRatio = 0.1
)

type entryCursorAnimation struct {
mu *sync.RWMutex
Expand All @@ -30,14 +34,26 @@ func newEntryCursorAnimation(cursor *canvas.Rectangle) *entryCursorAnimation {
// creates fyne animation
func (a *entryCursorAnimation) createAnim(inverted bool) *fyne.Animation {
cursorOpaque := theme.PrimaryColor()
r, g, b, _ := col.ToNRGBA(theme.PrimaryColor())
cursorDim := color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 0x16}
start, end := color.Color(cursorDim), cursorOpaque
ri, gi, bi, ai := col.ToNRGBA(theme.PrimaryColor())
r := uint8(ri >> 8)
g := uint8(gi >> 8)
b := uint8(bi >> 8)
endA := uint8(ai >> 8)
startA := cursorFadeAlpha
cursorDim := color.NRGBA{R: r, G: g, B: b, A: cursorFadeAlpha}
if inverted {
start, end = cursorOpaque, color.Color(cursorDim)
a.cursor.FillColor = cursorOpaque
startA, endA = endA, startA
} else {
a.cursor.FillColor = cursorDim
}

deltaA := endA - startA
fadeStart := float32(0.5 - cursorFadeRatio)
fadeStop := float32(0.5 + cursorFadeRatio)

interrupted := false
anim := canvas.NewColorRGBAAnimation(start, end, time.Second/2, func(c color.Color) {
anim := fyne.NewAnimation(time.Second/2, func(f float32) {
a.mu.RLock()
shouldInterrupt := a.timeNow().Sub(a.lastInterruptTime) <= cursorInterruptTime
a.mu.RUnlock()
Expand Down Expand Up @@ -67,7 +83,26 @@ func (a *entryCursorAnimation) createAnim(inverted bool) *fyne.Animation {
}()
return
}
a.cursor.FillColor = c

alpha := uint8(0)
if f < fadeStart {
if _, _, _, al := a.cursor.FillColor.RGBA(); uint8(al>>8) == cursorFadeAlpha {
return
}

a.cursor.FillColor = cursorDim
} else if f >= fadeStop {
if _, _, _, al := a.cursor.FillColor.RGBA(); al == 0xffff {
return
}

a.cursor.FillColor = cursorOpaque
} else {
fade := (f + cursorFadeRatio - 0.5) * (1 / (cursorFadeRatio * 2))
alpha = uint8(float32(deltaA) * fade)
a.cursor.FillColor = color.NRGBA{R: r, G: g, B: b, A: alpha}
}

a.cursor.Refresh()
})

Expand Down
2 changes: 1 addition & 1 deletion widget/entry_cursor_anim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestEntryCursorAnim(t *testing.T) {
alphaEquals := func(color1, color2 color.Color) bool {
_, _, _, a1 := col.ToNRGBA(color1)
_, _, _, a2 := col.ToNRGBA(color2)
return a1 == a2
return uint8(a1>>8) == uint8(a2>>8) // only check 8bit colour channels
}

cursor := canvas.NewRectangle(color.Black)
Expand Down
3 changes: 3 additions & 0 deletions widget/entry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ func TestEntry_MinSize(t *testing.T) {

min = entry.MinSize()
entry.ActionItem = canvas.NewCircle(color.Black)
entry.Refresh()
assert.Equal(t, min.Add(fyne.NewSize(theme.IconInlineSize()+theme.Padding(), 0)), entry.MinSize())
}

Expand All @@ -462,6 +463,7 @@ func TestEntryMultiline_MinSize(t *testing.T) {

min = entry.MinSize()
entry.ActionItem = canvas.NewCircle(color.Black)
entry.Refresh()
assert.Equal(t, min.Add(fyne.NewSize(theme.IconInlineSize()+theme.Padding(), 0)), entry.MinSize())
}

Expand Down Expand Up @@ -1775,6 +1777,7 @@ func TestMultiLineEntry_MinSize(t *testing.T) {
assert.True(t, multiMin.Height > singleMin.Height)

multi.MultiLine = false
multi.Refresh()
multiMin = multi.MinSize()
assert.Equal(t, singleMin.Height, multiMin.Height)
}
Expand Down

0 comments on commit caf50f6

Please sign in to comment.