Skip to content

Commit

Permalink
Merge pull request #1994 from andydotxyz/fix/1939
Browse files Browse the repository at this point in the history
Start fixing scrolling containing entries
  • Loading branch information
andydotxyz committed Feb 26, 2021
2 parents 7608746 + b9f9f6e commit 580b3de
Show file tree
Hide file tree
Showing 44 changed files with 338 additions and 301 deletions.
92 changes: 76 additions & 16 deletions widget/entry.go
Expand Up @@ -163,8 +163,15 @@ func (e *Entry) CreateRenderer() fyne.WidgetRenderer {
line := canvas.NewRectangle(theme.ShadowColor())

e.content = &entryContent{entry: e}
e.scroll = widget.NewScroll(e.content)
objects := []fyne.CanvasObject{box, line, e.scroll}
e.scroll = &widget.Scroll{}
objects := []fyne.CanvasObject{box, line}
if e.Wrapping != fyne.TextWrapOff {
e.scroll.Content = e.content
objects = append(objects, e.scroll)
} else {
e.scroll.Hide()
objects = append(objects, e.content)
}
e.content.scroll = e.scroll

if e.Password && e.ActionItem == nil {
Expand Down Expand Up @@ -1083,17 +1090,33 @@ type entryRenderer struct {
func (r *entryRenderer) Destroy() {
}

func (r *entryRenderer) trailingInset() float32 {
xInset := float32(0)

if r.entry.ActionItem != nil {
xInset = theme.IconInlineSize() + 2*theme.Padding()
}

if r.entry.Validator != nil {
if r.entry.ActionItem == nil {
xInset = theme.IconInlineSize() + 2*theme.Padding()
} else {
xInset += theme.IconInlineSize() + theme.Padding()
}
}

return xInset
}

func (r *entryRenderer) Layout(size fyne.Size) {
r.line.Resize(fyne.NewSize(size.Width, theme.InputBorderSize()))
r.line.Move(fyne.NewPos(0, size.Height-theme.InputBorderSize()))
r.box.Resize(size.Subtract(fyne.NewSize(0, theme.InputBorderSize()*2)))
r.box.Move(fyne.NewPos(0, theme.InputBorderSize()))

actionIconSize := fyne.NewSize(0, 0)
xInset := float32(0)
if r.entry.ActionItem != nil {
actionIconSize = fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize())
xInset = theme.IconInlineSize() + 2*theme.Padding()

r.entry.ActionItem.Resize(actionIconSize)
r.entry.ActionItem.Move(fyne.NewPos(size.Width-actionIconSize.Width-2*theme.Padding(), theme.Padding()*2))
Expand All @@ -1108,25 +1131,28 @@ func (r *entryRenderer) Layout(size fyne.Size) {

if r.entry.ActionItem == nil {
r.entry.validationStatus.Move(fyne.NewPos(size.Width-validatorIconSize.Width-2*theme.Padding(), theme.Padding()*2))
xInset = theme.IconInlineSize() + 2*theme.Padding()
} else {
r.entry.validationStatus.Move(fyne.NewPos(size.Width-validatorIconSize.Width-actionIconSize.Width-3*theme.Padding(), theme.Padding()*2))
xInset += theme.IconInlineSize() + theme.Padding()
}
}

entrySize := size.Subtract(fyne.NewSize(xInset, theme.InputBorderSize()*2))
entrySize := size.Subtract(fyne.NewSize(r.trailingInset(), theme.InputBorderSize()*2))
entryPos := fyne.NewPos(0, theme.InputBorderSize())
r.scroll.Resize(entrySize)
r.scroll.Move(entryPos)
if r.entry.Wrapping == fyne.TextWrapOff {
r.entry.content.Resize(entrySize)
r.entry.content.Move(entryPos)
} else {
r.scroll.Resize(entrySize)
r.scroll.Move(entryPos)
}
}

// MinSize calculates the minimum size of an entry widget.
// This is based on the contained text with a standard amount of padding added.
// If MultiLine is true then we will reserve space for at leasts 3 lines
func (r *entryRenderer) MinSize() fyne.Size {
if r.scroll.Direction == widget.ScrollNone {
return r.scroll.MinSize().Add(fyne.NewSize(0, theme.InputBorderSize()*2))
return r.entry.content.MinSize().Add(fyne.NewSize(0, theme.InputBorderSize()*2))
}

minSize := r.entry.placeholderProvider().charMinSize().Add(fyne.NewSize(theme.Padding()*2, theme.Padding()*2))
Expand All @@ -1150,12 +1176,44 @@ func (r *entryRenderer) Objects() []fyne.CanvasObject {
func (r *entryRenderer) Refresh() {
r.entry.propertyLock.RLock()
provider := r.entry.textProvider()
content := r.entry.Text
text := r.entry.Text
content := r.entry.content
focused := r.entry.focused
size := r.entry.size
wrapping := r.entry.Wrapping
r.entry.propertyLock.RUnlock()

if content != string(provider.buffer) {
r.entry.SetText(content)
// correct our scroll wrappers if the wrap mode changed
entrySize := size.Subtract(fyne.NewSize(r.trailingInset(), theme.InputBorderSize()*2))
if wrapping == fyne.TextWrapOff && r.scroll.Content != nil {
r.scroll.Hide()
r.scroll.Content = nil
content.Move(fyne.NewPos(0, theme.InputBorderSize()))
content.Resize(entrySize)

for i, o := range r.objects {
if o == r.scroll {
r.objects[i] = content
break
}
}
} else if wrapping != fyne.TextWrapOff && r.scroll.Content == nil {
r.scroll.Content = content
content.Move(fyne.NewPos(0, 0))
r.scroll.Move(fyne.NewPos(0, theme.InputBorderSize()))
r.scroll.Resize(entrySize)
r.scroll.Show()

for i, o := range r.objects {
if o == content {
r.objects[i] = r.scroll
break
}
}
}

if text != string(provider.buffer) {
r.entry.SetText(text)
return
}

Expand Down Expand Up @@ -1193,7 +1251,7 @@ func (r *entryRenderer) Refresh() {
r.entry.validationStatus.Hide()
}

cache.Renderer(r.scroll.Content.(*entryContent)).Refresh()
cache.Renderer(r.entry.content).Refresh()
canvas.Refresh(r.entry.super())
}

Expand Down Expand Up @@ -1455,8 +1513,10 @@ func (r *entryContentRenderer) ensureCursorVisible() {
} else if cy2 >= offset.X+size.Height {
move.DY += cy2 - (offset.Y + size.Height)
}
r.content.scroll.Offset = r.content.scroll.Offset.Add(move)
r.content.scroll.Refresh()
if r.content.scroll.Content != nil {
r.content.scroll.Offset = r.content.scroll.Offset.Add(move)
r.content.scroll.Refresh()
}
}

func (r *entryContentRenderer) moveCursor() {
Expand Down
42 changes: 32 additions & 10 deletions widget/entry_test.go
Expand Up @@ -1392,20 +1392,19 @@ func TestEntry_TextWrap(t *testing.T) {
"single line WrapOff": {
want: "entry/wrap_single_line_off.xml",
},
// Disallowed - fallback to TextWrapOff
"single line Truncate": {
wrap: fyne.TextTruncate,
want: "entry/wrap_single_line_off.xml",
want: "entry/wrap_single_line_truncate.xml",
},
// Disallowed - fallback to TextWrapOff
// Disallowed - fallback to TextWrapTruncate (horizontal)
"single line WrapBreak": {
wrap: fyne.TextWrapBreak,
want: "entry/wrap_single_line_off.xml",
want: "entry/wrap_single_line_truncate.xml",
},
// Disallowed - fallback to TextWrapOff
// Disallowed - fallback to TextWrapTruncate (horizontal)
"single line WrapWord": {
wrap: fyne.TextWrapWord,
want: "entry/wrap_single_line_off.xml",
want: "entry/wrap_single_line_truncate.xml",
},
"multi line WrapOff": {
multiLine: true,
Expand All @@ -1415,7 +1414,7 @@ func TestEntry_TextWrap(t *testing.T) {
"multi line Truncate": {
multiLine: true,
wrap: fyne.TextTruncate,
want: "entry/wrap_multi_line_off.xml",
want: "entry/wrap_multi_line_truncate.xml",
},
"multi line WrapBreak": {
multiLine: true,
Expand Down Expand Up @@ -1445,6 +1444,25 @@ func TestEntry_TextWrap(t *testing.T) {
}
}

func TestEntry_TextWrap_Changed(t *testing.T) {
e, window := setupImageTest(t, false)
defer teardownImageTest(window)
c := window.Canvas()

c.Focus(e)
e.Wrapping = fyne.TextWrapOff
e.SetText("Testing Wrapping")
test.AssertRendersToMarkup(t, "entry/wrap_single_line_off.xml", c)

e.Wrapping = fyne.TextTruncate
e.Refresh()
test.AssertRendersToMarkup(t, "entry/wrap_single_line_truncate.xml", c)

e.Wrapping = fyne.TextWrapOff
e.Refresh()
test.AssertRendersToMarkup(t, "entry/wrap_single_line_off.xml", c)
}

func TestMultiLineEntry_MinSize(t *testing.T) {
entry := widget.NewEntry()
singleMin := entry.MinSize()
Expand Down Expand Up @@ -1567,8 +1585,7 @@ func TestPasswordEntry_Reveal(t *testing.T) {
// the Password field is set to true.
// In this case the action item will be set when the renderer is created.
t.Run("Entry with Password field", func(t *testing.T) {
entry := &widget.Entry{}
entry.Password = true
entry := &widget.Entry{Password: true, Wrapping: fyne.TextWrapWord}
entry.Refresh()
window := test.NewWindow(entry)
defer window.Close()
Expand Down Expand Up @@ -1657,7 +1674,12 @@ func checkNewlineIgnored(t *testing.T, entry *widget.Entry) {
func setupImageTest(t *testing.T, multiLine bool) (*widget.Entry, fyne.Window) {
test.NewApp()

entry := &widget.Entry{MultiLine: multiLine}
var entry *widget.Entry
if multiLine {
entry = &widget.Entry{MultiLine: true, Wrapping: fyne.TextWrapWord}
} else {
entry = &widget.Entry{Wrapping: fyne.TextWrapOff}
}
w := test.NewWindow(entry)
w.Resize(fyne.NewSize(150, 200))

Expand Down
1 change: 1 addition & 0 deletions widget/select_entry.go
Expand Up @@ -17,6 +17,7 @@ type SelectEntry struct {
func NewSelectEntry(options []string) *SelectEntry {
e := &SelectEntry{}
e.ExtendBaseWidget(e)
e.Wrapping = fyne.TextTruncate
e.options = options
return e
}
Expand Down
8 changes: 3 additions & 5 deletions widget/testdata/entry/disableable_disabled_custom_value.xml
Expand Up @@ -3,11 +3,9 @@
<widget pos="10,10" size="120x37" type="*widget.Entry">
<rectangle fillColor="rgba(102,102,102,255)" pos="0,2" size="120x33"/>
<rectangle fillColor="disabled" pos="0,35" size="120x2"/>
<widget pos="0,2" size="120x33" type="*widget.Scroll">
<widget size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21">Hello</text>
</widget>
<widget pos="0,2" size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21">Hello</text>
</widget>
</widget>
</widget>
Expand Down
14 changes: 6 additions & 8 deletions widget/testdata/entry/disableable_disabled_empty.xml
Expand Up @@ -3,14 +3,12 @@
<widget pos="10,10" size="120x37" type="*widget.Entry">
<rectangle fillColor="rgba(102,102,102,255)" pos="0,2" size="120x33"/>
<rectangle fillColor="disabled" pos="0,35" size="120x2"/>
<widget pos="0,2" size="120x33" type="*widget.Scroll">
<widget size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21"></text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21"></text>
</widget>
<widget pos="0,2" size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21"></text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21"></text>
</widget>
</widget>
</widget>
Expand Down
14 changes: 6 additions & 8 deletions widget/testdata/entry/disableable_disabled_placeholder.xml
Expand Up @@ -3,14 +3,12 @@
<widget pos="10,10" size="120x37" type="*widget.Entry">
<rectangle fillColor="rgba(102,102,102,255)" pos="0,2" size="120x33"/>
<rectangle fillColor="disabled" pos="0,35" size="120x2"/>
<widget pos="0,2" size="120x33" type="*widget.Scroll">
<widget size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21">Type!</text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21"></text>
</widget>
<widget pos="0,2" size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21">Type!</text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text color="disabled" pos="8,6" size="112x21"></text>
</widget>
</widget>
</widget>
Expand Down
8 changes: 3 additions & 5 deletions widget/testdata/entry/disableable_enabled_custom_value.xml
Expand Up @@ -3,11 +3,9 @@
<widget pos="10,10" size="120x37" type="*widget.Entry">
<rectangle fillColor="rgba(102,102,102,255)" pos="0,2" size="120x33"/>
<rectangle fillColor="shadow" pos="0,35" size="120x2"/>
<widget pos="0,2" size="120x33" type="*widget.Scroll">
<widget size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text pos="8,6" size="112x21">Hello</text>
</widget>
<widget pos="0,2" size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text pos="8,6" size="112x21">Hello</text>
</widget>
</widget>
</widget>
Expand Down
14 changes: 6 additions & 8 deletions widget/testdata/entry/disableable_enabled_empty.xml
Expand Up @@ -3,14 +3,12 @@
<widget pos="10,10" size="120x37" type="*widget.Entry">
<rectangle fillColor="rgba(102,102,102,255)" pos="0,2" size="120x33"/>
<rectangle fillColor="shadow" pos="0,35" size="120x2"/>
<widget pos="0,2" size="120x33" type="*widget.Scroll">
<widget size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="placeholder" pos="8,6" size="112x21"></text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text pos="8,6" size="112x21"></text>
</widget>
<widget pos="0,2" size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="placeholder" pos="8,6" size="112x21"></text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text pos="8,6" size="112x21"></text>
</widget>
</widget>
</widget>
Expand Down
14 changes: 6 additions & 8 deletions widget/testdata/entry/disableable_enabled_placeholder.xml
Expand Up @@ -3,14 +3,12 @@
<widget pos="10,10" size="120x37" type="*widget.Entry">
<rectangle fillColor="rgba(102,102,102,255)" pos="0,2" size="120x33"/>
<rectangle fillColor="shadow" pos="0,35" size="120x2"/>
<widget pos="0,2" size="120x33" type="*widget.Scroll">
<widget size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="placeholder" pos="8,6" size="112x21">Type!</text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text pos="8,6" size="112x21"></text>
</widget>
<widget pos="0,2" size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="placeholder" pos="8,6" size="112x21">Type!</text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text pos="8,6" size="112x21"></text>
</widget>
</widget>
</widget>
Expand Down
16 changes: 7 additions & 9 deletions widget/testdata/entry/focus_gained.xml
Expand Up @@ -3,16 +3,14 @@
<widget pos="10,10" size="120x37" type="*widget.Entry">
<rectangle fillColor="rgba(102,102,102,255)" pos="0,2" size="120x33"/>
<rectangle fillColor="primary" pos="0,35" size="120x2"/>
<widget pos="0,2" size="120x33" type="*widget.Scroll">
<widget size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="placeholder" pos="8,6" size="112x21"></text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text pos="8,6" size="112x21"></text>
</widget>
<rectangle fillColor="primary" pos="7,6" size="2x21"/>
<widget pos="0,2" size="120x33" type="*widget.entryContent">
<widget size="120x33" type="*widget.textProvider">
<text color="placeholder" pos="8,6" size="112x21"></text>
</widget>
<widget size="120x33" type="*widget.textProvider">
<text pos="8,6" size="112x21"></text>
</widget>
<rectangle fillColor="primary" pos="7,6" size="2x21"/>
</widget>
</widget>
</content>
Expand Down

0 comments on commit 580b3de

Please sign in to comment.