diff --git a/.github/workflows/mobile_tests.yml b/.github/workflows/mobile_tests.yml new file mode 100644 index 0000000000..6362b3c124 --- /dev/null +++ b/.github/workflows/mobile_tests.yml @@ -0,0 +1,23 @@ +name: Mobile Tests +on: [push, pull_request] + +jobs: + mobile_tests: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + go-version: [1.12.x, 1.15.x] + + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: ${{ matrix.go-version }} + + - name: Get dependencies + run: sudo apt-get update && sudo apt-get install gcc libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev + + - name: Tests + run: go test -tags "ci mobile" ./... + diff --git a/.github/workflows/platform_tests.yml b/.github/workflows/platform_tests.yml index 55a7cb2d0d..bff0ebf406 100644 --- a/.github/workflows/platform_tests.yml +++ b/.github/workflows/platform_tests.yml @@ -17,7 +17,7 @@ jobs: go-version: ${{ matrix.go-version }} - name: Get dependencies - run: sudo apt-get update && sudo apt-get install golang gcc libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev + run: sudo apt-get update && sudo apt-get install gcc libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev if: ${{ runner.os == 'Linux' }} #- name: Verify go modules diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 3e44825c44..18e2dd32c0 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -12,8 +12,7 @@ jobs: - name: Get dependencies run: | - sudo apt-get update - sudo apt-get install golang gcc libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev + sudo apt-get update && sudo apt-get install gcc libgl1-mesa-dev libegl1-mesa-dev libgles2-mesa-dev libx11-dev xorg-dev GO111MODULE=off go get golang.org/x/tools/cmd/goimports GO111MODULE=off go get github.com/fzipp/gocyclo/cmd/gocyclo GO111MODULE=off go get golang.org/x/lint/golint diff --git a/CHANGELOG.md b/CHANGELOG.md index 760f4dcdd4..fcc4714e2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,51 @@ This file lists the main changes with each version of the Fyne toolkit. More detailed release notes can be found on the [releases page](https://github.com/fyne-io/fyne/releases). +## 2.0.1 - 4 March 2021 + +### Changed + +* An Entry with `Wrapping=fyne.TextWrapOff` no longer blocks scroll events from a parent + +### Fixed + +* Dialog.Resize() has no effect if called before Dialog.Show() (#1863) +* SelectTab does not always correctly set the blue underline to the selected tab (#1872) +* Entry Validation Broken when using Data binding (#1890) +* Fix background colour not applying until theme change +* android runtime error with fyne.dialog (#1896) +* Fix scale calculations for Wayland phones (PinePhone) +* Correct initial state of entry validation +* fix entry widget mouse drag selection when scrolled +* List widget panic when refreshing after changing content length (#1864) +* Fix image caching that was too agressive on resize +* Pointer and cursor misalignment in widget.Entry (#1937) +* SIGSEGV Sometimes When Closing a Program by Clicking a Button (#1604) +* Advanced Color Picker shows Black for custom primary color as RGBA (#1970) +* Canvas.Focus() before window visible causes application to crash (#1893) +* Menu over Content (#1973) +* Error compiling fyne on Apple M1 arm64 (#1739) +* Cells are not getting draw in correct location after column resize. (#1951) +* Possible panic when selecting text in a widget.Entry (#1983) +* Form validation doesn't enable submit button (#1965) +* Creating a window shows it before calling .Show() and .Hide() does not work (#1835) +* Dialogs are not refreshed correctly on .Show() (#1866) +* Failed creating setting storage : no such directory (#2023) +* Erroneous custom filter types not supported error on mobile (#2012) +* High importance button show no hovered state (#1785) +* List widget does not render all visible content after content data gets shorter (#1948) +* Calling Select on List before draw can crash (#1960) +* Dialog not resizing in newly created window (#1692) +* Dialog not returning to requested size (#1382) +* Entry without scrollable content prevents scrolling of outside scroller (#1939) +* fyne_demo crash after selecting custom Theme and table (#2018) +* Table widget crash when scrolling rapidly (#1887) +* Cursor animation sometimes distorts the text (#1778) +* Extended password entry panics when password revealer is clicked (#2036) +* Data binding limited to 1024 simultaneous operations (#1838) +* Custom theme does not refresh when variant changes (#2006) + + ## 2.0 - 22 January 2021 ### Changes that are not backward compatible diff --git a/README.md b/README.md index 92122bc9fe..552a88a870 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Go API Reference - 2.0.0 release + 2.0.1 release Join us on Slack
Code Status @@ -10,7 +10,7 @@ # About -[Fyne](https://fyne.io) is an easy to use UI toolkit and app API written in Go. +[Fyne](https://fyne.io) is an easy-to-use UI toolkit and app API written in Go. It is designed to build applications that run on desktop and mobile devices with a single codebase. @@ -42,13 +42,19 @@ To run a showcase of the features of Fyne execute the following: And you should see something like this (after you click a few buttons):

- Fyne Hello Light Theme + Fyne Demo Dark Theme

Or if you are using the light theme:

- Fyne Hello Light Theme + Fyne Demo Light Theme +

+ +And even running on a mobile device: + +

+ Fyne Demo Mobile Light Theme

# Getting Started @@ -86,7 +92,7 @@ func main() { And you can run that simply as: - go run main.go + $ go run main.go It should look like this: @@ -103,6 +109,12 @@ It should look like this: > Note that Windows applications load from a command prompt by default, which means if you click an icon you may see a command window. > To fix this add the parameters `-ldflags -H=windowsgui` to your run or build commands. +## Run in mobile simulation + +There is a helpful mobile simulation mode that gives a hint of how your app would work on a mobile device: + + $ go run -tags mobile main.go + # Installing Using `go install` will copy the executable into your go `bin` dir. @@ -112,7 +124,17 @@ application location you can use the fyne utility and the "install" subcommand. $ go get fyne.io/fyne/v2/cmd/fyne $ fyne install -# Packaging a release +# Packaging for mobile + +To run on a mobile device it is necessary to package up the application. +To do this we can use the fyne utility "package" subcommand. +You will need to add appropriate parameters as prompted, but the basic command is shown below. +Once packaged you can install using the platform development tools or the fyne "install" subcommand. + + $ fyne package -os android -appID my.domain.appname + $ fyne install -os android + +# Preparing a release Using the fyne utility "release" subcommand you can package up your app for release to app stores and market places. Make sure you have the standard build tools installed diff --git a/app/settings.go b/app/settings.go index 8da8ccc207..01078999c6 100644 --- a/app/settings.go +++ b/app/settings.go @@ -139,26 +139,36 @@ func (s *settings) fileChanged() { } func (s *settings) setupTheme() { - if s.themeSpecified { - return - } name := s.schema.ThemeName if env := os.Getenv("FYNE_THEME"); env != "" { - s.themeSpecified = true name = env } - if name == "light" { - s.applyTheme(theme.LightTheme(), theme.VariantLight) - } else if name == "dark" { - s.applyTheme(theme.DarkTheme(), theme.VariantDark) - } else { - if defaultVariant() == theme.VariantLight { - s.applyTheme(theme.LightTheme(), theme.VariantLight) - } else { - s.applyTheme(theme.DarkTheme(), theme.VariantDark) + var variant fyne.ThemeVariant + effectiveTheme := s.theme + switch name { + case "light": + variant = theme.VariantLight + if !s.themeSpecified { + effectiveTheme = theme.LightTheme() + } + case "dark": + variant = theme.VariantDark + if !s.themeSpecified { + effectiveTheme = theme.DarkTheme() + } + default: + variant = defaultVariant() + if s.themeSpecified { + break + } + effectiveTheme = theme.DarkTheme() + if variant == theme.VariantLight { + effectiveTheme = theme.LightTheme() } } + + s.applyTheme(effectiveTheme, variant) } func loadSettings() *settings { diff --git a/app/settings_desktop.go b/app/settings_desktop.go index 6f7f785558..0a9b3420e4 100644 --- a/app/settings_desktop.go +++ b/app/settings_desktop.go @@ -25,7 +25,7 @@ func ensureDirExists(dir string) { return } - err := os.Mkdir(dir, 0700) + err := os.MkdirAll(dir, 0700) if err != nil { fyne.LogError("Unable to create settings storage:", err) } diff --git a/app/settings_test.go b/app/settings_test.go index ea2368883e..67592838c7 100644 --- a/app/settings_test.go +++ b/app/settings_test.go @@ -76,16 +76,68 @@ func TestOverrideTheme_IgnoresSettingsChange(t *testing.T) { } set.setupTheme() assert.Equal(t, theme.LightTheme(), set.Theme()) + + err = set.loadFromFile(filepath.Join("testdata", "dark-theme.json")) + if err != nil { + t.Error(err) + } + set.setupTheme() + assert.Equal(t, theme.LightTheme(), set.Theme()) err = os.Setenv("FYNE_THEME", "") if err != nil { t.Error(err) } +} + +func TestCustomTheme(t *testing.T) { + type customTheme struct { + fyne.Theme + } + set := &settings{} + ctheme := &customTheme{theme.LightTheme()} + set.SetTheme(ctheme) + + set.setupTheme() + assert.True(t, set.Theme() == ctheme) + assert.Equal(t, defaultVariant(), set.ThemeVariant()) + + err := set.loadFromFile(filepath.Join("testdata", "light-theme.json")) + if err != nil { + t.Error(err) + } + set.setupTheme() + assert.True(t, set.Theme() == ctheme) + assert.Equal(t, theme.VariantLight, set.ThemeVariant()) err = set.loadFromFile(filepath.Join("testdata", "dark-theme.json")) if err != nil { t.Error(err) } + set.setupTheme() + assert.True(t, set.Theme() == ctheme) + assert.Equal(t, theme.VariantDark, set.ThemeVariant()) + err = os.Setenv("FYNE_THEME", "light") + if err != nil { + t.Error(err) + } set.setupTheme() - assert.Equal(t, theme.LightTheme(), set.Theme()) + assert.True(t, set.Theme() == ctheme) + assert.Equal(t, theme.VariantLight, set.ThemeVariant()) + + err = os.Setenv("FYNE_THEME", "dark") + if err != nil { + t.Error(err) + } + set.setupTheme() + assert.True(t, set.Theme() == ctheme) + assert.Equal(t, theme.VariantDark, set.ThemeVariant()) + + err = os.Setenv("FYNE_THEME", "") + if err != nil { + t.Error(err) + } + set.setupTheme() + assert.True(t, set.Theme() == ctheme) + assert.Equal(t, theme.VariantDark, set.ThemeVariant()) } diff --git a/canvas/base.go b/canvas/base.go index da2ff35419..d7b5d3f158 100644 --- a/canvas/base.go +++ b/canvas/base.go @@ -14,8 +14,8 @@ import ( ) type baseObject struct { - size fyne.Size // The current size of the Rectangle - position fyne.Position // The current position of the Rectangle + size fyne.Size // The current size of the canvas object + position fyne.Position // The current position of the object Hidden bool // Is this object currently hidden min fyne.Size // The minimum size this object can be @@ -23,7 +23,7 @@ type baseObject struct { propertyLock sync.RWMutex } -// CurrentSize returns the current size of this rectangle object +// CurrentSize returns the current size of this canvas object. func (r *baseObject) Size() fyne.Size { r.propertyLock.RLock() defer r.propertyLock.RUnlock() @@ -31,7 +31,7 @@ func (r *baseObject) Size() fyne.Size { return r.size } -// Resize sets a new size for the rectangle object +// Resize sets a new size for the canvas object. func (r *baseObject) Resize(size fyne.Size) { r.propertyLock.Lock() defer r.propertyLock.Unlock() @@ -39,7 +39,7 @@ func (r *baseObject) Resize(size fyne.Size) { r.size = size } -// CurrentPosition gets the current position of this rectangle object, relative to its parent / canvas +// CurrentPosition gets the current position of this canvas object, relative to its parent. func (r *baseObject) Position() fyne.Position { r.propertyLock.RLock() defer r.propertyLock.RUnlock() @@ -47,7 +47,7 @@ func (r *baseObject) Position() fyne.Position { return r.position } -// Move the rectangle object to a new position, relative to its parent / canvas +// Move the object to a new position, relative to its parent. func (r *baseObject) Move(pos fyne.Position) { r.propertyLock.Lock() defer r.propertyLock.Unlock() @@ -55,7 +55,7 @@ func (r *baseObject) Move(pos fyne.Position) { r.position = pos } -// MinSize returns the specified minimum size, if set, or {1, 1} otherwise +// MinSize returns the specified minimum size, if set, or {1, 1} otherwise. func (r *baseObject) MinSize() fyne.Size { r.propertyLock.RLock() defer r.propertyLock.RUnlock() @@ -67,7 +67,7 @@ func (r *baseObject) MinSize() fyne.Size { return r.min } -// SetMinSize specifies the smallest size this object should be +// SetMinSize specifies the smallest size this object should be. func (r *baseObject) SetMinSize(size fyne.Size) { r.propertyLock.Lock() defer r.propertyLock.Unlock() @@ -75,7 +75,7 @@ func (r *baseObject) SetMinSize(size fyne.Size) { r.min = size } -// IsVisible returns true if this object is visible, false otherwise +// IsVisible returns true if this object is visible, false otherwise. func (r *baseObject) Visible() bool { r.propertyLock.RLock() defer r.propertyLock.RUnlock() @@ -83,7 +83,7 @@ func (r *baseObject) Visible() bool { return !r.Hidden } -// Show will set this object to be visible +// Show will set this object to be visible. func (r *baseObject) Show() { r.propertyLock.Lock() defer r.propertyLock.Unlock() @@ -91,7 +91,7 @@ func (r *baseObject) Show() { r.Hidden = false } -// Hide will set this object to not be visible +// Hide will set this object to not be visible. func (r *baseObject) Hide() { r.propertyLock.Lock() defer r.propertyLock.Unlock() diff --git a/canvas/image.go b/canvas/image.go index 01c668332e..36334ba372 100644 --- a/canvas/image.go +++ b/canvas/image.go @@ -68,15 +68,12 @@ func (i *Image) Alpha() float64 { return 1.0 - i.Translucency } -// Resize on an image will usually scale the content or reposition it according to FillMode.. -// If the content of the File or Resource is an SVG file, however, this will cause a Refresh. +// Resize on an image will scale the content or reposition it according to FillMode. +// It will normally cause a Refresh to ensure the pixels are recalculated. func (i *Image) Resize(s fyne.Size) { i.baseObject.Resize(s) - if (i.File != "" && filepath.Ext(i.File) == ".svg") || - (i.Resource != nil && filepath.Ext(i.Resource.Name()) == ".svg") { - Refresh(i) - } + Refresh(i) } // Refresh causes this object to be redrawn in it's current state diff --git a/cmd/fyne/commands/bundle.go b/cmd/fyne/commands/bundle.go index 296a4e45c0..2019b1a580 100644 --- a/cmd/fyne/commands/bundle.go +++ b/cmd/fyne/commands/bundle.go @@ -160,7 +160,7 @@ func writeResource(file, name string, f io.Writer) { return } - v := fmt.Sprintf("var %s = &fyne.StaticResource{\n\tStaticName: %q,\n\tStaticContent: []byte(%q),\n}\n", name, staticRes.StaticName, staticRes.StaticContent) + v := fmt.Sprintf("var %s = &fyne.StaticResource{\n\tStaticName: %q,\n\tStaticContent: []byte(\n\t\t%q),\n}\n", name, staticRes.StaticName, staticRes.StaticContent) _, err = f.Write([]byte(v)) if err != nil { fyne.LogError("Unable to write to bundled file", err) diff --git a/cmd/fyne_settings/settings/appearance.go b/cmd/fyne_settings/settings/appearance.go index 96552341bc..ec44d9d62f 100644 --- a/cmd/fyne_settings/settings/appearance.go +++ b/cmd/fyne_settings/settings/appearance.go @@ -75,6 +75,9 @@ func (s *Settings) LoadAppearanceScreen(w fyne.Window) fyne.CanvasObject { box.Add(widget.NewCard("Appearance", "", appearance)) bottom := container.NewHBox(layout.NewSpacer(), &widget.Button{Text: "Apply", Importance: widget.HighImportance, OnTapped: func() { + if s.fyneSettings.Scale == 0.0 { + s.chooseScale(1.0) + } err := s.save() if err != nil { fyne.LogError("Failed on saving", err) diff --git a/container/apptabs.go b/container/apptabs.go index 9f2631e1e0..ccebc08dd4 100644 --- a/container/apptabs.go +++ b/container/apptabs.go @@ -56,7 +56,7 @@ func NewAppTabs(items ...*TabItem) *AppTabs { // Current is first tab item tabs.current = 0 } - tabs.ExtendBaseWidget(tabs) + tabs.BaseWidget.ExtendBaseWidget(tabs) if tabs.mismatchedContent() { internal.LogHint("AppTabs items should all have the same type of content (text, icons or both)") @@ -86,7 +86,7 @@ func (c *AppTabs) Append(item *TabItem) { // CreateRenderer is a private method to Fyne which links this widget to its renderer func (c *AppTabs) CreateRenderer() fyne.WidgetRenderer { - c.ExtendBaseWidget(c) + c.BaseWidget.ExtendBaseWidget(c) r := &appTabsRenderer{line: canvas.NewRectangle(theme.ShadowColor()), underline: canvas.NewRectangle(theme.PrimaryColor()), container: c} r.updateTabs() @@ -106,9 +106,16 @@ func (c *AppTabs) CurrentTabIndex() int { return c.current } +// ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality. +// +// Deprecated: Support for extending containers is being removed +func (c *AppTabs) ExtendBaseWidget(wid fyne.Widget) { + c.BaseWidget.ExtendBaseWidget(wid) +} + // MinSize returns the size that this widget should not shrink below func (c *AppTabs) MinSize() fyne.Size { - c.ExtendBaseWidget(c) + c.BaseWidget.ExtendBaseWidget(c) return c.BaseWidget.MinSize() } @@ -380,24 +387,28 @@ func (r *appTabsRenderer) moveSelection() { } r.underline.Show() - if r.underline.Position().IsZero() || r.underline.Position() == underlinePos { + if r.underline.Position().IsZero() { r.underline.Move(underlinePos) r.underline.Resize(underlineSize) - } else if r.animation == nil { - r.animation = canvas.NewPositionAnimation(r.underline.Position(), underlinePos, canvas.DurationShort, func(p fyne.Position) { - r.underline.Move(p) - canvas.Refresh(r.underline) - if p == underlinePos { - r.animation = nil - } - }) - r.animation.Start() + return + } - canvas.NewSizeAnimation(r.underline.Size(), underlineSize, canvas.DurationShort, func(s fyne.Size) { - r.underline.Resize(s) - canvas.Refresh(r.underline) - }).Start() + if r.animation != nil { + r.animation.Stop() } + r.animation = canvas.NewPositionAnimation(r.underline.Position(), underlinePos, canvas.DurationShort, func(p fyne.Position) { + r.underline.Move(p) + canvas.Refresh(r.underline) + if p == underlinePos { + r.animation = nil + } + }) + r.animation.Start() + + canvas.NewSizeAnimation(r.underline.Size(), underlineSize, canvas.DurationShort, func(s fyne.Size) { + r.underline.Resize(s) + canvas.Refresh(r.underline) + }).Start() } func (r *appTabsRenderer) tabsInSync() bool { diff --git a/container/apptabs_internal_test.go b/container/apptabs_internal_test.go index 57199b8240..87b60782b1 100644 --- a/container/apptabs_internal_test.go +++ b/container/apptabs_internal_test.go @@ -27,3 +27,21 @@ func Test_tabButtonRenderer_SetText(t *testing.T) { renderer = cache.Renderer(button).(*tabButtonRenderer) assert.Equal(t, "Replace", renderer.label.Text) } + +func Test_tabButtonRenderer_DeleteAdd(t *testing.T) { + item1 := &TabItem{Text: "Test", Content: widget.NewLabel("Content")} + item2 := &TabItem{Text: "Delete", Content: widget.NewLabel("Delete")} + tabs := NewAppTabs(item1, item2) + tabRenderer := cache.Renderer(tabs).(*appTabsRenderer) + underline := tabRenderer.underline + + pos := underline.Position() + tabs.SelectTab(item2) + assert.NotEqual(t, pos, underline.Position()) + pos = underline.Position() + + tabs.Remove(item2) + tabs.Append(item2) + tabs.SelectTab(item2) + assert.Equal(t, pos, underline.Position()) +} diff --git a/container/appTabs_test.go b/container/apptabs_test.go similarity index 100% rename from container/appTabs_test.go rename to container/apptabs_test.go diff --git a/container/split.go b/container/split.go index 10fdbdc881..caef2e8c1b 100644 --- a/container/split.go +++ b/container/split.go @@ -45,13 +45,13 @@ func newSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject) *Sp Leading: leading, Trailing: trailing, } - s.ExtendBaseWidget(s) + s.BaseWidget.ExtendBaseWidget(s) return s } // CreateRenderer is a private method to Fyne which links this widget to its renderer func (s *Split) CreateRenderer() fyne.WidgetRenderer { - s.ExtendBaseWidget(s) + s.BaseWidget.ExtendBaseWidget(s) d := newDivider(s) return &splitContainerRenderer{ split: s, @@ -60,6 +60,13 @@ func (s *Split) CreateRenderer() fyne.WidgetRenderer { } } +// ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality. +// +// Deprecated: Support for extending containers is being removed +func (s *Split) ExtendBaseWidget(wid fyne.Widget) { + s.BaseWidget.ExtendBaseWidget(wid) +} + // SetOffset sets the offset (0.0 to 1.0) of the Split divider. // 0.0 - Leading is min size, Trailing uses all remaining space. // 0.5 - Leading & Trailing equally share the available space. diff --git a/container/testdata/apptabs/desktop/change_content_change_hidden.xml b/container/testdata/apptabs/desktop/change_content_change_hidden.xml index 40e6092b23..26687a1ddb 100644 --- a/container/testdata/apptabs/desktop/change_content_change_hidden.xml +++ b/container/testdata/apptabs/desktop/change_content_change_hidden.xml @@ -2,7 +2,7 @@ - Text3 + Text3 diff --git a/container/testdata/apptabs/desktop/change_content_change_visible.xml b/container/testdata/apptabs/desktop/change_content_change_visible.xml index 40e6092b23..26687a1ddb 100644 --- a/container/testdata/apptabs/desktop/change_content_change_visible.xml +++ b/container/testdata/apptabs/desktop/change_content_change_visible.xml @@ -2,7 +2,7 @@ - Text3 + Text3 diff --git a/container/testdata/apptabs/desktop/change_content_initial.xml b/container/testdata/apptabs/desktop/change_content_initial.xml index 00cdc02f2a..167ce72824 100644 --- a/container/testdata/apptabs/desktop/change_content_initial.xml +++ b/container/testdata/apptabs/desktop/change_content_initial.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/change_icon_change_selected.xml b/container/testdata/apptabs/desktop/change_icon_change_selected.xml index a8f09cdc20..3b0153e95d 100644 --- a/container/testdata/apptabs/desktop/change_icon_change_selected.xml +++ b/container/testdata/apptabs/desktop/change_icon_change_selected.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/change_icon_change_unselected.xml b/container/testdata/apptabs/desktop/change_icon_change_unselected.xml index 83dfbe449b..043c1b0317 100644 --- a/container/testdata/apptabs/desktop/change_icon_change_unselected.xml +++ b/container/testdata/apptabs/desktop/change_icon_change_unselected.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/change_icon_initial.xml b/container/testdata/apptabs/desktop/change_icon_initial.xml index ff7f3e8635..f70ef6fbe5 100644 --- a/container/testdata/apptabs/desktop/change_icon_initial.xml +++ b/container/testdata/apptabs/desktop/change_icon_initial.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/change_label_change_selected.xml b/container/testdata/apptabs/desktop/change_label_change_selected.xml index d9e14ef920..5412067384 100644 --- a/container/testdata/apptabs/desktop/change_label_change_selected.xml +++ b/container/testdata/apptabs/desktop/change_label_change_selected.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/change_label_change_unselected.xml b/container/testdata/apptabs/desktop/change_label_change_unselected.xml index c657035a70..940f94ff04 100644 --- a/container/testdata/apptabs/desktop/change_label_change_unselected.xml +++ b/container/testdata/apptabs/desktop/change_label_change_unselected.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/change_label_initial.xml b/container/testdata/apptabs/desktop/change_label_initial.xml index 00cdc02f2a..167ce72824 100644 --- a/container/testdata/apptabs/desktop/change_label_initial.xml +++ b/container/testdata/apptabs/desktop/change_label_initial.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/dynamic_appended.xml b/container/testdata/apptabs/desktop/dynamic_appended.xml index 4fbb658872..14c622cb00 100644 --- a/container/testdata/apptabs/desktop/dynamic_appended.xml +++ b/container/testdata/apptabs/desktop/dynamic_appended.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/desktop/dynamic_appended_and_removed.xml b/container/testdata/apptabs/desktop/dynamic_appended_and_removed.xml index 40082ad5fa..edc6ca7809 100644 --- a/container/testdata/apptabs/desktop/dynamic_appended_and_removed.xml +++ b/container/testdata/apptabs/desktop/dynamic_appended_and_removed.xml @@ -2,7 +2,7 @@ - Text 2 + Text 2 diff --git a/container/testdata/apptabs/desktop/dynamic_appended_another_three.xml b/container/testdata/apptabs/desktop/dynamic_appended_another_three.xml index c87e9f4c00..48634e09da 100644 --- a/container/testdata/apptabs/desktop/dynamic_appended_another_three.xml +++ b/container/testdata/apptabs/desktop/dynamic_appended_another_three.xml @@ -2,7 +2,7 @@ - Text 2 + Text 2 diff --git a/container/testdata/apptabs/desktop/dynamic_initial.xml b/container/testdata/apptabs/desktop/dynamic_initial.xml index d00ca13516..b040be263a 100644 --- a/container/testdata/apptabs/desktop/dynamic_initial.xml +++ b/container/testdata/apptabs/desktop/dynamic_initial.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/desktop/dynamic_replaced_completely.xml b/container/testdata/apptabs/desktop/dynamic_replaced_completely.xml index f6dba008ea..002926c2df 100644 --- a/container/testdata/apptabs/desktop/dynamic_replaced_completely.xml +++ b/container/testdata/apptabs/desktop/dynamic_replaced_completely.xml @@ -2,7 +2,7 @@ - Text 6 + Text 6 diff --git a/container/testdata/apptabs/desktop/hover_first.xml b/container/testdata/apptabs/desktop/hover_first.xml index 90533fd6c0..c85dd829e0 100644 --- a/container/testdata/apptabs/desktop/hover_first.xml +++ b/container/testdata/apptabs/desktop/hover_first.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/hover_none.xml b/container/testdata/apptabs/desktop/hover_none.xml index 00cdc02f2a..167ce72824 100644 --- a/container/testdata/apptabs/desktop/hover_none.xml +++ b/container/testdata/apptabs/desktop/hover_none.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/hover_second.xml b/container/testdata/apptabs/desktop/hover_second.xml index eb4d7a705f..9746c3e9a6 100644 --- a/container/testdata/apptabs/desktop/hover_second.xml +++ b/container/testdata/apptabs/desktop/hover_second.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/desktop/single_custom_theme.png b/container/testdata/apptabs/desktop/single_custom_theme.png index 69914fd22f..1866dbe5cc 100644 Binary files a/container/testdata/apptabs/desktop/single_custom_theme.png and b/container/testdata/apptabs/desktop/single_custom_theme.png differ diff --git a/container/testdata/apptabs/desktop/single_initial.png b/container/testdata/apptabs/desktop/single_initial.png index ecffb3809a..db9f2568cd 100644 Binary files a/container/testdata/apptabs/desktop/single_initial.png and b/container/testdata/apptabs/desktop/single_initial.png differ diff --git a/container/testdata/apptabs/desktop/tab_location_bottom.xml b/container/testdata/apptabs/desktop/tab_location_bottom.xml index 30e263a3f2..364d59ebb1 100644 --- a/container/testdata/apptabs/desktop/tab_location_bottom.xml +++ b/container/testdata/apptabs/desktop/tab_location_bottom.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/desktop/tab_location_leading.xml b/container/testdata/apptabs/desktop/tab_location_leading.xml index fd6f863487..08374cebaf 100644 --- a/container/testdata/apptabs/desktop/tab_location_leading.xml +++ b/container/testdata/apptabs/desktop/tab_location_leading.xml @@ -1,8 +1,8 @@ - + - - - Text 1 + + + Text 1 diff --git a/container/testdata/apptabs/desktop/tab_location_top.xml b/container/testdata/apptabs/desktop/tab_location_top.xml index 9254aa733c..fb56676a92 100644 --- a/container/testdata/apptabs/desktop/tab_location_top.xml +++ b/container/testdata/apptabs/desktop/tab_location_top.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/desktop/tab_location_trailing.xml b/container/testdata/apptabs/desktop/tab_location_trailing.xml index a5cdf55e4d..e684262ba2 100644 --- a/container/testdata/apptabs/desktop/tab_location_trailing.xml +++ b/container/testdata/apptabs/desktop/tab_location_trailing.xml @@ -1,10 +1,10 @@ - + - - - Text 1 + + + Text 1 - + Test1 @@ -15,8 +15,8 @@ Test3 - - + + diff --git a/container/testdata/apptabs/desktop/tapped_first_selected.xml b/container/testdata/apptabs/desktop/tapped_first_selected.xml index 41af09bb4b..1120c51ab6 100644 --- a/container/testdata/apptabs/desktop/tapped_first_selected.xml +++ b/container/testdata/apptabs/desktop/tapped_first_selected.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/desktop/tapped_second_selected.xml b/container/testdata/apptabs/desktop/tapped_second_selected.xml index 646a0c6e26..43ed6a698d 100644 --- a/container/testdata/apptabs/desktop/tapped_second_selected.xml +++ b/container/testdata/apptabs/desktop/tapped_second_selected.xml @@ -2,7 +2,7 @@ - Text 2 + Text 2 diff --git a/container/testdata/apptabs/desktop/tapped_third_selected.xml b/container/testdata/apptabs/desktop/tapped_third_selected.xml index 1582684498..13159a6b1b 100644 --- a/container/testdata/apptabs/desktop/tapped_third_selected.xml +++ b/container/testdata/apptabs/desktop/tapped_third_selected.xml @@ -2,7 +2,7 @@ - Text 3 + Text 3 diff --git a/container/testdata/apptabs/mobile/change_content_change_hidden.xml b/container/testdata/apptabs/mobile/change_content_change_hidden.xml index 1aca7c473b..dd16604e28 100644 --- a/container/testdata/apptabs/mobile/change_content_change_hidden.xml +++ b/container/testdata/apptabs/mobile/change_content_change_hidden.xml @@ -2,7 +2,7 @@ - Text3 + Text3 diff --git a/container/testdata/apptabs/mobile/change_content_change_visible.xml b/container/testdata/apptabs/mobile/change_content_change_visible.xml index 1aca7c473b..dd16604e28 100644 --- a/container/testdata/apptabs/mobile/change_content_change_visible.xml +++ b/container/testdata/apptabs/mobile/change_content_change_visible.xml @@ -2,7 +2,7 @@ - Text3 + Text3 diff --git a/container/testdata/apptabs/mobile/change_content_initial.xml b/container/testdata/apptabs/mobile/change_content_initial.xml index f84948ef92..230e3821fa 100644 --- a/container/testdata/apptabs/mobile/change_content_initial.xml +++ b/container/testdata/apptabs/mobile/change_content_initial.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/mobile/change_icon_change_selected.xml b/container/testdata/apptabs/mobile/change_icon_change_selected.xml index dcafeaa3d4..48e1969700 100644 --- a/container/testdata/apptabs/mobile/change_icon_change_selected.xml +++ b/container/testdata/apptabs/mobile/change_icon_change_selected.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/mobile/change_icon_change_unselected.xml b/container/testdata/apptabs/mobile/change_icon_change_unselected.xml index c29619ca6d..40e7e37e74 100644 --- a/container/testdata/apptabs/mobile/change_icon_change_unselected.xml +++ b/container/testdata/apptabs/mobile/change_icon_change_unselected.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/mobile/change_icon_initial.xml b/container/testdata/apptabs/mobile/change_icon_initial.xml index 76f2dd6b23..52adc55b1e 100644 --- a/container/testdata/apptabs/mobile/change_icon_initial.xml +++ b/container/testdata/apptabs/mobile/change_icon_initial.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/mobile/change_label_change_selected.xml b/container/testdata/apptabs/mobile/change_label_change_selected.xml index 37771f7182..ec20cd902f 100644 --- a/container/testdata/apptabs/mobile/change_label_change_selected.xml +++ b/container/testdata/apptabs/mobile/change_label_change_selected.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/mobile/change_label_change_unselected.xml b/container/testdata/apptabs/mobile/change_label_change_unselected.xml index 373c8461fb..c871f52b5e 100644 --- a/container/testdata/apptabs/mobile/change_label_change_unselected.xml +++ b/container/testdata/apptabs/mobile/change_label_change_unselected.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/mobile/change_label_initial.xml b/container/testdata/apptabs/mobile/change_label_initial.xml index f84948ef92..230e3821fa 100644 --- a/container/testdata/apptabs/mobile/change_label_initial.xml +++ b/container/testdata/apptabs/mobile/change_label_initial.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/mobile/dynamic_appended.xml b/container/testdata/apptabs/mobile/dynamic_appended.xml index b5f09e2458..c237a12381 100644 --- a/container/testdata/apptabs/mobile/dynamic_appended.xml +++ b/container/testdata/apptabs/mobile/dynamic_appended.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/mobile/dynamic_appended_and_removed.xml b/container/testdata/apptabs/mobile/dynamic_appended_and_removed.xml index e4793b66e4..de902c1d74 100644 --- a/container/testdata/apptabs/mobile/dynamic_appended_and_removed.xml +++ b/container/testdata/apptabs/mobile/dynamic_appended_and_removed.xml @@ -2,7 +2,7 @@ - Text 2 + Text 2 diff --git a/container/testdata/apptabs/mobile/dynamic_appended_another_three.xml b/container/testdata/apptabs/mobile/dynamic_appended_another_three.xml index 05a34e4db3..a11eb81738 100644 --- a/container/testdata/apptabs/mobile/dynamic_appended_another_three.xml +++ b/container/testdata/apptabs/mobile/dynamic_appended_another_three.xml @@ -2,7 +2,7 @@ - Text 2 + Text 2 diff --git a/container/testdata/apptabs/mobile/dynamic_initial.xml b/container/testdata/apptabs/mobile/dynamic_initial.xml index f0d5e67dbc..cd3564e23e 100644 --- a/container/testdata/apptabs/mobile/dynamic_initial.xml +++ b/container/testdata/apptabs/mobile/dynamic_initial.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/mobile/dynamic_replaced_completely.xml b/container/testdata/apptabs/mobile/dynamic_replaced_completely.xml index 7465c2f5fc..5e9074826e 100644 --- a/container/testdata/apptabs/mobile/dynamic_replaced_completely.xml +++ b/container/testdata/apptabs/mobile/dynamic_replaced_completely.xml @@ -2,7 +2,7 @@ - Text 6 + Text 6 diff --git a/container/testdata/apptabs/mobile/hover_none.xml b/container/testdata/apptabs/mobile/hover_none.xml index f84948ef92..230e3821fa 100644 --- a/container/testdata/apptabs/mobile/hover_none.xml +++ b/container/testdata/apptabs/mobile/hover_none.xml @@ -2,7 +2,7 @@ - Text1 + Text1 diff --git a/container/testdata/apptabs/mobile/tab_location_bottom.xml b/container/testdata/apptabs/mobile/tab_location_bottom.xml index 324c304df1..a1f588ee79 100644 --- a/container/testdata/apptabs/mobile/tab_location_bottom.xml +++ b/container/testdata/apptabs/mobile/tab_location_bottom.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/mobile/tab_location_top.xml b/container/testdata/apptabs/mobile/tab_location_top.xml index 13b8e15c83..33b3ebc2eb 100644 --- a/container/testdata/apptabs/mobile/tab_location_top.xml +++ b/container/testdata/apptabs/mobile/tab_location_top.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/mobile/tapped_first_selected.xml b/container/testdata/apptabs/mobile/tapped_first_selected.xml index 7100d0e2f8..782b59cc1c 100644 --- a/container/testdata/apptabs/mobile/tapped_first_selected.xml +++ b/container/testdata/apptabs/mobile/tapped_first_selected.xml @@ -2,7 +2,7 @@ - Text 1 + Text 1 diff --git a/container/testdata/apptabs/mobile/tapped_second_selected.xml b/container/testdata/apptabs/mobile/tapped_second_selected.xml index b8d4f953f3..20109cdc84 100644 --- a/container/testdata/apptabs/mobile/tapped_second_selected.xml +++ b/container/testdata/apptabs/mobile/tapped_second_selected.xml @@ -2,7 +2,7 @@ - Text 2 + Text 2 diff --git a/container/testdata/apptabs/mobile/tapped_third_selected.xml b/container/testdata/apptabs/mobile/tapped_third_selected.xml index 067976f045..c4bda366e5 100644 --- a/container/testdata/apptabs/mobile/tapped_third_selected.xml +++ b/container/testdata/apptabs/mobile/tapped_third_selected.xml @@ -2,7 +2,7 @@ - Text 3 + Text 3 diff --git a/container/testdata/apptabs/mobile/theme_default.png b/container/testdata/apptabs/mobile/theme_default.png index 6a60930a41..fba5ce1621 100644 Binary files a/container/testdata/apptabs/mobile/theme_default.png and b/container/testdata/apptabs/mobile/theme_default.png differ diff --git a/container/testdata/apptabs/mobile/theme_ugly.png b/container/testdata/apptabs/mobile/theme_ugly.png index 9055bfd2b8..3625ff834f 100644 Binary files a/container/testdata/apptabs/mobile/theme_ugly.png and b/container/testdata/apptabs/mobile/theme_ugly.png differ diff --git a/data/binding/queue.go b/data/binding/queue.go index 7587a88906..cafaed58d2 100644 --- a/data/binding/queue.go +++ b/data/binding/queue.go @@ -1,6 +1,6 @@ package binding -var itemQueue = make(chan itemData, 1024) +var itemQueueIn, itemQueueOut = makeInfiniteQueue() type itemData struct { fn func() @@ -8,16 +8,50 @@ type itemData struct { } func queueItem(f func()) { - itemQueue <- itemData{fn: f} + itemQueueIn <- &itemData{fn: f} } func init() { go processItems() } +func makeInfiniteQueue() (chan<- *itemData, <-chan *itemData) { + in := make(chan *itemData) + out := make(chan *itemData) + go func() { + queued := make([]*itemData, 0, 1024) + pending := func() chan *itemData { + if len(queued) == 0 { + return nil + } + return out + } + next := func() *itemData { + if len(queued) == 0 { + return nil + } + return queued[0] + } + for len(queued) > 0 || in != nil { + select { + case val, ok := <-in: + if !ok { + in = nil + } else { + queued = append(queued, val) + } + case pending() <- next(): + queued = queued[1:] + } + } + close(out) + }() + return in, out +} + func processItems() { for { - i := <-itemQueue + i := <-itemQueueOut if i.fn != nil { i.fn() } @@ -29,6 +63,6 @@ func processItems() { func waitForItems() { done := make(chan interface{}) - itemQueue <- itemData{done: done} + itemQueueIn <- &itemData{done: done} <-done } diff --git a/data/binding/queue_test.go b/data/binding/queue_test.go index 41e213cce3..71e71dc38e 100644 --- a/data/binding/queue_test.go +++ b/data/binding/queue_test.go @@ -1,6 +1,7 @@ package binding import ( + "sync" "testing" "github.com/stretchr/testify/assert" @@ -18,3 +19,25 @@ func TestQueueItem(t *testing.T) { waitForItems() assert.Equal(t, 2, called) } + +func TestMakeInfiniteQueue(t *testing.T) { + var wg sync.WaitGroup + in, out := makeInfiniteQueue() + + wg.Add(1) + c := 0 + go func() { + for range out { + c++ + } + wg.Done() + }() + + for i := 0; i < 2048; i++ { + in <- &itemData{} + } + close(in) + + wg.Wait() + assert.Equal(t, 2048, c) +} diff --git a/dialog/base.go b/dialog/base.go index b375fec83c..3c7c74266c 100644 --- a/dialog/base.go +++ b/dialog/base.go @@ -31,12 +31,13 @@ type Dialog interface { var _ Dialog = (*dialog)(nil) type dialog struct { - callback func(bool) - title string - icon fyne.Resource + callback func(bool) + title string + icon fyne.Resource + desiredSize fyne.Size win *widget.PopUp - bg *canvas.Rectangle + bg *themedBackground content, label fyne.CanvasObject dismiss *widget.Button parent fyne.Window @@ -99,6 +100,9 @@ func (d *dialog) Hide() { } func (d *dialog) Show() { + if !d.desiredSize.IsZero() { + d.win.Resize(d.desiredSize) + } d.win.Show() } @@ -106,7 +110,7 @@ func (d *dialog) Layout(obj []fyne.CanvasObject, size fyne.Size) { d.bg.Move(fyne.NewPos(0, 0)) d.bg.Resize(size) - btnMin := obj[3].MinSize().Max(obj[3].Size()) + btnMin := obj[3].MinSize() // icon iconHeight := padHeight*2 + d.label.MinSize().Height*2 - theme.Padding() @@ -126,7 +130,7 @@ func (d *dialog) Layout(obj []fyne.CanvasObject, size fyne.Size) { func (d *dialog) MinSize(obj []fyne.CanvasObject) fyne.Size { contentMin := obj[2].MinSize() - btnMin := obj[3].MinSize().Max(obj[3].Size()) + btnMin := obj[3].MinSize() width := fyne.Max(fyne.Max(contentMin.Width, btnMin.Width), obj[4].MinSize().Width) + padWidth height := contentMin.Height + btnMin.Height + d.label.MinSize().Height + theme.Padding() + padHeight*2 @@ -135,27 +139,13 @@ func (d *dialog) MinSize(obj []fyne.CanvasObject) fyne.Size { } func (d *dialog) Refresh() { - d.applyTheme() d.win.Refresh() } // Resize dialog, call this function after dialog show func (d *dialog) Resize(size fyne.Size) { - maxSize := d.win.Size() - minSize := d.win.MinSize() - newWidth := size.Width - if size.Width > maxSize.Width { - newWidth = maxSize.Width - } else if size.Width < minSize.Width { - newWidth = minSize.Width - } - newHeight := size.Height - if size.Height > maxSize.Height { - newHeight = maxSize.Height - } else if size.Height < minSize.Height { - newHeight = minSize.Height - } - d.win.Resize(fyne.NewSize(newWidth, newHeight)) + d.desiredSize = size + d.win.Resize(size) } // SetDismissText allows custom text to be set in the confirmation button @@ -178,12 +168,6 @@ func (d *dialog) SetOnClosed(closed func()) { } } -func (d *dialog) applyTheme() { - r, g, b, _ := theme.BackgroundColor().RGBA() - bg := &color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 230} - d.bg.FillColor = bg -} - func (d *dialog) hideWithResponse(resp bool) { d.win.Hide() if d.callback != nil { @@ -192,8 +176,8 @@ func (d *dialog) hideWithResponse(resp bool) { } func (d *dialog) setButtons(buttons fyne.CanvasObject) { - d.bg = canvas.NewRectangle(theme.BackgroundColor()) - d.label = newDialogTitle(d.title, d) + d.bg = newThemedBackground() + d.label = widget.NewLabelWithStyle(d.title, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}) var content fyne.CanvasObject if d.icon == nil { @@ -240,27 +224,52 @@ func newButtonList(buttons ...*widget.Button) fyne.CanvasObject { return list } -// dialogTitle is really just a normal title but we use the Refresh() hook to update the background rectangle. -type dialogTitle struct { - widget.Label +// =============================================================== +// ThemedBackground +// =============================================================== + +type themedBackground struct { + widget.BaseWidget +} + +func newThemedBackground() *themedBackground { + t := &themedBackground{} + t.ExtendBaseWidget(t) + return t +} + +func (t *themedBackground) CreateRenderer() fyne.WidgetRenderer { + t.ExtendBaseWidget(t) + rect := canvas.NewRectangle(theme.BackgroundColor()) + return &themedBackgroundRenderer{rect, []fyne.CanvasObject{rect}} +} + +type themedBackgroundRenderer struct { + rect *canvas.Rectangle + objects []fyne.CanvasObject +} - d *dialog +func (renderer *themedBackgroundRenderer) Destroy() { } -// Refresh applies the current theme to the whole dialog before refreshing the underlying label. -func (t *dialogTitle) Refresh() { - t.d.Refresh() +func (renderer *themedBackgroundRenderer) Layout(size fyne.Size) { + renderer.rect.Resize(size) +} - t.BaseWidget.Refresh() +func (renderer *themedBackgroundRenderer) MinSize() fyne.Size { + return renderer.rect.MinSize() } -func newDialogTitle(title string, d *dialog) *dialogTitle { - l := &dialogTitle{} - l.Text = title - l.Alignment = fyne.TextAlignLeading - l.TextStyle.Bold = true +func (renderer *themedBackgroundRenderer) Objects() []fyne.CanvasObject { + return renderer.objects +} - l.d = d - l.ExtendBaseWidget(l) - return l +func (renderer *themedBackgroundRenderer) Refresh() { + r, g, b, _ := theme.BackgroundColor().RGBA() + bg := &color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 230} + renderer.rect.FillColor = bg } + +// =============================================================== +// DialogLayout +// =============================================================== diff --git a/dialog/base_test.go b/dialog/base_test.go index 7ab1240645..3046aa51e9 100644 --- a/dialog/base_test.go +++ b/dialog/base_test.go @@ -4,9 +4,12 @@ import ( "image/color" "testing" + "github.com/stretchr/testify/assert" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/test" + "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -28,3 +31,65 @@ func TestShowCustom_ApplyTheme(t *testing.T) { test.ApplyTheme(t, test.NewTheme()) test.AssertImageMatches(t, "dialog-custom-ugly.png", w.Canvas().Capture()) } + +func TestShowCustom_Resize(t *testing.T) { + w := test.NewWindow(canvas.NewRectangle(color.Transparent)) + w.Resize(fyne.NewSize(300, 300)) + + label := widget.NewLabel("Content") + label.Alignment = fyne.TextAlignCenter + d := NewCustom("Title", "OK", label, w) + + size := fyne.NewSize(200, 200) + d.Resize(size) + d.Show() + assert.Equal(t, size, d.(*dialog).win.Content.Size().Add(fyne.NewSize(theme.Padding()*2, theme.Padding()*2))) +} + +func TestCustom_ApplyThemeOnShow(t *testing.T) { + test.NewApp() + defer test.NewApp() + w := test.NewWindow(canvas.NewRectangle(color.Transparent)) + w.Resize(fyne.NewSize(200, 300)) + + label := widget.NewLabel("Content") + label.Alignment = fyne.TextAlignCenter + d := NewCustom("Title", "OK", label, w) + + test.ApplyTheme(t, test.Theme()) + d.Show() + test.AssertImageMatches(t, "dialog-onshow-theme-default.png", w.Canvas().Capture()) + d.Hide() + + test.ApplyTheme(t, test.NewTheme()) + d.Show() + test.AssertImageMatches(t, "dialog-onshow-theme-changed.png", w.Canvas().Capture()) + d.Hide() + + test.ApplyTheme(t, test.Theme()) + d.Show() + test.AssertImageMatches(t, "dialog-onshow-theme-default.png", w.Canvas().Capture()) + d.Hide() +} + +func TestCustom_ResizeOnShow(t *testing.T) { + test.NewApp() + defer test.NewApp() + w := test.NewWindow(canvas.NewRectangle(color.Transparent)) + size := fyne.NewSize(200, 300) + w.Resize(size) + + label := widget.NewLabel("Content") + label.Alignment = fyne.TextAlignCenter + d := NewCustom("Title", "OK", label, w).(*dialog) + + d.Show() + assert.Equal(t, size, d.win.Size()) + d.Hide() + + size = fyne.NewSize(500, 500) + w.Resize(size) + d.Show() + assert.Equal(t, size, d.win.Size()) + d.Hide() +} diff --git a/dialog/color.go b/dialog/color.go index 0cfdf4d779..aff4bd36da 100644 --- a/dialog/color.go +++ b/dialog/color.go @@ -243,17 +243,7 @@ func colorToRGBA(c color.Color) (r, g, b, a int) { b = int(col.B) a = int(col.A) default: - red, green, blue, alpha := c.RGBA() - if alpha != 0 && alpha != 1 { - red /= alpha - green /= alpha - blue /= alpha - } - // Convert from range 0-65535 to range 0-255 - r = int(float64(red) / 255.0) - g = int(float64(green) / 255.0) - b = int(float64(blue) / 255.0) - a = int(float64(alpha) / 255.0) + r, g, b, a = unmultiplyAlpha(c) } return } @@ -352,3 +342,19 @@ func hueToChannel(h, v1, v2 float64) float64 { } return v2 } + +func unmultiplyAlpha(c color.Color) (r, g, b, a int) { + red, green, blue, alpha := c.RGBA() + if alpha != 0 && alpha != 0xffff { + ratio := float64(alpha) / 0xffff + red = uint32(float64(red) / ratio) + green = uint32(float64(green) / ratio) + blue = uint32(float64(blue) / ratio) + } + // Convert from range 0-65535 to range 0-255 + r = int(red >> 8) + g = int(green >> 8) + b = int(blue >> 8) + a = int(alpha >> 8) + return +} diff --git a/dialog/color_test.go b/dialog/color_test.go index 80379c98be..861cd66c70 100644 --- a/dialog/color_test.go +++ b/dialog/color_test.go @@ -380,3 +380,21 @@ func Test_hueToChannel(t *testing.T) { }) } } + +func Test_UnmultiplyAlpha(t *testing.T) { + c := color.RGBA{R: 100, G: 100, B: 100, A: 100} + r, g, b, a := unmultiplyAlpha(c) + + assert.Equal(t, 255, r) + assert.Equal(t, 255, g) + assert.Equal(t, 255, b) + assert.Equal(t, 100, a) + + c = color.RGBA{R: 100, G: 100, B: 100, A: 255} + r, g, b, a = unmultiplyAlpha(c) + + assert.Equal(t, 100, r) + assert.Equal(t, 100, g) + assert.Equal(t, 100, b) + assert.Equal(t, 255, a) +} diff --git a/dialog/file.go b/dialog/file.go index d81c620abc..247e57bb66 100644 --- a/dialog/file.go +++ b/dialog/file.go @@ -47,7 +47,7 @@ type FileDialog struct { parent fyne.Window dialog *fileDialog dismissText string - desiredSize *fyne.Size + desiredSize fyne.Size // this will be applied to dialog.dir when it's loaded startingLocation fyne.ListableURI // this will be the initial filename in a FileDialog in save mode @@ -459,9 +459,8 @@ func (f *FileDialog) Show() { return } f.dialog = showFile(f) - if f.desiredSize != nil { - f.Resize(*f.desiredSize) - f.desiredSize = nil + if !f.desiredSize.IsZero() { + f.Resize(f.desiredSize) } } @@ -470,27 +469,14 @@ func (f *FileDialog) Refresh() { f.dialog.win.Refresh() } -// Resize dialog, call this function after dialog show +// Resize dialog to the requested size, if there is sufficient space. +// If the parent window is not large enough then the size will be reduced to fit. func (f *FileDialog) Resize(size fyne.Size) { + f.desiredSize = size if f.dialog == nil { - f.desiredSize = &size return } - maxSize := f.dialog.win.Size() - minSize := f.dialog.win.MinSize() - newWidth := size.Width - if size.Width > maxSize.Width { - newWidth = maxSize.Width - } else if size.Width < minSize.Width { - newWidth = minSize.Width - } - newHeight := size.Height - if size.Height > maxSize.Height { - newHeight = maxSize.Height - } else if size.Height < minSize.Height { - newHeight = minSize.Height - } - f.dialog.win.Resize(fyne.NewSize(newWidth, newHeight)) + f.dialog.win.Resize(size) } // Hide hides the file dialog. diff --git a/dialog/file_test.go b/dialog/file_test.go index e6c7bc405c..fdb43c4754 100644 --- a/dialog/file_test.go +++ b/dialog/file_test.go @@ -387,7 +387,7 @@ func TestFileFilters(t *testing.T) { count++ } } - assert.Equal(t, 3, count) + assert.Equal(t, 5, count) f.SetFilter(storage.NewMimeTypeFileFilter([]string{"image/jpeg"})) @@ -412,7 +412,7 @@ func TestFileFilters(t *testing.T) { count++ } } - assert.Equal(t, 4, count) + assert.Equal(t, 6, count) } func TestFileFavorites(t *testing.T) { diff --git a/dialog/progress.go b/dialog/progress.go index 72da3794db..56386c0981 100644 --- a/dialog/progress.go +++ b/dialog/progress.go @@ -1,7 +1,11 @@ package dialog import ( + "image/color" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -27,8 +31,9 @@ func (p *ProgressDialog) SetValue(v float64) { func NewProgress(title, message string, parent fyne.Window) *ProgressDialog { d := newDialog(title, message, theme.InfoIcon(), nil /*cancel?*/, parent) bar := widget.NewProgressBar() - bar.Resize(fyne.NewSize(200, bar.MinSize().Height)) + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(200, 0)) - d.setButtons(bar) + d.setButtons(container.NewMax(rect, bar)) return &ProgressDialog{d, bar} } diff --git a/dialog/progressinfinite.go b/dialog/progressinfinite.go index af937f04fe..a78090773a 100644 --- a/dialog/progressinfinite.go +++ b/dialog/progressinfinite.go @@ -1,7 +1,11 @@ package dialog import ( + "image/color" + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -22,9 +26,10 @@ type ProgressInfiniteDialog struct { func NewProgressInfinite(title, message string, parent fyne.Window) *ProgressInfiniteDialog { d := newDialog(title, message, theme.InfoIcon(), nil /*cancel?*/, parent) bar := widget.NewProgressBarInfinite() - bar.Resize(fyne.NewSize(200, bar.MinSize().Height)) + rect := canvas.NewRectangle(color.Transparent) + rect.SetMinSize(fyne.NewSize(200, 0)) - d.setButtons(bar) + d.setButtons(container.NewMax(rect, bar)) return &ProgressInfiniteDialog{d, bar} } diff --git a/dialog/testdata/color/channel_layout_foobar_0.png b/dialog/testdata/color/channel_layout_foobar_0.png index 7056c66570..82c8d5cac2 100644 Binary files a/dialog/testdata/color/channel_layout_foobar_0.png and b/dialog/testdata/color/channel_layout_foobar_0.png differ diff --git a/dialog/testdata/color/channel_layout_foobar_100.png b/dialog/testdata/color/channel_layout_foobar_100.png index 8d4b1786a9..b5cdd2bd02 100644 Binary files a/dialog/testdata/color/channel_layout_foobar_100.png and b/dialog/testdata/color/channel_layout_foobar_100.png differ diff --git a/dialog/testdata/color/channel_layout_foobar_50.png b/dialog/testdata/color/channel_layout_foobar_50.png index 1db2acef82..bf91a8d4a7 100644 Binary files a/dialog/testdata/color/channel_layout_foobar_50.png and b/dialog/testdata/color/channel_layout_foobar_50.png differ diff --git a/dialog/testdata/color/dialog_expanded_theme_default.png b/dialog/testdata/color/dialog_expanded_theme_default.png index 9021e756a6..b859a1f53d 100644 Binary files a/dialog/testdata/color/dialog_expanded_theme_default.png and b/dialog/testdata/color/dialog_expanded_theme_default.png differ diff --git a/dialog/testdata/color/dialog_expanded_theme_ugly.png b/dialog/testdata/color/dialog_expanded_theme_ugly.png index 920d79b8e4..d8ece4537a 100644 Binary files a/dialog/testdata/color/dialog_expanded_theme_ugly.png and b/dialog/testdata/color/dialog_expanded_theme_ugly.png differ diff --git a/dialog/testdata/color/dialog_recents_theme_default.png b/dialog/testdata/color/dialog_recents_theme_default.png index 29a31126c9..f84072116c 100644 Binary files a/dialog/testdata/color/dialog_recents_theme_default.png and b/dialog/testdata/color/dialog_recents_theme_default.png differ diff --git a/dialog/testdata/color/dialog_recents_theme_ugly.png b/dialog/testdata/color/dialog_recents_theme_ugly.png index 9c673e2ea7..4c33e0a712 100644 Binary files a/dialog/testdata/color/dialog_recents_theme_ugly.png and b/dialog/testdata/color/dialog_recents_theme_ugly.png differ diff --git a/dialog/testdata/color/dialog_simple_recents_theme_default.png b/dialog/testdata/color/dialog_simple_recents_theme_default.png index 89d09c1ade..f9a3c62aea 100644 Binary files a/dialog/testdata/color/dialog_simple_recents_theme_default.png and b/dialog/testdata/color/dialog_simple_recents_theme_default.png differ diff --git a/dialog/testdata/color/dialog_simple_recents_theme_ugly.png b/dialog/testdata/color/dialog_simple_recents_theme_ugly.png index e389255d1b..cd550e266c 100644 Binary files a/dialog/testdata/color/dialog_simple_recents_theme_ugly.png and b/dialog/testdata/color/dialog_simple_recents_theme_ugly.png differ diff --git a/dialog/testdata/color/dialog_simple_theme_default.png b/dialog/testdata/color/dialog_simple_theme_default.png index 0c5bba023e..3baca7bce2 100644 Binary files a/dialog/testdata/color/dialog_simple_theme_default.png and b/dialog/testdata/color/dialog_simple_theme_default.png differ diff --git a/dialog/testdata/color/dialog_simple_theme_ugly.png b/dialog/testdata/color/dialog_simple_theme_ugly.png index b2c043314f..98a41b946a 100644 Binary files a/dialog/testdata/color/dialog_simple_theme_ugly.png and b/dialog/testdata/color/dialog_simple_theme_ugly.png differ diff --git a/dialog/testdata/color/dialog_theme_default.png b/dialog/testdata/color/dialog_theme_default.png index 662c5239cd..1900ed1137 100644 Binary files a/dialog/testdata/color/dialog_theme_default.png and b/dialog/testdata/color/dialog_theme_default.png differ diff --git a/dialog/testdata/color/dialog_theme_ugly.png b/dialog/testdata/color/dialog_theme_ugly.png index 7942e32365..9d458b061e 100644 Binary files a/dialog/testdata/color/dialog_theme_ugly.png and b/dialog/testdata/color/dialog_theme_ugly.png differ diff --git a/dialog/testdata/color/picker_layout_advanced.png b/dialog/testdata/color/picker_layout_advanced.png index c3638d3074..0e81a1e958 100644 Binary files a/dialog/testdata/color/picker_layout_advanced.png and b/dialog/testdata/color/picker_layout_advanced.png differ diff --git a/dialog/testdata/dialog-custom-default.png b/dialog/testdata/dialog-custom-default.png index 94e19a0615..41656afc21 100644 Binary files a/dialog/testdata/dialog-custom-default.png and b/dialog/testdata/dialog-custom-default.png differ diff --git a/dialog/testdata/dialog-custom-ugly.png b/dialog/testdata/dialog-custom-ugly.png index 210755985d..af6c28bd56 100644 Binary files a/dialog/testdata/dialog-custom-ugly.png and b/dialog/testdata/dialog-custom-ugly.png differ diff --git a/dialog/testdata/dialog-onshow-theme-changed.png b/dialog/testdata/dialog-onshow-theme-changed.png new file mode 100644 index 0000000000..af6c28bd56 Binary files /dev/null and b/dialog/testdata/dialog-onshow-theme-changed.png differ diff --git a/dialog/testdata/dialog-onshow-theme-default.png b/dialog/testdata/dialog-onshow-theme-default.png new file mode 100644 index 0000000000..41656afc21 Binary files /dev/null and b/dialog/testdata/dialog-onshow-theme-default.png differ diff --git a/driver.go b/driver.go index e425614238..b9be762885 100644 --- a/driver.go +++ b/driver.go @@ -22,6 +22,7 @@ type Driver interface { // Run starts the main event loop of the driver. Run() // Quit closes the driver and open windows, then exit the application. + // On some some operating systems this does nothing, for example iOS and Android. Quit() // StartAnimation registers a new animation with this driver and requests it be started. diff --git a/driver/software/testdata/canvas.png b/driver/software/testdata/canvas.png index b62c08da5e..147c781eb8 100644 Binary files a/driver/software/testdata/canvas.png and b/driver/software/testdata/canvas.png differ diff --git a/driver/software/testdata/canvas_mobile.png b/driver/software/testdata/canvas_mobile.png index bc392b0b53..a73c09cb8b 100644 Binary files a/driver/software/testdata/canvas_mobile.png and b/driver/software/testdata/canvas_mobile.png differ diff --git a/driver/software/testdata/label_dark.png b/driver/software/testdata/label_dark.png index 83e107abd6..b86bcfeee5 100644 Binary files a/driver/software/testdata/label_dark.png and b/driver/software/testdata/label_dark.png differ diff --git a/driver/software/testdata/label_light.png b/driver/software/testdata/label_light.png index 0997927776..bd966f4d5d 100644 Binary files a/driver/software/testdata/label_light.png and b/driver/software/testdata/label_light.png differ diff --git a/img/widgets-mobile-light.png b/img/widgets-mobile-light.png new file mode 100644 index 0000000000..23274517c4 Binary files /dev/null and b/img/widgets-mobile-light.png differ diff --git a/internal/animation/animation.go b/internal/animation/animation.go index 246455115e..9c4656bf65 100644 --- a/internal/animation/animation.go +++ b/internal/animation/animation.go @@ -1,6 +1,7 @@ package animation import ( + "sync" "time" "fyne.io/fyne/v2" @@ -13,6 +14,9 @@ type anim struct { reverse bool start time.Time total int64 + + mu sync.RWMutex + stopped bool } func newAnim(a *fyne.Animation) *anim { @@ -21,3 +25,16 @@ func newAnim(a *fyne.Animation) *anim { animate.repeatsLeft = a.RepeatCount return animate } + +func (a *anim) setStopped() { + a.mu.Lock() + a.stopped = true + a.mu.Unlock() +} + +func (a *anim) isStopped() bool { + a.mu.RLock() + ret := a.stopped + a.mu.RUnlock() + return ret +} diff --git a/internal/animation/animation_test.go b/internal/animation/animation_test.go index 2808c1f18f..61f90bfd96 100644 --- a/internal/animation/animation_test.go +++ b/internal/animation/animation_test.go @@ -3,6 +3,7 @@ package animation import ( + "sync" "testing" "time" @@ -48,3 +49,46 @@ func TestGLDriver_StopAnimation(t *testing.T) { run.Stop(a) assert.Zero(t, len(run.animations)) } + +func TestGLDriver_StopAnimationImmediatelyAndInsideTick(t *testing.T) { + var wg sync.WaitGroup + run := &Runner{} + + // stopping an animation immediately after start, should be effectively removed + // from the internal animation list (first one is added directly to animation list) + a := &fyne.Animation{ + Duration: time.Second, + Tick: func(f float32) {}, + } + run.Start(a) + run.Stop(a) + + // stopping animation inside tick function + for i := 0; i < 10; i++ { + wg.Add(1) + var b *fyne.Animation + b = &fyne.Animation{ + Duration: time.Second, + Tick: func(d float32) { + run.Stop(b) + wg.Done() + }} + run.Start(b) + } + + // Similar to first part, but in this time this animation should be added and then removed + // from pendingAnimation slice. + c := &fyne.Animation{ + Duration: time.Second, + Tick: func(f float32) {}, + } + run.Start(c) + run.Stop(c) + + wg.Wait() + // animations stopped inside tick are really stopped in the next runner cycle + time.Sleep(time.Second/60 + 100*time.Millisecond) + run.animationMutex.RLock() + assert.Zero(t, len(run.animations)) + run.animationMutex.RUnlock() +} diff --git a/internal/animation/runner.go b/internal/animation/runner.go index 6b729775f8..764b062d53 100644 --- a/internal/animation/runner.go +++ b/internal/animation/runner.go @@ -9,19 +9,24 @@ import ( // Runner is the main driver for animations package type Runner struct { - animationMutex sync.RWMutex - animations []*anim + animationMutex sync.RWMutex + animations []*anim + pendingAnimations []*anim + + runnerStarted bool } // Start will register the passed application and initiate its ticking. func (r *Runner) Start(a *fyne.Animation) { r.animationMutex.Lock() defer r.animationMutex.Unlock() - wasStopped := len(r.animations) == 0 - r.animations = append(r.animations, newAnim(a)) - if wasStopped { + if !r.runnerStarted { + r.runnerStarted = true + r.animations = append(r.animations, newAnim(a)) r.runAnimations() + } else { + r.pendingAnimations = append(r.pendingAnimations, newAnim(a)) } } @@ -29,14 +34,31 @@ func (r *Runner) Start(a *fyne.Animation) { func (r *Runner) Stop(a *fyne.Animation) { r.animationMutex.Lock() defer r.animationMutex.Unlock() - oldList := r.animations - var newList []*anim - for _, item := range oldList { + + newList := make([]*anim, 0, len(r.animations)) + stopped := false + for _, item := range r.animations { if item.a != a { newList = append(newList, item) + } else { + item.setStopped() + stopped = true } } r.animations = newList + if stopped { + return + } + + newList = make([]*anim, 0, len(r.pendingAnimations)) + for _, item := range r.pendingAnimations { + if item.a != a { + newList = append(newList, item) + } else { + item.setStopped() + } + } + r.pendingAnimations = newList } func (r *Runner) runAnimations() { @@ -47,19 +69,22 @@ func (r *Runner) runAnimations() { <-draw.C r.animationMutex.Lock() oldList := r.animations - r.animations = nil // clear the list so we can append any new ones after processing r.animationMutex.Unlock() - var newList []*anim + newList := make([]*anim, 0, len(oldList)) for _, a := range oldList { - if r.tickAnimation(a) { + if !a.isStopped() && r.tickAnimation(a) { newList = append(newList, a) } } r.animationMutex.Lock() - r.animations = append(newList, r.animations...) + r.animations = append(newList, r.pendingAnimations...) + r.pendingAnimations = nil done = len(r.animations) == 0 r.animationMutex.Unlock() } + r.animationMutex.Lock() + r.runnerStarted = false + r.animationMutex.Unlock() draw.Stop() }() } @@ -90,6 +115,7 @@ func (r *Runner) tickAnimation(a *anim) bool { a.start = time.Now() a.end = a.start.Add(a.a.Duration) + return true } delta := time.Since(a.start).Nanoseconds() / 1000000 // TODO change this to Milliseconds() when we drop Go 1.12 diff --git a/internal/app/focus_manager.go b/internal/app/focus_manager.go index 1eff4bb605..668aa12520 100644 --- a/internal/app/focus_manager.go +++ b/internal/app/focus_manager.go @@ -59,6 +59,18 @@ func (f *FocusManager) Focus(obj fyne.Focusable) bool { return true } +// FocusBeforeAdded allows an object to be focused before it is added to the object tree. +// This is typically used before a canvas is visible and should be used with care. +func (f *FocusManager) FocusBeforeAdded(obj fyne.Focusable) { + f.RLock() + defer f.RUnlock() + if dis, ok := obj.(fyne.Disableable); ok && dis.Disabled() { + return + } + + f.focus(obj) +} + // Focused returns the currently focused object or nil if none. func (f *FocusManager) Focused() fyne.Focusable { f.RLock() diff --git a/internal/driver/glfw/canvas.go b/internal/driver/glfw/canvas.go index 265763cc3f..e2662b6637 100644 --- a/internal/driver/glfw/canvas.go +++ b/internal/driver/glfw/canvas.go @@ -84,13 +84,17 @@ func (c *glCanvas) Focus(obj fyne.Focusable) { c.RUnlock() for _, mgr := range focusMgrs { + if mgr == nil { + continue + } if focusMgr != mgr { if mgr.Focus(obj) { return } } } - fyne.LogError("Failed to focus object which is not part of the canvas’ content, menu or overlays.", nil) + + c.contentFocusMgr.FocusBeforeAdded(obj) // not found yet assume we are preparing the UI } func (c *glCanvas) Focused() fyne.Focusable { @@ -178,7 +182,7 @@ func (c *glCanvas) Resize(size fyne.Size) { if p, ok := overlay.(*widget.PopUp); ok { // TODO: remove this when #707 is being addressed. // “Notifies” the PopUp of the canvas size change. - p.Resize(p.Content.Size().Add(fyne.NewSize(theme.Padding()*2, theme.Padding()*2))) + p.Refresh() } else { overlay.Resize(size) } @@ -452,7 +456,14 @@ func (c *glCanvas) paint(size fyne.Size) { func (c *glCanvas) setContent(content fyne.CanvasObject) { c.content = content c.contentTree = &renderCacheTree{root: &renderCacheNode{obj: c.content}} + var focused fyne.Focusable + if c.contentFocusMgr != nil { + focused = c.contentFocusMgr.Focused() // keep old focus if possible + } c.contentFocusMgr = app.NewFocusManager(c.content) + if focused != nil { + c.contentFocusMgr.Focus(focused) + } } func (c *glCanvas) setDirty(dirty bool) { @@ -466,6 +477,14 @@ func (c *glCanvas) setMenuOverlay(b fyne.CanvasObject) { c.menu = b c.menuTree = &renderCacheTree{root: &renderCacheNode{obj: c.menu}} c.menuFocusMgr = app.NewFocusManager(c.menu) + + if c.menu != nil && !c.size.IsZero() { + c.content.Resize(c.contentSize(c.size)) + c.content.Move(c.contentPos()) + + c.menu.Refresh() + c.menu.Resize(fyne.NewSize(c.size.Width, c.menu.MinSize().Height)) + } } func (c *glCanvas) setupThemeListener() { diff --git a/internal/driver/glfw/canvas_test.go b/internal/driver/glfw/canvas_test.go index f5f6ccfe0e..4b9dc6d013 100644 --- a/internal/driver/glfw/canvas_test.go +++ b/internal/driver/glfw/canvas_test.go @@ -204,6 +204,17 @@ func TestGlCanvas_Focus(t *testing.T) { assert.True(t, o2e.focused) } +func TestGlCanvas_Focus_BeforeVisible(t *testing.T) { + w := createWindow("Test") + w.SetPadded(false) + e := widget.NewEntry() + c := w.Canvas().(*glCanvas) + c.Focus(e) // this crashed in the past + + w.SetContent(e) + assert.Equal(t, e, c.Focused(), "Item set to focus before SetContent was not focused after") +} + func TestGlCanvas_FocusHandlingWhenAddingAndRemovingOverlays(t *testing.T) { w := createWindow("Test") w.SetPadded(false) @@ -401,14 +412,8 @@ func TestGlCanvas_ResizeWithOverlays(t *testing.T) { o3 := widget.NewLabel("o3") w.SetContent(content) w.Canvas().Overlays().Add(o1) - // TODO: address #707; overlays should always be canvas size - o1.Resize(w.Canvas().Size()) w.Canvas().Overlays().Add(o2) - // TODO: address #707; overlays should always be canvas size - o2.Resize(w.Canvas().Size()) w.Canvas().Overlays().Add(o3) - // TODO: address #707; overlays should always be canvas size - o3.Resize(w.Canvas().Size()) size := fyne.NewSize(200, 100) assert.NotEqual(t, size, content.Size()) @@ -431,10 +436,11 @@ func TestGlCanvas_ResizeWithPopUpOverlay(t *testing.T) { content := widget.NewLabel("Content") over := widget.NewPopUp(widget.NewLabel("Over"), w.Canvas()) w.SetContent(content) - w.Canvas().Overlays().Add(over) + over.Show() size := fyne.NewSize(200, 100) overContentSize := over.Content.Size() + assert.NotZero(t, overContentSize) assert.NotEqual(t, size, content.Size()) assert.NotEqual(t, size, over.Size()) assert.NotEqual(t, size, overContentSize) @@ -445,6 +451,30 @@ func TestGlCanvas_ResizeWithPopUpOverlay(t *testing.T) { assert.Equal(t, overContentSize, over.Content.Size(), "canvas overlay content is _not_ resized") } +func TestGlCanvas_ResizeWithModalPopUpOverlay(t *testing.T) { + w := createWindow("Test") + w.SetPadded(false) + + content := widget.NewLabel("Content") + w.SetContent(content) + + popup := widget.NewModalPopUp(widget.NewLabel("PopUp"), w.Canvas()) + popupBgSize := fyne.NewSize(975, 575) + popup.Show() + popup.Resize(popupBgSize) + + winSize := fyne.NewSize(1000, 600) + w.Resize(winSize) + w.Show() + defer w.Close() + + // get popup content padding dynamically + popupContentPadding := popup.MinSize().Subtract(popup.Content.MinSize()) + + assert.Equal(t, popupBgSize.Subtract(popupContentPadding), popup.Content.Size()) + assert.Equal(t, winSize, popup.Size()) +} + func TestGlCanvas_Scale(t *testing.T) { w := createWindow("Test").(*window) c := w.Canvas().(*glCanvas) diff --git a/internal/driver/glfw/driver_test.go b/internal/driver/glfw/driver_test.go index 95fb3a9217..aaea7eed0d 100644 --- a/internal/driver/glfw/driver_test.go +++ b/internal/driver/glfw/driver_test.go @@ -67,7 +67,7 @@ func Test_gLDriver_AbsolutePositionForObject(t *testing.T) { }{ "a cell": { object: cr1c3, - want: fyne.NewPos(182, 33), + want: fyne.NewPos(198, 33), }, "a row": { object: cr2, @@ -89,11 +89,11 @@ func Test_gLDriver_AbsolutePositionForObject(t *testing.T) { "an overlay item": { object: ovli2, - want: fyne.NewPos(79, 81), + want: fyne.NewPos(87, 81), }, "the overlay content": { object: ovlContent, - want: fyne.NewPos(79, 40), + want: fyne.NewPos(87, 40), }, "the overlay": { object: ovl, diff --git a/internal/driver/glfw/glfw_core.go b/internal/driver/glfw/glfw_core.go index 8c57655fad..3646b379d9 100644 --- a/internal/driver/glfw/glfw_core.go +++ b/internal/driver/glfw/glfw_core.go @@ -1,4 +1,4 @@ -// +build !gles,!arm,!arm64 +// +build !gles,!arm,!arm64 darwin package glfw diff --git a/internal/driver/glfw/glfw_es.go b/internal/driver/glfw/glfw_es.go index afeac02c72..c389c929d5 100644 --- a/internal/driver/glfw/glfw_es.go +++ b/internal/driver/glfw/glfw_es.go @@ -1,4 +1,5 @@ // +build gles arm arm64 +// +build !darwin package glfw diff --git a/internal/driver/glfw/loop.go b/internal/driver/glfw/loop.go index f76d69dded..69e4fd306e 100644 --- a/internal/driver/glfw/loop.go +++ b/internal/driver/glfw/loop.go @@ -113,7 +113,6 @@ func (d *gLDriver) runGL() { w.viewLock.Lock() w.visible = false v := w.viewport - w.viewport = nil w.viewLock.Unlock() // remove window from window list @@ -201,10 +200,10 @@ func (d *gLDriver) startDrawThread() { w := win.(*window) w.viewLock.RLock() canvas := w.canvas - view := w.viewport + closing := w.closing visible := w.visible w.viewLock.RUnlock() - if view == nil || !canvas.isDirty() || !visible { + if closing || !canvas.isDirty() || !visible { continue } diff --git a/internal/driver/glfw/testdata/menu_bar_hovered_content.png b/internal/driver/glfw/testdata/menu_bar_hovered_content.png index 0569411355..fc5c0c3a9b 100644 Binary files a/internal/driver/glfw/testdata/menu_bar_hovered_content.png and b/internal/driver/glfw/testdata/menu_bar_hovered_content.png differ diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index baf63bffbd..01e0ac4cca 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -51,6 +51,7 @@ type window struct { viewLock sync.RWMutex createLock sync.Once decorate bool + closing bool fixedSize bool cursor desktop.Cursor @@ -273,7 +274,7 @@ func (w *window) fitContent() { return } - if w.viewport == nil { + if w.isClosing() { return } @@ -405,7 +406,7 @@ func (w *window) doShow() { } func (w *window) Hide() { - if w.viewport == nil { + if w.isClosing() { return } @@ -423,10 +424,11 @@ func (w *window) Hide() { } func (w *window) Close() { - if w.viewport == nil { + if w.isClosing() { return } + w.closing = true w.viewport.SetShouldClose(true) w.canvas.walkTrees(nil, func(node *renderCacheNode) { @@ -449,7 +451,7 @@ func (w *window) ShowAndRun() { // Clipboard returns the system clipboard func (w *window) Clipboard() fyne.Clipboard { - if w.viewport == nil { + if w.view() == nil { return nil } @@ -1176,6 +1178,9 @@ func (w *window) focused(_ *glfw.Window, isFocused bool) { } func (w *window) RunWithContext(f func()) { + if w.isClosing() { + return + } w.viewport.MakeContextCurrent() f() @@ -1190,7 +1195,7 @@ func (w *window) RescaleContext() { } func (w *window) rescaleOnMain() { - if w.viewport == nil { + if w.isClosing() { return } w.fitContent() @@ -1354,7 +1359,7 @@ func (w *window) create() { } func (w *window) doShowAgain() { - if w.viewport == nil { + if w.isClosing() { return } @@ -1372,10 +1377,17 @@ func (w *window) doShowAgain() { }) } +func (w *window) isClosing() bool { + return w.closing || w.viewport == nil +} + func (w *window) view() *glfw.Window { w.viewLock.RLock() defer w.viewLock.RUnlock() + if w.closing { + return nil + } return w.viewport } diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index c78474bb9d..ed51481468 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -1010,6 +1010,7 @@ func TestWindow_CloseInterception(t *testing.T) { assert.False(t, onIntercepted) // The interceptor is not called by the Close. assert.True(t, onClosed) + w.closing = false // fake a fresh window onIntercepted = false onClosed = false w.closed(w.viewport) @@ -1017,6 +1018,7 @@ func TestWindow_CloseInterception(t *testing.T) { assert.True(t, onIntercepted) // The interceptor is called by the closed. assert.False(t, onClosed) // If the interceptor is set Close is not called. + w.closing = false // fake a fresh window onClosed = false w.SetCloseIntercept(nil) w.closed(w.viewport) diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index eeab19cfad..b62fe7e045 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -340,8 +340,7 @@ func (c *mobileCanvas) sizeContent(size fyne.Size) { if p, ok := overlay.(*widget.PopUp); ok { // TODO: remove this when #707 is being addressed. // “Notifies” the PopUp of the canvas size change. - size := p.Content.Size().Add(fyne.NewSize(theme.Padding()*2, theme.Padding()*2)).Min(areaSize) - p.Resize(size) + p.Refresh() } else { overlay.Resize(areaSize) overlay.Move(topLeft) diff --git a/internal/driver/gomobile/canvas_test.go b/internal/driver/gomobile/canvas_test.go index 8d4cc37bcb..7929cfbe37 100644 --- a/internal/driver/gomobile/canvas_test.go +++ b/internal/driver/gomobile/canvas_test.go @@ -227,6 +227,26 @@ func TestWindow_TappedAndDoubleTapped(t *testing.T) { assert.Equal(t, 2, tapped) } +func TestGlCanvas_ResizeWithModalPopUpOverlay(t *testing.T) { + c := NewCanvas().(*mobileCanvas) + + c.SetContent(widget.NewLabel("Content")) + + popup := widget.NewModalPopUp(widget.NewLabel("PopUp"), c) + popupBgSize := fyne.NewSize(200, 200) + popup.Show() + popup.Resize(popupBgSize) + + canvasSize := fyne.NewSize(600, 700) + c.resize(canvasSize) + + // get popup content padding dynamically + popupContentPadding := popup.MinSize().Subtract(popup.Content.MinSize()) + + assert.Equal(t, popupBgSize.Subtract(popupContentPadding), popup.Content.Size()) + assert.Equal(t, canvasSize, popup.Size()) +} + func TestCanvas_Focusable(t *testing.T) { content := newFocusableEntry() c := NewCanvas().(*mobileCanvas) diff --git a/internal/driver/gomobile/device_android.go b/internal/driver/gomobile/device_android.go index c78b6fc4f0..3601a2a2b7 100644 --- a/internal/driver/gomobile/device_android.go +++ b/internal/driver/gomobile/device_android.go @@ -4,6 +4,8 @@ package gomobile import "fyne.io/fyne/v2" +const tapYOffset = -12.0 // to compensate for how we hold our fingers on the device + func (*device) SystemScaleForWindow(_ fyne.Window) float32 { if currentDPI >= 600 { return 4 diff --git a/internal/driver/gomobile/device_desktop.go b/internal/driver/gomobile/device_desktop.go index 1c5fcb8afc..047251b71b 100644 --- a/internal/driver/gomobile/device_desktop.go +++ b/internal/driver/gomobile/device_desktop.go @@ -1,9 +1,11 @@ -// +build !ios,!android +// +build !ios,!android,!wayland package gomobile import "fyne.io/fyne/v2" +const tapYOffset = 0 // no finger compensation on desktop (simulation) + func (*device) SystemScaleForWindow(_ fyne.Window) float32 { return 2 // this is simply due to the high number of pixels on a mobile device - just an approximation } diff --git a/internal/driver/gomobile/device_ios.go b/internal/driver/gomobile/device_ios.go index ba2ed7a94d..e5270f7597 100644 --- a/internal/driver/gomobile/device_ios.go +++ b/internal/driver/gomobile/device_ios.go @@ -4,6 +4,8 @@ package gomobile import "fyne.io/fyne/v2" +const tapYOffset = -12.0 // to compensate for how we hold our fingers on the device + func (*device) SystemScaleForWindow(_ fyne.Window) float32 { if currentDPI >= 450 { return 3 diff --git a/internal/driver/gomobile/device_wayland.go b/internal/driver/gomobile/device_wayland.go new file mode 100644 index 0000000000..c2ce3bb73e --- /dev/null +++ b/internal/driver/gomobile/device_wayland.go @@ -0,0 +1,9 @@ +// +build wayland + +package gomobile + +import "fyne.io/fyne/v2" + +func (*device) SystemScaleForWindow(_ fyne.Window) float32 { + return 1 // PinePhone simplification, our only wayland mobile currently +} diff --git a/internal/driver/gomobile/driver.go b/internal/driver/gomobile/driver.go index d5ba90d241..4a428f54e7 100644 --- a/internal/driver/gomobile/driver.go +++ b/internal/driver/gomobile/driver.go @@ -26,7 +26,6 @@ import ( const ( tapMoveThreshold = 4.0 // how far can we move before it is a drag tapSecondaryDelay = 300 * time.Millisecond // how long before secondary tap - tapYOffset = -12.0 // to compensate for how we hold our fingers on the device ) type mobileDriver struct { @@ -64,7 +63,15 @@ func (d *mobileDriver) currentWindow() fyne.Window { return nil } - return d.windows[len(d.windows)-1] + var last fyne.Window + for i := len(d.windows) - 1; i >= 0; i-- { + last = d.windows[i] + if last.(*window).visible { + return last + } + } + + return last } func (d *mobileDriver) RenderedTextSize(text string, size float32, style fyne.TextStyle) fyne.Size { diff --git a/internal/driver/gomobile/file.go b/internal/driver/gomobile/file.go index 8afe32e5ba..51834c354c 100644 --- a/internal/driver/gomobile/file.go +++ b/internal/driver/gomobile/file.go @@ -36,7 +36,7 @@ func mobileFilter(filter storage.FileFilter) *app.FileFilter { mobile.MimeTypes = f.MimeTypes } else if f, ok := filter.(*storage.ExtensionFileFilter); ok { mobile.Extensions = f.Extensions - } else { + } else if filter != nil { fyne.LogError("Custom filter types not supported on mobile", nil) } diff --git a/internal/driver/gomobile/file_test.go b/internal/driver/gomobile/file_test.go new file mode 100644 index 0000000000..ea609b54c7 --- /dev/null +++ b/internal/driver/gomobile/file_test.go @@ -0,0 +1,23 @@ +package gomobile + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "fyne.io/fyne/v2/storage" +) + +func TestMobileFilter(t *testing.T) { + f := mobileFilter(nil) + assert.Equal(t, 0, len(f.Extensions)) + assert.Equal(t, 0, len(f.MimeTypes)) + + f = mobileFilter(storage.NewExtensionFileFilter([]string{".png"})) + assert.Equal(t, 1, len(f.Extensions)) + assert.Equal(t, 0, len(f.MimeTypes)) + + f = mobileFilter(storage.NewMimeTypeFileFilter([]string{"text/plain"})) + assert.Equal(t, 0, len(f.Extensions)) + assert.Equal(t, 1, len(f.MimeTypes)) +} diff --git a/internal/driver/gomobile/keyboard.go b/internal/driver/gomobile/keyboard.go index 967878b218..d41435754d 100644 --- a/internal/driver/gomobile/keyboard.go +++ b/internal/driver/gomobile/keyboard.go @@ -8,12 +8,21 @@ import ( func showVirtualKeyboard(keyboard mobile.KeyboardType) { if driver, ok := fyne.CurrentApp().Driver().(*mobileDriver); ok { + if driver.app == nil { // not yet running + fyne.LogError("Cannot show keyboard before app is running", nil) + return + } + driver.app.ShowVirtualKeyboard(app.KeyboardType(keyboard)) } } func hideVirtualKeyboard() { if driver, ok := fyne.CurrentApp().Driver().(*mobileDriver); ok { + if driver.app == nil { // not yet running + return + } + driver.app.HideVirtualKeyboard() } } diff --git a/internal/driver/gomobile/keyboard_test.go b/internal/driver/gomobile/keyboard_test.go new file mode 100644 index 0000000000..09456105c3 --- /dev/null +++ b/internal/driver/gomobile/keyboard_test.go @@ -0,0 +1,11 @@ +package gomobile + +import ( + "testing" + + _ "fyne.io/fyne/v2/test" +) + +func TestDevice_HideVirtualKeyboard_BeforeRun(t *testing.T) { + hideVirtualKeyboard() // should not crash! +} diff --git a/internal/driver/gomobile/window.go b/internal/driver/gomobile/window.go index 8bc00a3985..cfb6f65537 100644 --- a/internal/driver/gomobile/window.go +++ b/internal/driver/gomobile/window.go @@ -121,6 +121,7 @@ func (w *window) Show() { w.visible = true if w.Content() != nil { + w.Content().Refresh() w.Content().Show() } } diff --git a/internal/painter/gl/gl_core.go b/internal/painter/gl/gl_core.go index b7f5e49c48..34196f8dc8 100644 --- a/internal/painter/gl/gl_core.go +++ b/internal/painter/gl/gl_core.go @@ -1,4 +1,4 @@ -// +build !gles,!arm,!arm64,!android,!ios,!mobile +// +build !gles,!arm,!arm64,!android,!ios,!mobile darwin,!mobile,!ios package gl diff --git a/internal/painter/gl/gl_es.go b/internal/painter/gl/gl_es.go index 817ffe937a..5ff64a4f3b 100644 --- a/internal/painter/gl/gl_es.go +++ b/internal/painter/gl/gl_es.go @@ -1,5 +1,6 @@ // +build gles arm arm64 // +build !android,!ios,!mobile +// +build !darwin package gl diff --git a/internal/painter/software/testdata/draw_text_clipped.png b/internal/painter/software/testdata/draw_text_clipped.png index 58d07f339f..75ecdd6e6b 100644 Binary files a/internal/painter/software/testdata/draw_text_clipped.png and b/internal/painter/software/testdata/draw_text_clipped.png differ diff --git a/internal/widget/scroller.go b/internal/widget/scroller.go index 504132d3b0..2a708856f4 100644 --- a/internal/widget/scroller.go +++ b/internal/widget/scroller.go @@ -402,6 +402,9 @@ func (r *scrollContainerRenderer) handleShadowVisibility(offset, contentSize, sc } func (r *scrollContainerRenderer) updatePosition() { + if r.scroll.Content == nil { + return + } scrollSize := r.scroll.Size() contentSize := r.scroll.Content.Size() diff --git a/test/testcanvas.go b/test/testcanvas.go index 24ac67b32c..1bb95e3a1b 100644 --- a/test/testcanvas.go +++ b/test/testcanvas.go @@ -160,8 +160,19 @@ func (c *testCanvas) Resize(size fyne.Size) { return } + // Ensure testcanvas mimics real canvas.Resize behavior for _, overlay := range overlays.List() { - overlay.Resize(size) + type popupWidget interface { + fyne.CanvasObject + ShowAtPosition(fyne.Position) + } + if p, ok := overlay.(popupWidget); ok { + // TODO: remove this when #707 is being addressed. + // “Notifies” the PopUp of the canvas size change. + p.Refresh() + } else { + overlay.Resize(size) + } } if padded { diff --git a/widget/accordion_internal_test.go b/widget/accordion_internal_test.go index 379423eed6..4a96aa32a7 100644 --- a/widget/accordion_internal_test.go +++ b/widget/accordion_internal_test.go @@ -149,7 +149,7 @@ func TestAccordionRenderer_MinSize(t *testing.T) { assert.Equal(t, float32(0), min.Height) }) t.Run("Single", func(t *testing.T) { - ai := NewAccordionItem("foo", NewLabel("foobar")) + ai := NewAccordionItem("title", NewLabel("foobar")) t.Run("Open", func(t *testing.T) { ac := NewAccordion() ac.Append(ai) @@ -173,9 +173,9 @@ func TestAccordionRenderer_MinSize(t *testing.T) { }) }) t.Run("Multiple", func(t *testing.T) { - ai0 := NewAccordionItem("foo0", NewLabel("foobar0")) - ai1 := NewAccordionItem("foo1", NewLabel("foobar1")) - ai2 := NewAccordionItem("foo2", NewLabel("foobar2")) + ai0 := NewAccordionItem("title0", NewLabel("foobar0")) + ai1 := NewAccordionItem("title1", NewLabel("foobar1")) + ai2 := NewAccordionItem("title2", NewLabel("foobar2")) t.Run("One_Open", func(t *testing.T) { ac := NewAccordion() ac.Append(ai0) diff --git a/widget/accordion_test.go b/widget/accordion_test.go index 59900c8727..4d9a2e964e 100644 --- a/widget/accordion_test.go +++ b/widget/accordion_test.go @@ -219,16 +219,16 @@ func TestAccordion_Layout(t *testing.T) { func TestAccordion_MinSize(t *testing.T) { minSizeA := fyne.MeasureText("A", theme.TextSize(), fyne.TextStyle{}) - minSizeA.Width += theme.IconInlineSize() + theme.Padding()*5 + minSizeA.Width += theme.IconInlineSize() + theme.Padding()*7 minSizeA.Height = fyne.Max(minSizeA.Height, theme.IconInlineSize()) + theme.Padding()*2 minSizeB := fyne.MeasureText("B", theme.TextSize(), fyne.TextStyle{}) - minSizeB.Width += theme.IconInlineSize() + theme.Padding()*5 + minSizeB.Width += theme.IconInlineSize() + theme.Padding()*7 minSizeB.Height = fyne.Max(minSizeB.Height, theme.IconInlineSize()) + theme.Padding()*2 minSize1 := fyne.MeasureText("111111", theme.TextSize(), fyne.TextStyle{}) - minSize1.Width += theme.Padding() * 2 + minSize1.Width += theme.Padding() * 4 minSize1.Height += theme.Padding() * 4 minSize2 := fyne.MeasureText("2222222222", theme.TextSize(), fyne.TextStyle{}) - minSize2.Width += theme.Padding() * 2 + minSize2.Width += theme.Padding() * 4 minSize2.Height += theme.Padding() * 4 minWidthA1 := fyne.Max(minSizeA.Width, minSize1.Width) diff --git a/widget/button.go b/widget/button.go index 5c2ecbcc37..07e390287e 100644 --- a/widget/button.go +++ b/widget/button.go @@ -312,10 +312,25 @@ func (r *buttonRenderer) buttonColor() color.Color { switch { case r.button.Disabled(): return theme.DisabledButtonColor() + case r.button.hovered: + bg := theme.ButtonColor() + if r.button.Importance == HighImportance { + bg = theme.PrimaryColor() + } + + dstR, dstG, dstB, dstA := bg.RGBA() + srcR, srcG, srcB, srcA := theme.HoverColor().RGBA() + srcAlpha := float32(srcA) / 0xFFFF + dstAlpha := float32(dstA) / 0xFFFF + targetAlpha := 1 - srcAlpha*dstAlpha + + outAlpha := srcAlpha + targetAlpha + outR := (srcAlpha*float32(srcR) + targetAlpha*float32(dstR)) / outAlpha + outG := (srcAlpha*float32(srcG) + targetAlpha*float32(dstG)) / outAlpha + outB := (srcAlpha*float32(srcB) + targetAlpha*float32(dstB)) / outAlpha + return color.RGBA{R: uint8(uint32(outR) >> 8), G: uint8(uint32(outG) >> 8), B: uint8(uint32(outB) >> 8), A: uint8(outAlpha * 0xFF)} case r.button.Importance == HighImportance: return theme.PrimaryColor() - case r.button.hovered: - return theme.HoverColor() default: return theme.ButtonColor() } diff --git a/widget/button_test.go b/widget/button_test.go index d2818ffbf6..25320bf39c 100644 --- a/widget/button_test.go +++ b/widget/button_test.go @@ -121,6 +121,35 @@ func TestButton_Disabled(t *testing.T) { assert.False(t, button.Disabled()) } +func TestButton_Hover(t *testing.T) { + app := test.NewApp() + defer test.NewApp() + app.Settings().SetTheme(theme.LightTheme()) + + b := widget.NewButtonWithIcon("Test", theme.HomeIcon(), func() {}) + w := test.NewWindow(b) + defer w.Close() + + if !fyne.CurrentDevice().IsMobile() { + test.MoveMouse(w.Canvas(), fyne.NewPos(5, 5)) + test.AssertImageMatches(t, "button/hovered.png", w.Canvas().Capture()) + } + + b.Importance = widget.HighImportance + b.Refresh() + if !fyne.CurrentDevice().IsMobile() { + test.AssertImageMatches(t, "button/high_importance_hovered.png", w.Canvas().Capture()) + + test.MoveMouse(w.Canvas(), fyne.NewPos(0, 0)) + b.Refresh() + } + test.AssertImageMatches(t, "button/high_importance.png", w.Canvas().Capture()) + + b.Importance = widget.MediumImportance + b.Refresh() + test.AssertImageMatches(t, "button/initial.png", w.Canvas().Capture()) +} + func TestButton_Layout(t *testing.T) { test.NewApp() defer test.NewApp() diff --git a/widget/entry.go b/widget/entry.go index 41a51ba4a8..0f28e899a8 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -4,7 +4,6 @@ import ( "image/color" "math" "strings" - "time" "unicode" "fyne.io/fyne/v2" @@ -57,6 +56,8 @@ type Entry struct { CursorRow, CursorColumn int OnCursorChanged func() `json:"-"` + cursorAnim *entryCursorAnimation + focused bool text *textProvider placeholder *textProvider @@ -131,7 +132,7 @@ func (e *Entry) Bind(data binding.String) { val, err := data.Get() if err != nil { convertErr = err - e.SetValidationError(err) + e.SetValidationError(e.Validate()) return } e.Text = val @@ -145,7 +146,7 @@ func (e *Entry) Bind(data binding.String) { e.OnChanged = func(s string) { convertErr = data.Set(s) - e.SetValidationError(convertErr) + e.SetValidationError(e.Validate()) } } @@ -161,10 +162,20 @@ func (e *Entry) CreateRenderer() fyne.WidgetRenderer { box := canvas.NewRectangle(theme.InputBackgroundColor()) line := canvas.NewRectangle(theme.ShadowColor()) + cursor := canvas.NewRectangle(color.Transparent) + cursor.Hide() + e.cursorAnim = newEntryCursorAnimation(cursor) 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 { @@ -237,12 +248,15 @@ func (e *Entry) DragEnd() { // // Implements: fyne.Draggable func (e *Entry) Dragged(d *fyne.DragEvent) { + pevt := d.PointEvent + // Convert the relative drag position from our Entry coordinates to be relative + // for Scroll.Content + pevt.Position = pevt.Position.Subtract(e.scroll.Offset) if !e.selecting { - e.selectRow, e.selectColumn = e.getRowCol(&d.PointEvent) - + e.selectRow, e.selectColumn = e.getRowCol(&pevt) e.selecting = true } - e.updateMousePointer(&d.PointEvent, false) + e.updateMousePointer(&pevt, false) } // Enable this widget, updating any style or features appropriately. @@ -494,7 +508,9 @@ func (e *Entry) TypedKey(key *fyne.KeyEvent) { if e.Disabled() { return } - + if e.cursorAnim != nil { + e.cursorAnim.interrupt() + } e.propertyLock.RLock() provider := e.textProvider() onSubmitted := e.OnSubmitted @@ -721,9 +737,10 @@ func (e *Entry) copyToClipboard(clipboard fyne.Clipboard) { func (e *Entry) cursorColAt(text []rune, pos fyne.Position) int { for i := 0; i < len(text); i++ { - str := string(text[0 : i+1]) - wid := fyne.MeasureText(str, theme.TextSize(), e.textStyle()).Width + theme.Padding() - if wid > pos.X+theme.Padding() { + str := string(text[0:i]) + wid := fyne.MeasureText(str, theme.TextSize(), e.textStyle()).Width + charWid := fyne.MeasureText(string(text[i]), theme.TextSize(), e.textStyle()).Width + if pos.X < theme.Padding()*2+wid+(charWid/2) { return i } } @@ -1080,6 +1097,24 @@ 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())) @@ -1087,10 +1122,8 @@ func (r *entryRenderer) Layout(size fyne.Size) { 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)) @@ -1105,17 +1138,20 @@ 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. @@ -1123,7 +1159,7 @@ func (r *entryRenderer) Layout(size fyne.Size) { // 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)) @@ -1147,12 +1183,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 } @@ -1190,7 +1258,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()) } @@ -1199,6 +1267,10 @@ func (r *entryRenderer) ensureValidationSetup() { r.entry.validationStatus = newValidationStatus(r.entry) r.objects = append(r.objects, r.entry.validationStatus) r.Layout(r.entry.size) + + if r.entry.Text != "" { + r.entry.Validate() + } r.Refresh() } } @@ -1215,9 +1287,6 @@ type entryContent struct { func (e *entryContent) CreateRenderer() fyne.WidgetRenderer { e.ExtendBaseWidget(e) - cursor := canvas.NewRectangle(color.Transparent) - cursor.Hide() - e.entry.propertyLock.Lock() defer e.entry.propertyLock.Unlock() provider := e.entry.textProvider() @@ -1225,9 +1294,9 @@ func (e *entryContent) CreateRenderer() fyne.WidgetRenderer { if provider.len() != 0 { placeholder.Hide() } - objects := []fyne.CanvasObject{placeholder, provider, cursor} + objects := []fyne.CanvasObject{placeholder, provider, e.entry.cursorAnim.cursor} - r := &entryContentRenderer{cursor, []fyne.CanvasObject{}, nil, objects, + r := &entryContentRenderer{e.entry.cursorAnim.cursor, []fyne.CanvasObject{}, objects, provider, placeholder, e} r.updateScrollDirections() r.Layout(e.size) @@ -1256,17 +1325,16 @@ func (e *entryContent) Dragged(d *fyne.DragEvent) { var _ fyne.WidgetRenderer = (*entryContentRenderer)(nil) type entryContentRenderer struct { - cursor *canvas.Rectangle - selection []fyne.CanvasObject - cursorAnim *fyne.Animation - objects []fyne.CanvasObject + cursor *canvas.Rectangle + selection []fyne.CanvasObject + objects []fyne.CanvasObject provider, placeholder *textProvider content *entryContent } func (r *entryContentRenderer) Destroy() { - r.cursorAnim.Stop() + r.content.entry.cursorAnim.stop() } func (r *entryContentRenderer) Layout(size fyne.Size) { @@ -1289,7 +1357,9 @@ func (r *entryContentRenderer) Objects() []fyne.CanvasObject { defer r.content.entry.propertyLock.RUnlock() // Objects are generated dynamically force selection rectangles to appear underneath the text if r.content.entry.selecting { - return append(r.selection, r.objects...) + objs := make([]fyne.CanvasObject, 0, len(r.selection)+len(r.objects)) + objs = append(objs, r.selection...) + return append(objs, r.objects...) } return r.objects } @@ -1316,15 +1386,9 @@ func (r *entryContentRenderer) Refresh() { if focused { r.cursor.Show() - if r.cursorAnim == nil { - r.cursorAnim = makeCursorAnimation(r.cursor) - r.cursorAnim.Start() - } + r.content.entry.cursorAnim.start() } else { - if r.cursorAnim != nil { - r.cursorAnim.Stop() - r.cursorAnim = nil - } + r.content.entry.cursorAnim.stop() r.cursor.Hide() } r.moveCursor() @@ -1446,8 +1510,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() { @@ -1572,17 +1638,3 @@ func getTextWhitespaceRegion(row []rune, col int) (int, int) { } return start, end } - -func makeCursorAnimation(cursor *canvas.Rectangle) *fyne.Animation { - cursorOpaque := theme.PrimaryColor() - r, g, b, _ := theme.PrimaryColor().RGBA() - cursorDim := color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 0x16} - anim := canvas.NewColorRGBAAnimation(cursorDim, cursorOpaque, time.Second/2, func(c color.Color) { - cursor.FillColor = c - cursor.Refresh() - }) - anim.RepeatCount = fyne.AnimationRepeatForever - anim.AutoReverse = true - - return anim -} diff --git a/widget/entry_cursor_anim.go b/widget/entry_cursor_anim.go new file mode 100644 index 0000000000..5fcfc83e13 --- /dev/null +++ b/widget/entry_cursor_anim.go @@ -0,0 +1,106 @@ +package widget + +import ( + "image/color" + "sync" + "time" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/canvas" + "fyne.io/fyne/v2/theme" +) + +const cursorInterruptTime = 300 * time.Millisecond + +type entryCursorAnimation struct { + mu *sync.RWMutex + cursor *canvas.Rectangle + anim *fyne.Animation + lastInterruptTime time.Time + + timeNow func() time.Time // useful for testing +} + +func newEntryCursorAnimation(cursor *canvas.Rectangle) *entryCursorAnimation { + a := &entryCursorAnimation{mu: &sync.RWMutex{}, cursor: cursor, timeNow: time.Now} + return a +} + +// creates fyne animation +func (a *entryCursorAnimation) createAnim(inverted bool) *fyne.Animation { + cursorOpaque := theme.PrimaryColor() + r, g, b, _ := theme.PrimaryColor().RGBA() + cursorDim := color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 0x16} + start, end := color.Color(cursorDim), color.Color(cursorOpaque) + if inverted { + start, end = color.Color(cursorOpaque), color.Color(cursorDim) + } + interrupted := false + anim := canvas.NewColorRGBAAnimation(start, end, time.Second/2, func(c color.Color) { + a.mu.RLock() + shouldInterrupt := a.timeNow().Sub(a.lastInterruptTime) <= cursorInterruptTime + a.mu.RUnlock() + if shouldInterrupt { + if !interrupted { + a.cursor.FillColor = cursorOpaque + a.cursor.Refresh() + interrupted = true + } + return + } + if interrupted { + a.mu.Lock() + a.anim.Stop() + if !inverted { + a.anim = a.createAnim(true) + } + interrupted = false + a.mu.Unlock() + go func() { + a.mu.RLock() + canStart := a.anim != nil + a.mu.RUnlock() + if canStart { + a.anim.Start() + } + }() + return + } + a.cursor.FillColor = c + a.cursor.Refresh() + }) + + anim.RepeatCount = fyne.AnimationRepeatForever + anim.AutoReverse = true + return anim +} + +// starts cursor animation. +func (a *entryCursorAnimation) start() { + a.mu.Lock() + isStopped := a.anim == nil + if isStopped { + a.anim = a.createAnim(false) + } + a.mu.Unlock() + if isStopped { + a.anim.Start() + } +} + +// temporarily stops the animation by "cursorInterruptTime". +func (a *entryCursorAnimation) interrupt() { + a.mu.Lock() + a.lastInterruptTime = a.timeNow() + a.mu.Unlock() +} + +// stops cursor animation. +func (a *entryCursorAnimation) stop() { + a.mu.Lock() + if a.anim != nil { + a.anim.Stop() + a.anim = nil + } + a.mu.Unlock() +} diff --git a/widget/entry_cursor_anim_test.go b/widget/entry_cursor_anim_test.go new file mode 100644 index 0000000000..d25d1d3c63 --- /dev/null +++ b/widget/entry_cursor_anim_test.go @@ -0,0 +1,73 @@ +package widget + +import ( + "image/color" + "runtime" + "testing" + "time" + + "fyne.io/fyne/v2/canvas" + _ "fyne.io/fyne/v2/test" + "fyne.io/fyne/v2/theme" + "github.com/stretchr/testify/assert" +) + +func TestEntryCursorAnim(t *testing.T) { + cursorOpaque := theme.PrimaryColor() + r, g, b, _ := theme.PrimaryColor().RGBA() + cursorDim := color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 0x16} + + alphaEquals := func(color1, color2 color.Color) bool { + _, _, _, a1 := color1.RGBA() + _, _, _, a2 := color2.RGBA() + return a1 == a2 + } + + cursor := canvas.NewRectangle(color.Black) + a := newEntryCursorAnimation(cursor) + + a.start() + a.anim.Tick(0.0) + assert.True(t, alphaEquals(cursorDim, a.cursor.FillColor)) + a.anim.Tick(1.0) + assert.True(t, alphaEquals(cursorOpaque, a.cursor.FillColor)) + + a.interrupt() + a.anim.Tick(0.0) + assert.True(t, alphaEquals(cursorOpaque, a.cursor.FillColor)) + a.anim.Tick(0.5) + assert.True(t, alphaEquals(cursorOpaque, a.cursor.FillColor)) + a.anim.Tick(1.0) + assert.True(t, alphaEquals(cursorOpaque, a.cursor.FillColor)) + + a.timeNow = func() time.Time { + return time.Now().Add(cursorInterruptTime) + } + // animation should be restarted inverting the colors + a.anim.Tick(0.0) + runtime.Gosched() + time.Sleep(10 * time.Millisecond) // ensure go routine for restart animation is executed + a.anim.Tick(0.0) + assert.True(t, alphaEquals(cursorOpaque, a.cursor.FillColor)) + a.anim.Tick(1.0) + assert.True(t, alphaEquals(cursorDim, a.cursor.FillColor)) + + a.timeNow = time.Now + a.interrupt() + a.anim.Tick(0.0) + assert.True(t, alphaEquals(cursorOpaque, a.cursor.FillColor)) + + a.timeNow = func() time.Time { + return time.Now().Add(cursorInterruptTime) + } + a.anim.Tick(0.0) + runtime.Gosched() + time.Sleep(10 * time.Millisecond) // ensure go routine for restart animation is executed + a.anim.Tick(0.0) + assert.True(t, alphaEquals(cursorOpaque, a.cursor.FillColor)) + a.anim.Tick(1.0) + assert.True(t, alphaEquals(cursorDim, a.cursor.FillColor)) + + a.stop() + assert.Nil(t, a.anim) +} diff --git a/widget/entry_internal_test.go b/widget/entry_internal_test.go index 8bce6d8afa..ec307c7346 100644 --- a/widget/entry_internal_test.go +++ b/widget/entry_internal_test.go @@ -93,6 +93,28 @@ func TestEntry_DragSelect(t *testing.T) { assert.Equal(t, "r the laz", entry.SelectedText()) } +func TestEntry_DragSelectWithScroll(t *testing.T) { + entry := NewEntry() + entry.SetText("The quick brown fox jumped over and over the lazy dog.") + + // get position after the letter 'a'. + ev1 := getClickPosition("The quick brown fox jumped over and over the la", 0) + // get position after the letter 'u' + ev2 := getClickPosition("The qu", 0) + + // mouse down and drag from 'a' to 'i' + me := &desktop.MouseEvent{PointEvent: *ev1, Button: desktop.MouseButtonPrimary} + entry.MouseDown(me) + de := &fyne.DragEvent{PointEvent: *ev1, Dragged: fyne.NewDelta(1, 0)} + entry.Dragged(de) + de = &fyne.DragEvent{PointEvent: *ev2, Dragged: fyne.NewDelta(1, 0)} + entry.Dragged(de) + me = &desktop.MouseEvent{PointEvent: *ev1, Button: desktop.MouseButtonPrimary} + entry.MouseUp(me) + + assert.Equal(t, "ick brown fox jumped over and over the la", entry.SelectedText()) +} + func TestEntry_ExpandSelectionForDoubleTap(t *testing.T) { str := []rune(" fish 日本語日 \t test 本日本 moose \t") @@ -269,7 +291,7 @@ func TestEntry_TabSelection(t *testing.T) { } func getClickPosition(str string, row int) *fyne.PointEvent { - x := fyne.MeasureText(str, theme.TextSize(), fyne.TextStyle{}).Width + x := fyne.MeasureText(str, theme.TextSize(), fyne.TextStyle{}).Width + theme.Padding() rowHeight := fyne.MeasureText("M", theme.TextSize(), fyne.TextStyle{}).Height y := float32(row)*rowHeight + rowHeight/2 diff --git a/widget/entry_password.go b/widget/entry_password.go index 6b73f8a08d..acf80a5c3b 100644 --- a/widget/entry_password.go +++ b/widget/entry_password.go @@ -46,7 +46,7 @@ func (r *passwordRevealer) Tapped(*fyne.PointEvent) { r.entry.setFieldsAndRefresh(func() { r.entry.Password = !r.entry.Password }) - fyne.CurrentApp().Driver().CanvasForObject(r).Focus(r.entry) + fyne.CurrentApp().Driver().CanvasForObject(r).Focus(r.entry.super().(fyne.Focusable)) } var _ fyne.WidgetRenderer = (*passwordRevealerRenderer)(nil) diff --git a/widget/entry_password_extend_test.go b/widget/entry_password_extend_test.go new file mode 100644 index 0000000000..489b64f584 --- /dev/null +++ b/widget/entry_password_extend_test.go @@ -0,0 +1,31 @@ +package widget + +import ( + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/test" + + "github.com/stretchr/testify/assert" +) + +type extendEntry struct { + Entry +} + +func TestEntry_Password_Extended_CreateRenderer(t *testing.T) { + entry := &extendEntry{} + entry.ExtendBaseWidget(entry) + entry.Password = true + entry.Wrapping = fyne.TextTruncate + assert.NotNil(t, test.WidgetRenderer(entry)) + r := test.WidgetRenderer(entry).(*entryRenderer).scroll.Content.(*entryContent) + p := test.WidgetRenderer(r).(*entryContentRenderer).provider + texts := test.WidgetRenderer(p).(*textRenderer).texts + + test.Type(entry, "Pass") + assert.Equal(t, passwordChar+passwordChar+passwordChar+passwordChar, texts[0].Text) + assert.NotNil(t, entry.ActionItem) + test.Tap(entry.ActionItem.(*passwordRevealer)) + assert.Equal(t, "Pass", texts[0].Text) +} diff --git a/widget/entry_test.go b/widget/entry_test.go index 1981a9a1f7..96cfc0b52e 100644 --- a/widget/entry_test.go +++ b/widget/entry_test.go @@ -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, @@ -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, @@ -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() @@ -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() @@ -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)) diff --git a/widget/fileicon.go b/widget/fileicon.go index b2c10df17e..fd6cfef74e 100644 --- a/widget/fileicon.go +++ b/widget/fileicon.go @@ -67,18 +67,25 @@ func (i *FileIcon) MinSize() fyne.Size { // CreateRenderer is a private method to Fyne which links this widget to its renderer func (i *FileIcon) CreateRenderer() fyne.WidgetRenderer { i.ExtendBaseWidget(i) + i.propertyLock.Lock() + i.setURI(i.URI) + i.propertyLock.Unlock() + i.propertyLock.RLock() defer i.propertyLock.RUnlock() - i.setURI(i.URI) - // TODO should FileIcon render a background representing selection, or should it be in the collection? // TODO file dialog currently uses a container with NewGridWrapLayout, but if this changes to List, or Table then the primary color background would be rendered twice. background := canvas.NewRectangle(theme.PrimaryColor()) background.Hide() s := &fileIconRenderer{file: i, background: background} - s.updateObjects() + s.img = canvas.NewImageFromResource(s.file.resource) + s.img.FillMode = canvas.ImageFillContain + s.ext = canvas.NewText(s.file.extension, theme.BackgroundColor()) + s.ext.Alignment = fyne.TextAlignCenter + + s.SetObjects([]fyne.CanvasObject{s.background, s.img, s.ext}) i.cachedURI = i.URI return s @@ -149,9 +156,13 @@ func (s *fileIconRenderer) Layout(size fyne.Size) { } yoff += isize * ratioDown + oldSize := s.ext.TextSize s.ext.TextSize = float32(int(isize * ratioTextSize)) s.ext.Resize(fyne.NewSize(isize, s.ext.MinSize().Height)) s.ext.Move(fyne.NewPos(xoff, yoff)) + if oldSize != s.ext.TextSize { + s.ext.Refresh() + } s.Objects()[0].Resize(size) s.Objects()[1].Resize(size) @@ -159,10 +170,14 @@ func (s *fileIconRenderer) Layout(size fyne.Size) { func (s *fileIconRenderer) Refresh() { if s.file.URI != s.file.cachedURI { - s.file.propertyLock.RLock() + s.file.propertyLock.Lock() s.file.setURI(s.file.URI) - s.updateObjects() + s.file.propertyLock.Unlock() + + s.file.propertyLock.RLock() s.file.cachedURI = s.file.URI + s.img.Resource = s.file.resource + s.ext.Text = s.file.extension s.file.propertyLock.RUnlock() } @@ -184,16 +199,6 @@ func (s *fileIconRenderer) Refresh() { canvas.Refresh(s.ext) } -func (s *fileIconRenderer) updateObjects() { - s.img = canvas.NewImageFromResource(s.file.resource) - s.ext = canvas.NewText(s.file.extension, theme.BackgroundColor()) - s.img.FillMode = canvas.ImageFillContain - s.ext.Alignment = fyne.TextAlignCenter - s.ext.TextSize = theme.TextSize() - - s.SetObjects([]fyne.CanvasObject{s.background, s.img, s.ext}) -} - func trimmedExtension(uri fyne.URI) string { ext := uri.Extension() if len(ext) > 5 { diff --git a/widget/fileicon_internal_test.go b/widget/fileicon_internal_test.go index 0c32088567..ee2b04c838 100644 --- a/widget/fileicon_internal_test.go +++ b/widget/fileicon_internal_test.go @@ -23,23 +23,23 @@ func newRenderedFileIcon(uri fyne.URI) *FileIcon { } func TestFileIcon_NewFileIcon(t *testing.T) { - item := newRenderedFileIcon(storage.NewURI("file:///path/to/filename.zip")) + item := newRenderedFileIcon(storage.NewFileURI("/path/to/filename.zip")) assert.Equal(t, ".zip", item.extension) assert.Equal(t, theme.FileApplicationIcon(), item.resource) - item = newRenderedFileIcon(storage.NewURI("file:///path/to/filename.mp3")) + item = newRenderedFileIcon(storage.NewFileURI("/path/to/filename.mp3")) assert.Equal(t, ".mp3", item.extension) assert.Equal(t, theme.FileAudioIcon(), item.resource) - item = newRenderedFileIcon(storage.NewURI("file:///path/to/filename.png")) + item = newRenderedFileIcon(storage.NewFileURI("/path/to/filename.png")) assert.Equal(t, ".png", item.extension) assert.Equal(t, theme.FileImageIcon(), item.resource) - item = newRenderedFileIcon(storage.NewURI("file:///path/to/filename.txt")) + item = newRenderedFileIcon(storage.NewFileURI("/path/to/filename.txt")) assert.Equal(t, ".txt", item.extension) assert.Equal(t, theme.FileTextIcon(), item.resource) - item = newRenderedFileIcon(storage.NewURI("file:///path/to/filename.mp4")) + item = newRenderedFileIcon(storage.NewFileURI("/path/to/filename.mp4")) assert.Equal(t, ".mp4", item.extension) assert.Equal(t, theme.FileVideoIcon(), item.resource) } @@ -53,11 +53,11 @@ func TestFileIcon_NewFileIcon_NoExtension(t *testing.T) { binFileWithNoExt := filepath.Join(workingDir, "testdata/bin") textFileWithNoExt := filepath.Join(workingDir, "testdata/text") - item := newRenderedFileIcon(storage.NewURI("file://" + binFileWithNoExt)) + item := newRenderedFileIcon(storage.NewFileURI(binFileWithNoExt)) assert.Equal(t, "", item.extension) assert.Equal(t, theme.FileApplicationIcon(), item.resource) - item = newRenderedFileIcon(storage.NewURI("file://" + textFileWithNoExt)) + item = newRenderedFileIcon(storage.NewFileURI(textFileWithNoExt)) assert.Equal(t, "", item.extension) assert.Equal(t, theme.FileTextIcon(), item.resource) } @@ -70,11 +70,11 @@ func TestFileIcon_NewURI_WithFolder(t *testing.T) { } dir := filepath.Join(workingDir, "testdata") - item := newRenderedFileIcon(storage.NewURI("file://" + dir)) + item := newRenderedFileIcon(storage.NewFileURI(dir)) assert.Empty(t, item.extension) assert.Equal(t, theme.FolderIcon(), item.resource) - item.SetURI(storage.NewURI("file://" + dir)) + item.SetURI(storage.NewFileURI(dir)) assert.Empty(t, item.extension) assert.Equal(t, theme.FolderIcon(), item.resource) } @@ -97,28 +97,28 @@ func TestFileIcon_NewFileIcon_Rendered(t *testing.T) { test.AssertImageMatches(t, "fileicon/fileicon_nil.png", w.Canvas().Capture()) text := filepath.Join(workingDir, "testdata/text") - icon2 := NewFileIcon(storage.NewURI("file://" + text)) + icon2 := NewFileIcon(storage.NewFileURI(text)) w.SetContent(icon2) w.Resize(fyne.NewSize(150, 150)) test.AssertImageMatches(t, "fileicon/fileicon_text.png", w.Canvas().Capture()) text += ".txt" - icon3 := NewFileIcon(storage.NewURI("file://" + text)) + icon3 := NewFileIcon(storage.NewFileURI(text)) w.SetContent(icon3) w.Resize(fyne.NewSize(150, 150)) test.AssertImageMatches(t, "fileicon/fileicon_text_txt.png", w.Canvas().Capture()) bin := filepath.Join(workingDir, "testdata/bin") - icon4 := NewFileIcon(storage.NewURI("file://" + bin)) + icon4 := NewFileIcon(storage.NewFileURI(bin)) w.SetContent(icon4) w.Resize(fyne.NewSize(150, 150)) test.AssertImageMatches(t, "fileicon/fileicon_bin.png", w.Canvas().Capture()) dir := filepath.Join(workingDir, "testdata") - icon5 := NewFileIcon(storage.NewURI("file://" + dir)) + icon5 := NewFileIcon(storage.NewFileURI(dir)) w.SetContent(icon5) w.Resize(fyne.NewSize(150, 150)) @@ -128,23 +128,23 @@ func TestFileIcon_NewFileIcon_Rendered(t *testing.T) { } func TestFileIcon_SetURI(t *testing.T) { - item := newRenderedFileIcon(storage.NewURI("file:///path/to/filename.zip")) + item := newRenderedFileIcon(storage.NewFileURI("/path/to/filename.zip")) assert.Equal(t, ".zip", item.extension) assert.Equal(t, theme.FileApplicationIcon(), item.resource) - item.SetURI(storage.NewURI("file:///path/to/filename.mp3")) + item.SetURI(storage.NewFileURI("/path/to/filename.mp3")) assert.Equal(t, ".mp3", item.extension) assert.Equal(t, theme.FileAudioIcon(), item.resource) - item.SetURI(storage.NewURI("file:///path/to/filename.png")) + item.SetURI(storage.NewFileURI("/path/to/filename.png")) assert.Equal(t, ".png", item.extension) assert.Equal(t, theme.FileImageIcon(), item.resource) - item.SetURI(storage.NewURI("file:///path/to/filename.txt")) + item.SetURI(storage.NewFileURI("/path/to/filename.txt")) assert.Equal(t, ".txt", item.extension) assert.Equal(t, theme.FileTextIcon(), item.resource) - item.SetURI(storage.NewURI("file:///path/to/filename.mp4")) + item.SetURI(storage.NewFileURI("/path/to/filename.mp4")) assert.Equal(t, ".mp4", item.extension) assert.Equal(t, theme.FileVideoIcon(), item.resource) } @@ -160,11 +160,11 @@ func TestFileIcon_SetURI_WithFolder(t *testing.T) { item := newRenderedFileIcon(nil) assert.Empty(t, item.extension) - item.SetURI(storage.NewURI("file://" + dir)) + item.SetURI(storage.NewFileURI(dir)) assert.Empty(t, item.extension) assert.Equal(t, theme.FolderIcon(), item.resource) - item.SetURI(storage.NewURI("file://" + dir)) + item.SetURI(storage.NewFileURI(dir)) assert.Empty(t, item.extension) assert.Equal(t, theme.FolderIcon(), item.resource) } diff --git a/widget/form.go b/widget/form.go index c75fd82141..c6b69660ac 100644 --- a/widget/form.go +++ b/widget/form.go @@ -1,6 +1,8 @@ package widget import ( + "errors" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/internal/cache" @@ -8,6 +10,10 @@ import ( "fyne.io/fyne/v2/theme" ) +// errFormItemInitialState defines the error if the initial validation for a FormItem result +// in an error +var errFormItemInitialState = errors.New("widget.FormItem initial state error") + // FormItem provides the details for a row in a form type FormItem struct { Text string @@ -155,10 +161,14 @@ func (f *Form) checkValidation(err error) { func (f *Form) setUpValidation(widget fyne.CanvasObject, i int) { if w, ok := widget.(fyne.Validatable); ok { f.Items[i].invalid = w.Validate() != nil - if e, ok := w.(*Entry); ok && e.Validator != nil { - e.SetValidationError(nil) // clear initial state, will appear when we type + if e, ok := w.(*Entry); ok && e.Validator != nil && f.Items[i].invalid { + // set initial state error to guarantee next error (if triggers) is always different + e.SetValidationError(errFormItemInitialState) } w.SetOnValidationChanged(func(err error) { + if err == errFormItemInitialState { + return + } f.Items[i].validationError = err f.Items[i].invalid = err != nil f.checkValidation(err) diff --git a/widget/form_test.go b/widget/form_test.go index ad7c9f006f..5e037296ce 100644 --- a/widget/form_test.go +++ b/widget/form_test.go @@ -1,6 +1,7 @@ package widget import ( + "errors" "testing" "fyne.io/fyne/v2" @@ -188,3 +189,46 @@ func TestForm_Validation(t *testing.T) { test.AssertImageMatches(t, "form/validation_valid.png", w.Canvas().Capture()) } + +func TestForm_EntryValidation_FirstTypeValid(t *testing.T) { + app := test.NewApp() + defer test.NewApp() + app.Settings().SetTheme(theme.LightTheme()) + + notEmptyValidator := func(s string) error { + if s == "" { + return errors.New("can't be empty") + } + return nil + } + + entry1 := &Entry{Validator: notEmptyValidator, Text: ""} + entry2 := &Entry{Validator: notEmptyValidator, Text: ""} + items := []*FormItem{ + {Text: "First", Widget: entry1}, + {Text: "Second", Widget: entry2}, + } + + form := &Form{Items: items, OnSubmit: func() {}, OnCancel: func() {}} + w := test.NewWindow(form) + defer w.Close() + + assert.Equal(t, errFormItemInitialState, entry1.validationError) + assert.Equal(t, errFormItemInitialState, entry2.validationError) + + test.AssertImageMatches(t, "form/validation_entry_first_type_initial.png", w.Canvas().Capture()) + + test.Type(entry1, "H") + test.Type(entry2, "L") + entry1.focused = false + entry1.Refresh() + w = test.NewWindow(form) + + test.AssertImageMatches(t, "form/validation_entry_first_type_valid.png", w.Canvas().Capture()) + + entry1.SetText("") + entry2.SetText("") + w = test.NewWindow(form) + + test.AssertImageMatches(t, "form/validation_entry_first_type_invalid.png", w.Canvas().Capture()) +} diff --git a/widget/hyperlink.go b/widget/hyperlink.go index d40f00f301..9863d5a8b0 100644 --- a/widget/hyperlink.go +++ b/widget/hyperlink.go @@ -121,7 +121,7 @@ func (hl *Hyperlink) Tapped(*fyne.PointEvent) { func (hl *Hyperlink) CreateRenderer() fyne.WidgetRenderer { hl.ExtendBaseWidget(hl) hl.provider = newTextProvider(hl.Text, hl) - hl.provider.extraPad = fyne.NewSize(0, theme.Padding()) + hl.provider.extraPad = fyne.NewSize(theme.Padding(), theme.Padding()) return hl.provider.CreateRenderer() } diff --git a/widget/label.go b/widget/label.go index f96058d8e6..ccc6ada36b 100644 --- a/widget/label.go +++ b/widget/label.go @@ -152,7 +152,7 @@ func (l *Label) object() fyne.Widget { func (l *Label) CreateRenderer() fyne.WidgetRenderer { l.ExtendBaseWidget(l) l.provider = newTextProvider(l.Text, l) - l.provider.extraPad = fyne.NewSize(0, theme.Padding()) + l.provider.extraPad = fyne.NewSize(theme.Padding(), theme.Padding()) l.provider.size = l.size return l.provider.CreateRenderer() } diff --git a/widget/list.go b/widget/list.go index cc0be32c47..f5f933da7f 100644 --- a/widget/list.go +++ b/widget/list.go @@ -3,6 +3,7 @@ package widget import ( "fmt" "math" + "sync" "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" @@ -78,12 +79,12 @@ func (l *List) CreateRenderer() fyne.WidgetRenderer { l.itemMin = newListItem(f(), nil).MinSize() } } - layout := fyne.NewContainerWithLayout(newListLayout(l)) - layout.Resize(layout.MinSize()) + layout := &fyne.Container{} l.scroller = widget.NewVScroll(layout) + layout.Layout = newListLayout(l) + layout.Resize(layout.MinSize()) objects := []fyne.CanvasObject{l.scroller} lr := newListRenderer(objects, l, l.scroller, layout) - l.offsetUpdated = lr.offsetUpdated return lr } @@ -148,87 +149,19 @@ var _ fyne.WidgetRenderer = (*listRenderer)(nil) type listRenderer struct { widget.BaseRenderer - list *List - scroller *widget.Scroll - layout *fyne.Container - itemPool *syncPool - children []fyne.CanvasObject - size fyne.Size - visibleItemCount int - firstItemIndex ListItemID - lastItemIndex ListItemID - previousOffsetY float32 + list *List + scroller *widget.Scroll + layout *fyne.Container } func newListRenderer(objects []fyne.CanvasObject, l *List, scroller *widget.Scroll, layout *fyne.Container) *listRenderer { lr := &listRenderer{BaseRenderer: widget.NewBaseRenderer(objects), list: l, scroller: scroller, layout: layout} - lr.scroller.OnScrolled = lr.offsetUpdated + lr.scroller.OnScrolled = l.offsetUpdated return lr } func (l *listRenderer) Layout(size fyne.Size) { - length := 0 - if f := l.list.Length; f != nil { - length = f() - } - if length <= 0 { - if len(l.children) > 0 { - for _, child := range l.children { - l.itemPool.Release(child) - } - l.previousOffsetY = 0 - l.firstItemIndex = 0 - l.lastItemIndex = 0 - l.visibleItemCount = 0 - l.list.offsetY = 0 - l.layout.Layout.(*listLayout).layoutEndY = 0 - l.children = nil - l.layout.Objects = nil - l.list.Refresh() - } - return - } - if size != l.size { - if size.Width != l.size.Width { - for _, child := range l.children { - child.Resize(fyne.NewSize(size.Width, l.list.itemMin.Height)) - } - } - l.scroller.Resize(size) - l.size = size - } - if l.itemPool == nil { - l.itemPool = &syncPool{} - } - - // Relayout What Is Visible - no scroll change - initial layout or possibly from a resize. - l.visibleItemCount = int(math.Ceil(float64(l.scroller.Size().Height) / float64(l.list.itemMin.Height+theme.SeparatorThicknessSize()))) - if l.visibleItemCount <= 0 { - return - } - min := int(fyne.Min(float32(length), float32(l.visibleItemCount))) - if len(l.children) > min { - for i := len(l.children); i >= min; i-- { - l.itemPool.Release(l.children[i-1]) - } - l.children = l.children[:min-1] - } - for i := len(l.children) + l.firstItemIndex; len(l.children) <= l.visibleItemCount && i < length; i++ { - l.appendItem(i) - } - l.layout.Layout.(*listLayout).children = l.children - l.layout.Layout.Layout(l.children, l.list.itemMin) - l.layout.Objects = l.layout.Layout.(*listLayout).getObjects() - l.lastItemIndex = l.firstItemIndex + len(l.children) - 1 - - i := l.firstItemIndex - for _, child := range l.children { - if f := l.list.UpdateItem; f != nil { - f(i, child.(*listItem).child) - } - l.setupListItem(child, i) - i++ - } + l.scroller.Resize(size) } func (l *listRenderer) MinSize() fyne.Size { @@ -244,125 +177,6 @@ func (l *listRenderer) Refresh() { canvas.Refresh(l.list.super()) } -func (l *listRenderer) appendItem(id ListItemID) { - item := l.getItem() - l.children = append(l.children, item) - l.setupListItem(item, id) - l.layout.Layout.(*listLayout).children = l.children - l.layout.Layout.(*listLayout).appendedItem(l.children) - l.layout.Objects = l.layout.Layout.(*listLayout).getObjects() -} - -func (l *listRenderer) getItem() fyne.CanvasObject { - item := l.itemPool.Obtain() - if item == nil { - if f := l.list.CreateItem; f != nil { - item = newListItem(f(), nil) - } - } - return item -} - -func (l *listRenderer) offsetChanged() { - offsetChange := float32(math.Abs(float64(l.previousOffsetY - l.list.offsetY))) - - if l.previousOffsetY < l.list.offsetY { - // Scrolling Down. - l.scrollDown(offsetChange) - } else if l.previousOffsetY > l.list.offsetY { - // Scrolling Up. - l.scrollUp(offsetChange) - } - l.layout.Layout.(*listLayout).updateDividers() -} - -func (l *listRenderer) prependItem(id ListItemID) { - item := l.getItem() - l.children = append([]fyne.CanvasObject{item}, l.children...) - l.setupListItem(item, id) - l.layout.Layout.(*listLayout).children = l.children - l.layout.Layout.(*listLayout).prependedItem(l.children) - l.layout.Objects = l.layout.Layout.(*listLayout).getObjects() -} - -func (l *listRenderer) scrollDown(offsetChange float32) { - itemChange := 0 - separatorThickness := theme.SeparatorThicknessSize() - layoutEndY := l.children[len(l.children)-1].Position().Y + l.list.itemMin.Height + separatorThickness - scrollerEndY := l.scroller.Offset.Y + l.scroller.Size().Height - if layoutEndY < scrollerEndY { - itemChange = int(math.Ceil(float64(scrollerEndY-layoutEndY) / float64(l.list.itemMin.Height+separatorThickness))) - } else if offsetChange < l.list.itemMin.Height+separatorThickness { - return - } else { - itemChange = int(math.Floor(float64(offsetChange) / float64(l.list.itemMin.Height+separatorThickness))) - } - l.previousOffsetY = l.list.offsetY - length := 0 - if f := l.list.Length; f != nil { - length = f() - } - if length == 0 { - return - } - for i := 0; i < itemChange && l.lastItemIndex != length-1; i++ { - l.itemPool.Release(l.children[0]) - l.children = l.children[1:] - l.firstItemIndex++ - l.lastItemIndex++ - l.appendItem(l.lastItemIndex) - } -} - -func (l *listRenderer) scrollUp(offsetChange float32) { - itemChange := 0 - layoutStartY := l.children[0].Position().Y - separatorThickness := theme.SeparatorThicknessSize() - if layoutStartY > l.scroller.Offset.Y { - itemChange = int(math.Ceil(float64(layoutStartY-l.scroller.Offset.Y) / float64(l.list.itemMin.Height+separatorThickness))) - } else if offsetChange < l.list.itemMin.Height+separatorThickness { - return - } else { - itemChange = int(math.Floor(float64(offsetChange) / float64(l.list.itemMin.Height+separatorThickness))) - } - l.previousOffsetY = l.list.offsetY - for i := 0; i < itemChange && l.firstItemIndex != 0; i++ { - l.itemPool.Release(l.children[len(l.children)-1]) - l.children = l.children[:len(l.children)-1] - l.firstItemIndex-- - l.lastItemIndex-- - l.prependItem(l.firstItemIndex) - } -} - -func (l *listRenderer) setupListItem(item fyne.CanvasObject, id ListItemID) { - li := item.(*listItem) - previousIndicator := li.selected - li.selected = false - for _, s := range l.list.selected { - if id == s { - li.selected = true - } - } - if previousIndicator != li.selected { - item.Refresh() - } - if f := l.list.UpdateItem; f != nil { - f(id, li.child) - } - li.onTapped = func() { - l.list.Select(id) - } -} - -func (l *listRenderer) offsetUpdated(pos fyne.Position) { - if l.list.offsetY == pos.Y { - return - } - l.list.offsetY = pos.Y - l.offsetChanged() -} - // Declare conformity with interfaces. var _ fyne.Widget = (*listItem)(nil) var _ fyne.Tappable = (*listItem)(nil) @@ -477,31 +291,26 @@ func (li *listItemRenderer) Refresh() { var _ fyne.Layout = (*listLayout)(nil) type listLayout struct { - list *List - dividers []fyne.CanvasObject - children []fyne.CanvasObject - layoutEndY float32 + list *List + dividers []fyne.CanvasObject + children []fyne.CanvasObject + + itemPool *syncPool + visible map[ListItemID]*listItem + renderLock sync.Mutex } func newListLayout(list *List) fyne.Layout { - return &listLayout{list: list} + l := &listLayout{list: list, itemPool: &syncPool{}, visible: make(map[ListItemID]*listItem)} + list.offsetUpdated = l.offsetUpdated + return l } -func (l *listLayout) Layout(objects []fyne.CanvasObject, size fyne.Size) { - if l.list.offsetY != 0 { - return - } - y := float32(0) - for _, child := range l.children { - child.Move(fyne.NewPos(0, y)) - y += l.list.itemMin.Height + theme.SeparatorThicknessSize() - child.Resize(fyne.NewSize(l.list.size.Width, l.list.itemMin.Height)) - } - l.layoutEndY = y - l.updateDividers() +func (l *listLayout) Layout([]fyne.CanvasObject, fyne.Size) { + l.updateList() } -func (l *listLayout) MinSize(objects []fyne.CanvasObject) fyne.Size { +func (l *listLayout) MinSize([]fyne.CanvasObject) fyne.Size { if f := l.list.Length; f != nil { separatorThickness := theme.SeparatorThicknessSize() return fyne.NewSize(l.list.itemMin.Width, @@ -510,24 +319,96 @@ func (l *listLayout) MinSize(objects []fyne.CanvasObject) fyne.Size { return fyne.NewSize(0, 0) } -func (l *listLayout) getObjects() []fyne.CanvasObject { - objects := l.children - objects = append(objects, l.dividers...) - return objects +func (l *listLayout) getItem() *listItem { + item := l.itemPool.Obtain() + if item == nil { + if f := l.list.CreateItem; f != nil { + item = newListItem(f(), nil) + } + } + return item.(*listItem) } -func (l *listLayout) appendedItem(objects []fyne.CanvasObject) { - if len(objects) > 1 { - objects[len(objects)-1].Move(fyne.NewPos(0, objects[len(objects)-2].Position().Y+l.list.itemMin.Height+theme.SeparatorThicknessSize())) - } else { - objects[len(objects)-1].Move(fyne.NewPos(0, 0)) +func (l *listLayout) offsetUpdated(pos fyne.Position) { + if l.list.offsetY == pos.Y { + return } - objects[len(objects)-1].Resize(fyne.NewSize(l.list.size.Width, l.list.itemMin.Height)) + l.list.offsetY = pos.Y + l.updateList() } -func (l *listLayout) prependedItem(objects []fyne.CanvasObject) { - objects[0].Move(fyne.NewPos(0, objects[1].Position().Y-l.list.itemMin.Height-theme.SeparatorThicknessSize())) - objects[0].Resize(fyne.NewSize(l.list.size.Width, l.list.itemMin.Height)) +func (l *listLayout) setupListItem(li *listItem, id ListItemID) { + previousIndicator := li.selected + li.selected = false + for _, s := range l.list.selected { + if id == s { + li.selected = true + break + } + } + if previousIndicator != li.selected { + li.Refresh() + } + if f := l.list.UpdateItem; f != nil { + f(id, li.child) + } + li.onTapped = func() { + l.list.Select(id) + } +} + +func (l *listLayout) updateList() { + l.renderLock.Lock() + defer l.renderLock.Unlock() + separatorThickness := theme.SeparatorThicknessSize() + width := l.list.Size().Width + length := 0 + if f := l.list.Length; f != nil { + length = f() + } + visibleItemCount := int(math.Ceil(float64(l.list.scroller.Size().Height)/float64(l.list.itemMin.Height+theme.SeparatorThicknessSize()))) + 1 + offY := l.list.offsetY - float32(int(l.list.offsetY)%int(l.list.itemMin.Height+separatorThickness)) + minRow := ListItemID(offY / (l.list.itemMin.Height + separatorThickness)) + maxRow := ListItemID(fyne.Min(float32(minRow+visibleItemCount), float32(length))) + + if l.list.UpdateItem == nil { + fyne.LogError("Missing UpdateCell callback required for List", nil) + } + + wasVisible := l.visible + l.visible = make(map[ListItemID]*listItem) + var cells []fyne.CanvasObject + y := offY + size := fyne.NewSize(width, l.list.itemMin.Height) + for row := minRow; row < maxRow; row++ { + c, ok := wasVisible[row] + if !ok { + c = l.getItem() + if c == nil { + continue + } + } + + c.Move(fyne.NewPos(0, y)) + c.Resize(size) + l.setupListItem(c, row) + + y += l.list.itemMin.Height + separatorThickness + l.visible[row] = c + cells = append(cells, c) + } + + for id, old := range wasVisible { + if _, ok := l.visible[id]; !ok { + l.itemPool.Release(old) + } + } + l.children = cells + l.updateDividers() + + objects := l.children + objects = append(objects, l.dividers...) + l.list.scroller.Content.(*fyne.Container).Objects = objects } func (l *listLayout) updateDividers() { diff --git a/widget/list_test.go b/widget/list_test.go index d4f5536d4c..a4eaecd05a 100644 --- a/widget/list_test.go +++ b/widget/list_test.go @@ -3,7 +3,6 @@ package widget import ( "fmt" "image/color" - "math" "testing" "time" @@ -21,15 +20,11 @@ func TestNewList(t *testing.T) { list := createList(1000) template := newListItem(fyne.NewContainerWithLayout(layout.NewHBoxLayout(), NewIcon(theme.DocumentIcon()), NewLabel("Template Object")), nil) - firstItemIndex := test.WidgetRenderer(list).(*listRenderer).firstItemIndex - lastItemIndex := test.WidgetRenderer(list).(*listRenderer).lastItemIndex - visibleCount := len(test.WidgetRenderer(list).(*listRenderer).children) assert.Equal(t, 1000, list.Length()) assert.GreaterOrEqual(t, list.MinSize().Width, template.MinSize().Width) assert.Equal(t, list.MinSize(), template.MinSize().Max(test.WidgetRenderer(list).(*listRenderer).scroller.MinSize())) - assert.Equal(t, 0, firstItemIndex) - assert.Equal(t, visibleCount, lastItemIndex-firstItemIndex+1) + assert.Equal(t, float32(0), list.offsetY) } func TestList_MinSize(t *testing.T) { @@ -63,27 +58,12 @@ func TestList_MinSize(t *testing.T) { func TestList_Resize(t *testing.T) { defer test.NewApp() list, w := setupList(t) - template := newListItem(fyne.NewContainerWithLayout(layout.NewHBoxLayout(), NewIcon(theme.DocumentIcon()), NewLabel("Template Object")), nil) - firstItemIndex := test.WidgetRenderer(list).(*listRenderer).firstItemIndex - lastItemIndex := test.WidgetRenderer(list).(*listRenderer).lastItemIndex - visibleCount := len(test.WidgetRenderer(list).(*listRenderer).children) - assert.Equal(t, 0, firstItemIndex) - assert.Equal(t, visibleCount, lastItemIndex-firstItemIndex+1) + assert.Equal(t, float32(0), list.offsetY) w.Resize(fyne.NewSize(200, 600)) - indexChange := int(math.Floor(float64(200) / float64(template.MinSize().Height))) - - newFirstItemIndex := test.WidgetRenderer(list).(*listRenderer).firstItemIndex - newLastItemIndex := test.WidgetRenderer(list).(*listRenderer).lastItemIndex - newVisibleCount := len(test.WidgetRenderer(list).(*listRenderer).children) - - assert.Equal(t, firstItemIndex, newFirstItemIndex) - assert.NotEqual(t, lastItemIndex, newLastItemIndex) - assert.Equal(t, newLastItemIndex, lastItemIndex+indexChange) - assert.NotEqual(t, visibleCount, newVisibleCount) - assert.Equal(t, newVisibleCount, newLastItemIndex-newFirstItemIndex+1) + assert.Equal(t, float32(0), list.offsetY) test.AssertRendersToMarkup(t, "list/resized.xml", w.Canvas()) } @@ -94,36 +74,19 @@ func TestList_OffsetChange(t *testing.T) { list := createList(1000) w := test.NewWindow(list) w.Resize(fyne.NewSize(200, 400)) - template := newListItem(fyne.NewContainerWithLayout(layout.NewHBoxLayout(), NewIcon(theme.DocumentIcon()), NewLabel("Template Object")), nil) - - firstItemIndex := test.WidgetRenderer(list).(*listRenderer).firstItemIndex - lastItemIndex := test.WidgetRenderer(list).(*listRenderer).lastItemIndex - visibleCount := test.WidgetRenderer(list).(*listRenderer).visibleItemCount - assert.Equal(t, 0, firstItemIndex) - assert.Equal(t, visibleCount, lastItemIndex-firstItemIndex) + assert.Equal(t, float32(0), list.offsetY) scroll := test.WidgetRenderer(list).(*listRenderer).scroller scroll.Scrolled(&fyne.ScrollEvent{Scrolled: fyne.NewDelta(0, -280)}) - indexChange := int(math.Floor(float64(300) / float64(template.MinSize().Height))) - - newFirstItemIndex := test.WidgetRenderer(list).(*listRenderer).firstItemIndex - newLastItemIndex := test.WidgetRenderer(list).(*listRenderer).lastItemIndex - newVisibleCount := test.WidgetRenderer(list).(*listRenderer).visibleItemCount - - assert.NotEqual(t, firstItemIndex, newFirstItemIndex) - assert.Equal(t, newFirstItemIndex, firstItemIndex+indexChange-1) - assert.NotEqual(t, lastItemIndex, newLastItemIndex) - assert.Equal(t, newLastItemIndex, lastItemIndex+indexChange-1) - assert.Equal(t, visibleCount, newVisibleCount) - assert.Equal(t, newVisibleCount, newLastItemIndex-newFirstItemIndex) + assert.NotEqual(t, 0, list.offsetY) test.AssertRendersToMarkup(t, "list/offset_changed.xml", w.Canvas()) } func TestList_Hover(t *testing.T) { list := createList(1000) - children := test.WidgetRenderer(list).(*listRenderer).children + children := list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children for i := 0; i < 2; i++ { assert.False(t, children[i].(*listItem).statusIndicator.Visible()) @@ -136,7 +99,7 @@ func TestList_Hover(t *testing.T) { func TestList_Selection(t *testing.T) { list := createList(1000) - children := test.WidgetRenderer(list).(*listRenderer).children + children := list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children assert.False(t, children[0].(*listItem).statusIndicator.Visible()) children[0].(*listItem).Tapped(&fyne.PointEvent{}) @@ -153,39 +116,52 @@ func TestList_Selection(t *testing.T) { } func TestList_Select(t *testing.T) { - list := createList(1000) + list := NewList( + func() int { + return 5 + }, + func() fyne.CanvasObject { + return NewLabel("") + }, + func(id ListItemID, item fyne.CanvasObject) { + }, + ) + list.Resize(fyne.NewSize(20, 20)) + list.Select(3) + + list = createList(1000) - assert.Equal(t, test.WidgetRenderer(list).(*listRenderer).firstItemIndex, 0) + assert.Equal(t, float32(0), list.offsetY) list.Select(50) - assert.Equal(t, test.WidgetRenderer(list).(*listRenderer).lastItemIndex, 50) - children := test.WidgetRenderer(list).(*listRenderer).children - assert.Equal(t, children[len(children)-1].(*listItem).statusIndicator.FillColor, theme.PrimaryColor()) - assert.True(t, children[len(children)-1].(*listItem).statusIndicator.Visible()) + assert.Equal(t, float32(1345), list.offsetY) + visible := list.scroller.Content.(*fyne.Container).Layout.(*listLayout).visible + assert.Equal(t, visible[50].statusIndicator.FillColor, theme.PrimaryColor()) + assert.True(t, visible[50].statusIndicator.Visible()) list.Select(5) - assert.Equal(t, test.WidgetRenderer(list).(*listRenderer).firstItemIndex, 5) - children = test.WidgetRenderer(list).(*listRenderer).children - assert.Equal(t, children[0].(*listItem).statusIndicator.FillColor, theme.PrimaryColor()) - assert.True(t, children[0].(*listItem).statusIndicator.Visible()) + assert.Equal(t, float32(230), list.offsetY) + visible = list.scroller.Content.(*fyne.Container).Layout.(*listLayout).visible + assert.Equal(t, visible[5].statusIndicator.FillColor, theme.PrimaryColor()) + assert.True(t, visible[5].statusIndicator.Visible()) list.Select(6) - assert.Equal(t, test.WidgetRenderer(list).(*listRenderer).firstItemIndex, 5) - children = test.WidgetRenderer(list).(*listRenderer).children - assert.False(t, children[0].(*listItem).statusIndicator.Visible()) - assert.Equal(t, children[1].(*listItem).statusIndicator.FillColor, theme.PrimaryColor()) - assert.True(t, children[1].(*listItem).statusIndicator.Visible()) + assert.Equal(t, float32(230), list.offsetY) + visible = list.scroller.Content.(*fyne.Container).Layout.(*listLayout).visible + assert.False(t, visible[5].statusIndicator.Visible()) + assert.Equal(t, visible[6].statusIndicator.FillColor, theme.PrimaryColor()) + assert.True(t, visible[6].statusIndicator.Visible()) } func TestList_Unselect(t *testing.T) { list := createList(1000) list.Select(10) - children := test.WidgetRenderer(list).(*listRenderer).children + children := list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children assert.Equal(t, children[10].(*listItem).statusIndicator.FillColor, theme.PrimaryColor()) assert.True(t, children[10].(*listItem).statusIndicator.Visible()) list.Unselect(10) - children = test.WidgetRenderer(list).(*listRenderer).children + children = list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children assert.False(t, children[10].(*listItem).statusIndicator.Visible()) assert.Nil(t, list.selected) } @@ -195,12 +171,12 @@ func TestList_DataChange(t *testing.T) { defer test.NewApp() list, w := setupList(t) - children := test.WidgetRenderer(list).(*listRenderer).children + children := list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children assert.Equal(t, children[0].(*listItem).child.(*fyne.Container).Objects[1].(*Label).Text, "Test Item 0") changeData(list) list.Refresh() - children = test.WidgetRenderer(list).(*listRenderer).children + children = list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children assert.Equal(t, children[0].(*listItem).child.(*fyne.Container).Objects[1].(*Label).Text, "a") test.AssertRendersToMarkup(t, "list/new_data.xml", w.Canvas()) } @@ -239,13 +215,13 @@ func TestList_SmallList(t *testing.T) { w := test.NewWindow(list) w.Resize(fyne.NewSize(200, 400)) - visibleCount := len(test.WidgetRenderer(list).(*listRenderer).children) + visibleCount := len(list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children) assert.Equal(t, visibleCount, 1) data = append(data, "Test Item 1") list.Refresh() - visibleCount = len(test.WidgetRenderer(list).(*listRenderer).children) + visibleCount = len(list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children) assert.Equal(t, visibleCount, 2) test.AssertRendersToMarkup(t, "list/small.xml", w.Canvas()) @@ -256,18 +232,12 @@ func TestList_ClearList(t *testing.T) { list, w := setupList(t) assert.Equal(t, 1000, list.Length()) - firstItemIndex := test.WidgetRenderer(list).(*listRenderer).firstItemIndex - lastItemIndex := test.WidgetRenderer(list).(*listRenderer).lastItemIndex - visibleCount := len(test.WidgetRenderer(list).(*listRenderer).children) - - assert.Equal(t, visibleCount, lastItemIndex-firstItemIndex+1) - list.Length = func() int { return 0 } list.Refresh() - visibleCount = len(test.WidgetRenderer(list).(*listRenderer).children) + visibleCount := len(list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children) assert.Equal(t, visibleCount, 0) @@ -297,17 +267,55 @@ func TestList_RemoveItem(t *testing.T) { w := test.NewWindow(list) w.Resize(fyne.NewSize(200, 400)) - visibleCount := len(test.WidgetRenderer(list).(*listRenderer).children) + visibleCount := len(list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children) assert.Equal(t, visibleCount, 3) data = data[:len(data)-1] list.Refresh() - visibleCount = len(test.WidgetRenderer(list).(*listRenderer).children) + visibleCount = len(list.scroller.Content.(*fyne.Container).Layout.(*listLayout).children) assert.Equal(t, visibleCount, 2) test.AssertRendersToMarkup(t, "list/item_removed.xml", w.Canvas()) } +func TestList_ScrollThenShrink(t *testing.T) { + test.NewApp() + defer test.NewApp() + + data := make([]string, 0, 20) + for i := 0; i < 20; i++ { + data = append(data, fmt.Sprintf("Data %d", i)) + } + + list := NewList( + func() int { + return len(data) + }, + func() fyne.CanvasObject { + return NewLabel("TEMPLATE") + }, + func(id ListItemID, item fyne.CanvasObject) { + item.(*Label).SetText(data[id]) + }, + ) + w := test.NewWindow(list) + w.Resize(fyne.NewSize(300, 300)) + + visibles := list.scroller.Content.(*fyne.Container).Layout.(*listLayout).visible + assert.Equal(t, len(visibles), 8) + + list.scroller.ScrollToBottom() + visibles = list.scroller.Content.(*fyne.Container).Layout.(*listLayout).visible + assert.Equal(t, "Data 19", visibles[19].child.(*Label).Text) + + data = data[:1] + assert.NotPanics(t, func() { list.Refresh() }) + + visibles = list.scroller.Content.(*fyne.Container).Layout.(*listLayout).visible + assert.Equal(t, len(visibles), 1) + assert.Equal(t, "Data 0", visibles[0].child.(*Label).Text) +} + func TestList_NoFunctionsSet(t *testing.T) { list := &List{} w := test.NewWindow(list) @@ -326,7 +334,8 @@ func createList(items int) *List { return len(data) }, func() fyne.CanvasObject { - return fyne.NewContainerWithLayout(layout.NewHBoxLayout(), NewIcon(theme.DocumentIcon()), NewLabel("Template Object")) + icon := NewIcon(theme.DocumentIcon()) + return fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, nil, icon, nil), icon, NewLabel("Template Object")) }, func(id ListItemID, item fyne.CanvasObject) { item.(*fyne.Container).Objects[1].(*Label).SetText(data[id]) diff --git a/widget/popup.go b/widget/popup.go index 848f33e321..652af47dca 100644 --- a/widget/popup.go +++ b/widget/popup.go @@ -48,7 +48,6 @@ func (p *PopUp) Move(pos fyne.Position) { // Implements: fyne.Widget func (p *PopUp) Resize(size fyne.Size) { p.innerSize = size - p.BaseWidget.Resize(p.Canvas.Size()) // The canvas size might not have changed and therefore the Resize won't trigger a layout. // Until we have a widget.Relayout() or similar, the renderer's refresh will do the re-layout. p.Refresh() @@ -57,12 +56,10 @@ func (p *PopUp) Resize(size fyne.Size) { // Show this pop-up as overlay if not already shown. func (p *PopUp) Show() { if !p.overlayShown { - if p.Size().IsZero() { - p.Resize(p.MinSize()) - } p.Canvas.Overlays().Add(p) p.overlayShown = true } + p.Refresh() p.BaseWidget.Show() } @@ -172,26 +169,27 @@ type popUpRenderer struct { } func (r *popUpRenderer) Layout(_ fyne.Size) { - r.popUp.Content.Resize(r.popUp.innerSize.Subtract(r.padding())) + innerSize := r.popUp.innerSize.Max(r.popUp.MinSize()) + r.popUp.Content.Resize(innerSize.Subtract(r.padding())) innerPos := r.popUp.innerPos - if innerPos.X+r.popUp.innerSize.Width > r.popUp.Canvas.Size().Width { - innerPos.X = r.popUp.Canvas.Size().Width - r.popUp.innerSize.Width + if innerPos.X+innerSize.Width > r.popUp.Canvas.Size().Width { + innerPos.X = r.popUp.Canvas.Size().Width - innerSize.Width if innerPos.X < 0 { innerPos.X = 0 // TODO here we may need a scroller as it's wider than our canvas } } - if innerPos.Y+r.popUp.innerSize.Height > r.popUp.Canvas.Size().Height { - innerPos.Y = r.popUp.Canvas.Size().Height - r.popUp.innerSize.Height + if innerPos.Y+innerSize.Height > r.popUp.Canvas.Size().Height { + innerPos.Y = r.popUp.Canvas.Size().Height - innerSize.Height if innerPos.Y < 0 { innerPos.Y = 0 // TODO here we may need a scroller as it's longer than our canvas } } r.popUp.Content.Move(innerPos.Add(r.offset())) - r.background.Resize(r.popUp.innerSize) + r.background.Resize(innerSize) r.background.Move(innerPos) - r.LayoutShadow(r.popUp.innerSize, innerPos) + r.LayoutShadow(innerSize, innerPos) } func (r *popUpRenderer) MinSize() fyne.Size { @@ -200,9 +198,16 @@ func (r *popUpRenderer) MinSize() fyne.Size { func (r *popUpRenderer) Refresh() { r.background.FillColor = theme.BackgroundColor() - if r.background.Size() != r.popUp.innerSize || r.background.Position() != r.popUp.innerPos { + expectedContentSize := r.popUp.innerSize.Max(r.popUp.MinSize()).Subtract(r.padding()) + shouldRelayout := !r.popUp.Content.Size().Subtract(expectedContentSize).IsZero() + + if r.background.Size() != r.popUp.innerSize || r.background.Position() != r.popUp.innerPos || shouldRelayout { r.Layout(r.popUp.Size()) } + if !r.popUp.Canvas.Size().Subtract(r.popUp.BaseWidget.Size()).IsZero() { + r.popUp.BaseWidget.Resize(r.popUp.Canvas.Size()) + } + r.popUp.Content.Refresh() r.background.Refresh() } @@ -238,8 +243,15 @@ func (r *modalPopUpRenderer) MinSize() fyne.Size { func (r *modalPopUpRenderer) Refresh() { r.underlay.FillColor = theme.ShadowColor() r.background.FillColor = theme.BackgroundColor() - if r.background.Size() != r.popUp.innerSize { + expectedContentSize := r.popUp.innerSize.Max(r.popUp.MinSize()).Subtract(r.padding()) + shouldRelayout := !r.popUp.Content.Size().Subtract(expectedContentSize).IsZero() + + if r.background.Size() != r.popUp.innerSize || shouldRelayout { r.Layout(r.popUp.Size()) } + if !r.popUp.Canvas.Size().Subtract(r.popUp.BaseWidget.Size()).IsZero() { + r.popUp.BaseWidget.Resize(r.popUp.Canvas.Size()) + } + r.popUp.Content.Refresh() r.background.Refresh() } diff --git a/widget/popup_test.go b/widget/popup_test.go index f693beb572..701ba37ae4 100644 --- a/widget/popup_test.go +++ b/widget/popup_test.go @@ -144,7 +144,7 @@ func TestPopUp_Move(t *testing.T) { label := NewLabel("Hi") win := test.NewWindow(NewLabel("OK")) defer win.Close() - win.Resize(fyne.NewSize(50, 50)) + win.Resize(fyne.NewSize(70, 70)) pop := newPopUp(label, win.Canvas()) defer test.Canvas().Overlays().Remove(pop) @@ -208,7 +208,7 @@ func TestPopUp_Resize(t *testing.T) { pop.Show() defer test.Canvas().Overlays().Remove(pop) - size := fyne.NewSize(50, 40) + size := fyne.NewSize(60, 50) pop.Resize(size) assert.Equal(t, size.Subtract(fyne.NewSize(theme.Padding()*2, theme.Padding()*2)), pop.Content.Size()) @@ -289,7 +289,7 @@ func TestPopUp_Layout(t *testing.T) { pop.ShowAtPosition(pos) defer test.Canvas().Overlays().Remove(pop) - size := fyne.NewSize(50, 40) + size := fyne.NewSize(60, 50) pop.Resize(size) r := cache.Renderer(pop) require.GreaterOrEqual(t, len(r.Objects()), 3) @@ -306,6 +306,77 @@ func TestPopUp_Layout(t *testing.T) { assert.Equal(t, r.Objects()[2], content) } +func TestPopUp_ApplyThemeOnShow(t *testing.T) { + test.NewApp() + defer test.NewApp() + w := test.NewWindow(canvas.NewRectangle(color.Transparent)) + w.Resize(fyne.NewSize(200, 300)) + + pop := NewPopUp(NewLabel("Label"), w.Canvas()) + + test.ApplyTheme(t, test.Theme()) + pop.Show() + test.AssertImageMatches(t, "popup/normal-onshow-theme-default.png", w.Canvas().Capture()) + pop.Hide() + + test.ApplyTheme(t, test.NewTheme()) + pop.Show() + test.AssertImageMatches(t, "popup/normal-onshow-theme-changed.png", w.Canvas().Capture()) + pop.Hide() + + test.ApplyTheme(t, test.Theme()) + pop.Show() + test.AssertImageMatches(t, "popup/normal-onshow-theme-default.png", w.Canvas().Capture()) + pop.Hide() +} + +func TestPopUp_ResizeOnShow(t *testing.T) { + test.NewApp() + defer test.NewApp() + w := test.NewWindow(canvas.NewRectangle(color.Transparent)) + size := fyne.NewSize(200, 300) + w.Resize(size) + + pop := NewPopUp(NewLabel("Label"), w.Canvas()) + + pop.Show() + assert.Equal(t, size, pop.Size()) + pop.Hide() + + size = fyne.NewSize(500, 500) + w.Resize(size) + pop.Show() + assert.Equal(t, size, pop.Size()) + pop.Hide() +} + +func TestPopUp_ResizeBeforeShow_CanvasSizeZero(t *testing.T) { + test.NewApp() + defer test.NewApp() + + // Simulate canvas size {0,0} + rect := canvas.NewRectangle(color.Black) + rect.SetMinSize(fyne.NewSize(0, 0)) + w := test.NewWindow(rect) + w.SetPadded(false) + w.Resize(fyne.NewSize(0, 0)) + assert.Zero(t, w.Canvas().Size()) + + pop := NewPopUp(NewLabel("Label"), w.Canvas()) + popBgSize := fyne.NewSize(200, 200) + pop.Resize(popBgSize) + pop.Show() + + winSize := fyne.NewSize(300, 300) + w.Resize(winSize) + + // get content padding dynamically + popContentPadding := pop.MinSize().Subtract(pop.Content.MinSize()) + + assert.Equal(t, popBgSize.Subtract(popContentPadding), pop.Content.Size()) + assert.Equal(t, winSize, pop.Size()) +} + func TestModalPopUp_Tapped(t *testing.T) { label := NewLabel("Hi") pop := NewModalPopUp(label, test.Canvas()) @@ -363,3 +434,74 @@ func TestModalPopUp_Resize_Constrained(t *testing.T) { assert.Equal(t, float32(80), pop.Size().Width) assert.Equal(t, float32(80), pop.Size().Height) } + +func TestModalPopUp_ApplyThemeOnShow(t *testing.T) { + test.NewApp() + defer test.NewApp() + w := test.NewWindow(canvas.NewRectangle(color.Transparent)) + w.Resize(fyne.NewSize(200, 300)) + + pop := NewModalPopUp(NewLabel("Label"), w.Canvas()) + + test.ApplyTheme(t, test.Theme()) + pop.Show() + test.AssertImageMatches(t, "popup/modal-onshow-theme-default.png", w.Canvas().Capture()) + pop.Hide() + + test.ApplyTheme(t, test.NewTheme()) + pop.Show() + test.AssertImageMatches(t, "popup/modal-onshow-theme-changed.png", w.Canvas().Capture()) + pop.Hide() + + test.ApplyTheme(t, test.Theme()) + pop.Show() + test.AssertImageMatches(t, "popup/modal-onshow-theme-default.png", w.Canvas().Capture()) + pop.Hide() +} + +func TestModalPopUp_ResizeOnShow(t *testing.T) { + test.NewApp() + defer test.NewApp() + w := test.NewWindow(canvas.NewRectangle(color.Transparent)) + size := fyne.NewSize(200, 300) + w.Resize(size) + + pop := NewModalPopUp(NewLabel("Label"), w.Canvas()) + + pop.Show() + assert.Equal(t, size, pop.Size()) + pop.Hide() + + size = fyne.NewSize(500, 500) + w.Resize(size) + pop.Show() + assert.Equal(t, size, pop.Size()) + pop.Hide() +} + +func TestModelPopUp_ResizeBeforeShow_CanvasSizeZero(t *testing.T) { + test.NewApp() + defer test.NewApp() + + // Simulate canvas size {0,0} + rect := canvas.NewRectangle(color.Black) + rect.SetMinSize(fyne.NewSize(0, 0)) + w := test.NewWindow(rect) + w.SetPadded(false) + w.Resize(fyne.NewSize(0, 0)) + assert.Zero(t, w.Canvas().Size()) + + pop := NewModalPopUp(NewLabel("Label"), w.Canvas()) + popBgSize := fyne.NewSize(200, 200) + pop.Resize(popBgSize) + pop.Show() + + winSize := fyne.NewSize(300, 300) + w.Resize(winSize) + + // get content padding dynamically + popContentPadding := pop.MinSize().Subtract(pop.Content.MinSize()) + + assert.Equal(t, popBgSize.Subtract(popContentPadding), pop.Content.Size()) + assert.Equal(t, winSize, pop.Size()) +} diff --git a/widget/progressbar.go b/widget/progressbar.go index 6de273d415..80033aabe8 100644 --- a/widget/progressbar.go +++ b/widget/progressbar.go @@ -136,7 +136,7 @@ func (p *ProgressBar) CreateRenderer() fyne.WidgetRenderer { p.Max = 1.0 } - background := canvas.NewRectangle(theme.ShadowColor()) + background := canvas.NewRectangle(progressBackgroundColor()) bar := canvas.NewRectangle(theme.PrimaryColor()) label := canvas.NewText("0%", theme.ForegroundColor()) label.Alignment = fyne.TextAlignCenter diff --git a/widget/select_entry.go b/widget/select_entry.go index c9ad3891e0..92c878f93f 100644 --- a/widget/select_entry.go +++ b/widget/select_entry.go @@ -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 } diff --git a/widget/select_entry_test.go b/widget/select_entry_test.go index 70eb775e9e..9d84f44adf 100644 --- a/widget/select_entry_test.go +++ b/widget/select_entry_test.go @@ -123,29 +123,29 @@ func TestSelectEntry_MinSize(t *testing.T) { want fyne.Size }{ "empty": { - want: fyne.NewSize(emptyTextWidth()+dropDownIconWidth()+4*theme.Padding(), labelHeight), + want: fyne.NewSize(emptyTextWidth()+dropDownIconWidth()+2*theme.Padding(), labelHeight), }, "empty + small options": { options: smallOptions, - want: fyne.NewSize(emptyTextWidth()+dropDownIconWidth()+4*theme.Padding(), labelHeight), + want: fyne.NewSize(emptyTextWidth()+dropDownIconWidth()+2*theme.Padding(), labelHeight), }, "empty + large options": { options: largeOptions, - want: fyne.NewSize(largeOptionsMinWidth+2*theme.Padding(), labelHeight), + want: fyne.NewSize(largeOptionsMinWidth, labelHeight), }, "value": { value: "foo", // in a scroller - want: fyne.NewSize(emptyTextWidth()+dropDownIconWidth()+4*theme.Padding(), labelHeight), + want: fyne.NewSize(emptyTextWidth()+dropDownIconWidth()+2*theme.Padding(), labelHeight), }, "large value + small options": { value: "large", // in a scroller options: smallOptions, - want: fyne.NewSize(emptyTextWidth()+dropDownIconWidth()+4*theme.Padding(), labelHeight), + want: fyne.NewSize(emptyTextWidth()+dropDownIconWidth()+2*theme.Padding(), labelHeight), }, "small value + large options": { value: "small", // in a scroller options: largeOptions, - want: fyne.NewSize(largeOptionsMinWidth+2*theme.Padding(), labelHeight), + want: fyne.NewSize(largeOptionsMinWidth, labelHeight), }, } for name, tt := range tests { diff --git a/widget/table.go b/widget/table.go index f49c6aaee4..bca4eb6e12 100644 --- a/widget/table.go +++ b/widget/table.go @@ -330,7 +330,7 @@ func (t *tableRenderer) moveIndicators() { divs := 0 i := minCol - for x := offX + visibleColWidths[i]; i < minCol+colDivs; x += visibleColWidths[i] + separatorThickness { + for x := offX + visibleColWidths[i]; i < minCol+colDivs && divs < len(t.dividers); x += visibleColWidths[i] + separatorThickness { i++ t.dividers[divs].Move(fyne.NewPos(theme.Padding()+x-t.scroll.Offset.X, theme.Padding())) @@ -341,7 +341,7 @@ func (t *tableRenderer) moveIndicators() { i = 0 count := int(t.scroll.Offset.Y) % int(t.cellSize.Height+separatorThickness) - for y := theme.Padding() + t.scroll.Offset.Y - float32(count) - separatorThickness; y < t.scroll.Offset.Y+t.t.size.Height && i < rows-1; y += t.cellSize.Height + separatorThickness { + for y := theme.Padding() + t.scroll.Offset.Y - float32(count) - separatorThickness; y < t.scroll.Offset.Y+t.t.size.Height && i < rows-1 && divs < len(t.dividers); y += t.cellSize.Height + separatorThickness { if y < theme.Padding()+t.scroll.Offset.Y { continue } @@ -508,7 +508,20 @@ func (r *tableCellsRenderer) MinSize() fyne.Size { } else { fyne.LogError("Missing Length callback required for Table", nil) } - return fyne.NewSize(r.cells.cellSize.Width*float32(cols)+float32(cols-1), r.cells.cellSize.Height*float32(rows)+float32(rows-1)) + + width := float32(0) + cellWidth := r.cells.cellSize.Width + for col := 0; col < cols; col++ { + colWidth, ok := r.cells.t.columnWidths[col] + if ok { + width += colWidth + } else { + width += cellWidth + } + } + + separatorSize := theme.SeparatorThicknessSize() + return fyne.NewSize(width+float32(cols-1)*separatorSize, r.cells.cellSize.Height*float32(rows)+float32(rows-1)*separatorSize) } func (r *tableCellsRenderer) Refresh() { @@ -556,12 +569,12 @@ func (r *tableCellsRenderer) Refresh() { if c == nil { continue } - - c.Move(fyne.NewPos(theme.Padding()+cellOffset, - theme.Padding()+float32(row)*(r.cells.cellSize.Height+separatorThickness))) - c.Resize(fyne.NewSize(colWidth-theme.Padding()*2, r.cells.cellSize.Height-theme.Padding()*2)) } + c.Move(fyne.NewPos(theme.Padding()+cellOffset, + theme.Padding()+float32(row)*(r.cells.cellSize.Height+separatorThickness))) + c.Resize(fyne.NewSize(colWidth-theme.Padding()*2, r.cells.cellSize.Height-theme.Padding()*2)) + if updateCell != nil { updateCell(TableCellID{row, col}, c) } diff --git a/widget/table_test.go b/widget/table_test.go index d0b3113e91..b37ed53c6c 100644 --- a/widget/table_test.go +++ b/widget/table_test.go @@ -267,7 +267,7 @@ func TestTable_SetColumnWidth(t *testing.T) { obj.(*Label).Text = "placeholder" } }) - table.SetColumnWidth(0, 16) + table.SetColumnWidth(0, 32) table.Resize(fyne.NewSize(120, 120)) table.Select(TableCellID{1, 0}) @@ -275,7 +275,13 @@ func TestTable_SetColumnWidth(t *testing.T) { cellRenderer := test.WidgetRenderer(renderer.scroll.Content.(*tableCells)) cellRenderer.Refresh() assert.Equal(t, 8, len(cellRenderer.Objects())) + assert.Equal(t, float32(32), cellRenderer.(*tableCellsRenderer).Objects()[0].Size().Width) + cell1Offset := theme.SeparatorThicknessSize() + theme.Padding()*3 + assert.Equal(t, float32(32)+cell1Offset, cellRenderer.(*tableCellsRenderer).Objects()[1].Position().X) + + table.SetColumnWidth(0, 16) assert.Equal(t, float32(16), cellRenderer.(*tableCellsRenderer).Objects()[0].Size().Width) + assert.Equal(t, float32(16)+cell1Offset, cellRenderer.(*tableCellsRenderer).Objects()[1].Position().X) w := test.NewWindow(table) defer w.Close() @@ -297,3 +303,32 @@ func TestTable_ShowVisible(t *testing.T) { cellRenderer.Refresh() assert.Equal(t, 8, len(cellRenderer.Objects())) } + +func TestTable_SeparatorThicknessZero_NotPanics(t *testing.T) { + test.NewApp() + defer test.NewApp() + + test.ApplyTheme(t, &separatorThicknessZeroTheme{test.Theme()}) + + table := NewTable( + func() (int, int) { return 500, 150 }, + func() fyne.CanvasObject { + return NewLabel("placeholder") + }, + func(TableCellID, fyne.CanvasObject) {}) + + assert.NotPanics(t, func() { + table.Resize(fyne.NewSize(400, 644)) + }) +} + +type separatorThicknessZeroTheme struct { + fyne.Theme +} + +func (t *separatorThicknessZeroTheme) Size(n fyne.ThemeSizeName) float32 { + if n == theme.SizeNameSeparatorThickness { + return 0 + } + return t.Theme.Size(n) +} diff --git a/widget/testdata/accordion/layout_multiple_open_multiple_items.xml b/widget/testdata/accordion/layout_multiple_open_multiple_items.xml index 3aefbe7258..9bcf74e0b5 100644 --- a/widget/testdata/accordion/layout_multiple_open_multiple_items.xml +++ b/widget/testdata/accordion/layout_multiple_open_multiple_items.xml @@ -1,21 +1,21 @@ - - - + + + A - - + + B - - + + diff --git a/widget/testdata/accordion/layout_multiple_open_multiple_items_opened.xml b/widget/testdata/accordion/layout_multiple_open_multiple_items_opened.xml index 328526b601..d91715547f 100644 --- a/widget/testdata/accordion/layout_multiple_open_multiple_items_opened.xml +++ b/widget/testdata/accordion/layout_multiple_open_multiple_items_opened.xml @@ -1,27 +1,27 @@ - - - + + + A - - + + B - - 11111 + + 11111 - - 2222222222 + + 2222222222 - - + + diff --git a/widget/testdata/accordion/layout_multiple_open_one_item.xml b/widget/testdata/accordion/layout_multiple_open_one_item.xml index dd076b2e38..0d034fa3fc 100644 --- a/widget/testdata/accordion/layout_multiple_open_one_item.xml +++ b/widget/testdata/accordion/layout_multiple_open_one_item.xml @@ -1,9 +1,9 @@ - - - + + + A diff --git a/widget/testdata/accordion/layout_multiple_open_one_item_opened.xml b/widget/testdata/accordion/layout_multiple_open_one_item_opened.xml index c904675873..831d9f017c 100644 --- a/widget/testdata/accordion/layout_multiple_open_one_item_opened.xml +++ b/widget/testdata/accordion/layout_multiple_open_one_item_opened.xml @@ -1,15 +1,15 @@ - - - + + + A - - 11111 + + 11111 diff --git a/widget/testdata/accordion/layout_single_open_multiple_items.xml b/widget/testdata/accordion/layout_single_open_multiple_items.xml index 3aefbe7258..9bcf74e0b5 100644 --- a/widget/testdata/accordion/layout_single_open_multiple_items.xml +++ b/widget/testdata/accordion/layout_single_open_multiple_items.xml @@ -1,21 +1,21 @@ - - - + + + A - - + + B - - + + diff --git a/widget/testdata/accordion/layout_single_open_multiple_items_opened.xml b/widget/testdata/accordion/layout_single_open_multiple_items_opened.xml index 99c777a63e..d072924d43 100644 --- a/widget/testdata/accordion/layout_single_open_multiple_items_opened.xml +++ b/widget/testdata/accordion/layout_single_open_multiple_items_opened.xml @@ -1,24 +1,24 @@ - - - + + + A - - + + B - - 2222222222 + + 2222222222 - - + + diff --git a/widget/testdata/accordion/layout_single_open_one_item.xml b/widget/testdata/accordion/layout_single_open_one_item.xml index dd076b2e38..0d034fa3fc 100644 --- a/widget/testdata/accordion/layout_single_open_one_item.xml +++ b/widget/testdata/accordion/layout_single_open_one_item.xml @@ -1,9 +1,9 @@ - - - + + + A diff --git a/widget/testdata/accordion/layout_single_open_one_item_opened.xml b/widget/testdata/accordion/layout_single_open_one_item_opened.xml index c904675873..831d9f017c 100644 --- a/widget/testdata/accordion/layout_single_open_one_item_opened.xml +++ b/widget/testdata/accordion/layout_single_open_one_item_opened.xml @@ -1,15 +1,15 @@ - - - + + + A - - 11111 + + 11111 diff --git a/widget/testdata/accordion/theme_changed.png b/widget/testdata/accordion/theme_changed.png index 95cd33173f..148c2627fa 100644 Binary files a/widget/testdata/accordion/theme_changed.png and b/widget/testdata/accordion/theme_changed.png differ diff --git a/widget/testdata/accordion/theme_initial.png b/widget/testdata/accordion/theme_initial.png index 681b1a1e72..f85908bae3 100644 Binary files a/widget/testdata/accordion/theme_initial.png and b/widget/testdata/accordion/theme_initial.png differ diff --git a/widget/testdata/button/high_importance.png b/widget/testdata/button/high_importance.png new file mode 100644 index 0000000000..1339bd9cac Binary files /dev/null and b/widget/testdata/button/high_importance.png differ diff --git a/widget/testdata/button/high_importance_hovered.png b/widget/testdata/button/high_importance_hovered.png new file mode 100644 index 0000000000..ef5bf89f80 Binary files /dev/null and b/widget/testdata/button/high_importance_hovered.png differ diff --git a/widget/testdata/button/hovered.png b/widget/testdata/button/hovered.png new file mode 100644 index 0000000000..8a0effa81e Binary files /dev/null and b/widget/testdata/button/hovered.png differ diff --git a/widget/testdata/entry/disableable_disabled_custom_value.xml b/widget/testdata/entry/disableable_disabled_custom_value.xml index 2babf255c3..d243203f5a 100644 --- a/widget/testdata/entry/disableable_disabled_custom_value.xml +++ b/widget/testdata/entry/disableable_disabled_custom_value.xml @@ -3,11 +3,9 @@ - - - - Hello - + + + Hello diff --git a/widget/testdata/entry/disableable_disabled_empty.xml b/widget/testdata/entry/disableable_disabled_empty.xml index f5f7a0adea..47b12478fa 100644 --- a/widget/testdata/entry/disableable_disabled_empty.xml +++ b/widget/testdata/entry/disableable_disabled_empty.xml @@ -3,14 +3,12 @@ - - - - - - - - + + + + + + diff --git a/widget/testdata/entry/disableable_disabled_placeholder.xml b/widget/testdata/entry/disableable_disabled_placeholder.xml index 349e910d40..521bda760b 100644 --- a/widget/testdata/entry/disableable_disabled_placeholder.xml +++ b/widget/testdata/entry/disableable_disabled_placeholder.xml @@ -3,14 +3,12 @@ - - - - Type! - - - - + + + Type! + + + diff --git a/widget/testdata/entry/disableable_enabled_custom_value.xml b/widget/testdata/entry/disableable_enabled_custom_value.xml index 4246b0bb02..608aedda12 100644 --- a/widget/testdata/entry/disableable_enabled_custom_value.xml +++ b/widget/testdata/entry/disableable_enabled_custom_value.xml @@ -3,11 +3,9 @@ - - - - Hello - + + + Hello diff --git a/widget/testdata/entry/disableable_enabled_empty.xml b/widget/testdata/entry/disableable_enabled_empty.xml index e11bd1d6e6..d0860b5e97 100644 --- a/widget/testdata/entry/disableable_enabled_empty.xml +++ b/widget/testdata/entry/disableable_enabled_empty.xml @@ -3,14 +3,12 @@ - - - - - - - - + + + + + + diff --git a/widget/testdata/entry/disableable_enabled_placeholder.xml b/widget/testdata/entry/disableable_enabled_placeholder.xml index 87953a6c18..e0e0321688 100644 --- a/widget/testdata/entry/disableable_enabled_placeholder.xml +++ b/widget/testdata/entry/disableable_enabled_placeholder.xml @@ -3,14 +3,12 @@ - - - - Type! - - - - + + + Type! + + + diff --git a/widget/testdata/entry/focus_gained.xml b/widget/testdata/entry/focus_gained.xml index fc9aa295da..42480a808a 100644 --- a/widget/testdata/entry/focus_gained.xml +++ b/widget/testdata/entry/focus_gained.xml @@ -3,16 +3,14 @@ - - - - - - - - - + + + + + + + diff --git a/widget/testdata/entry/focus_lost.xml b/widget/testdata/entry/focus_lost.xml index e11bd1d6e6..d0860b5e97 100644 --- a/widget/testdata/entry/focus_lost.xml +++ b/widget/testdata/entry/focus_lost.xml @@ -3,14 +3,12 @@ - - - - - - - - + + + + + + diff --git a/widget/testdata/entry/focus_with_popup_dismissed.xml b/widget/testdata/entry/focus_with_popup_dismissed.xml index fc9aa295da..42480a808a 100644 --- a/widget/testdata/entry/focus_with_popup_dismissed.xml +++ b/widget/testdata/entry/focus_with_popup_dismissed.xml @@ -3,16 +3,14 @@ - - - - - - - - - + + + + + + + diff --git a/widget/testdata/entry/focus_with_popup_entry_selected.xml b/widget/testdata/entry/focus_with_popup_entry_selected.xml index fc9aa295da..42480a808a 100644 --- a/widget/testdata/entry/focus_with_popup_entry_selected.xml +++ b/widget/testdata/entry/focus_with_popup_entry_selected.xml @@ -3,16 +3,14 @@ - - - - - - - - - + + + + + + + diff --git a/widget/testdata/entry/focus_with_popup_initial.xml b/widget/testdata/entry/focus_with_popup_initial.xml index e506f57665..bca0591afc 100644 --- a/widget/testdata/entry/focus_with_popup_initial.xml +++ b/widget/testdata/entry/focus_with_popup_initial.xml @@ -3,16 +3,14 @@ - - - - - - - - - + + + + + + + diff --git a/widget/testdata/entry/focused_disabled.xml b/widget/testdata/entry/focused_disabled.xml index f5f7a0adea..47b12478fa 100644 --- a/widget/testdata/entry/focused_disabled.xml +++ b/widget/testdata/entry/focused_disabled.xml @@ -3,14 +3,12 @@ - - - - - - - - + + + + + + diff --git a/widget/testdata/entry/focused_enabled.xml b/widget/testdata/entry/focused_enabled.xml index fc9aa295da..42480a808a 100644 --- a/widget/testdata/entry/focused_enabled.xml +++ b/widget/testdata/entry/focused_enabled.xml @@ -3,16 +3,14 @@ - - - - - - - - - + + + + + + + diff --git a/widget/testdata/entry/initial.xml b/widget/testdata/entry/initial.xml index e11bd1d6e6..d0860b5e97 100644 --- a/widget/testdata/entry/initial.xml +++ b/widget/testdata/entry/initial.xml @@ -3,14 +3,12 @@ - - - - - - - - + + + + + + diff --git a/widget/testdata/entry/initial_multiline.xml b/widget/testdata/entry/initial_multiline.xml index c2e820f06e..3960d9c6af 100644 --- a/widget/testdata/entry/initial_multiline.xml +++ b/widget/testdata/entry/initial_multiline.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/entry/on_key_down_newline_initial.xml b/widget/testdata/entry/on_key_down_newline_initial.xml index 6820a92484..15a12ddf89 100644 --- a/widget/testdata/entry/on_key_down_newline_initial.xml +++ b/widget/testdata/entry/on_key_down_newline_initial.xml @@ -6,7 +6,7 @@ - Hi + Hi diff --git a/widget/testdata/entry/on_key_down_newline_typed.xml b/widget/testdata/entry/on_key_down_newline_typed.xml index 8400c4fa89..a72ac4d265 100644 --- a/widget/testdata/entry/on_key_down_newline_typed.xml +++ b/widget/testdata/entry/on_key_down_newline_typed.xml @@ -6,8 +6,8 @@ - H - oi + H + oi diff --git a/widget/testdata/entry/placeholder_with_text.xml b/widget/testdata/entry/placeholder_with_text.xml index 47208ccdae..5c1f040bfd 100644 --- a/widget/testdata/entry/placeholder_with_text.xml +++ b/widget/testdata/entry/placeholder_with_text.xml @@ -3,11 +3,9 @@ - - - - Text - + + + Text diff --git a/widget/testdata/entry/placeholder_without_text.xml b/widget/testdata/entry/placeholder_without_text.xml index fa72a0bbdd..8a28f52b1a 100644 --- a/widget/testdata/entry/placeholder_without_text.xml +++ b/widget/testdata/entry/placeholder_without_text.xml @@ -3,14 +3,12 @@ - - - - Placehold - - - - + + + Placehold + + + diff --git a/widget/testdata/entry/select_add_selection.xml b/widget/testdata/entry/select_add_selection.xml index 0dc22ab066..b16c4b9585 100644 --- a/widget/testdata/entry/select_add_selection.xml +++ b/widget/testdata/entry/select_add_selection.xml @@ -3,14 +3,12 @@ - - - - - Testing - - + + + + Testing + diff --git a/widget/testdata/entry/select_all_initial.xml b/widget/testdata/entry/select_all_initial.xml index 4ccff7d34e..f52c960e78 100644 --- a/widget/testdata/entry/select_all_initial.xml +++ b/widget/testdata/entry/select_all_initial.xml @@ -6,9 +6,9 @@ - First Row - Second Row - Third Row + First Row + Second Row + Third Row diff --git a/widget/testdata/entry/select_all_selected.xml b/widget/testdata/entry/select_all_selected.xml index 72b4be839c..2f16d01ff2 100644 --- a/widget/testdata/entry/select_all_selected.xml +++ b/widget/testdata/entry/select_all_selected.xml @@ -9,9 +9,9 @@ - First Row - Second Row - Third Row + First Row + Second Row + Third Row diff --git a/widget/testdata/entry/select_initial.xml b/widget/testdata/entry/select_initial.xml index 88d06bab3a..fa18fe2d61 100644 --- a/widget/testdata/entry/select_initial.xml +++ b/widget/testdata/entry/select_initial.xml @@ -3,13 +3,11 @@ - - - - Testing - - + + + Testing + diff --git a/widget/testdata/entry/select_move_wo_shift.xml b/widget/testdata/entry/select_move_wo_shift.xml index 7e03ae3262..635991555d 100644 --- a/widget/testdata/entry/select_move_wo_shift.xml +++ b/widget/testdata/entry/select_move_wo_shift.xml @@ -3,13 +3,11 @@ - - - - Testing - - + + + Testing + diff --git a/widget/testdata/entry/select_multi_line_initial.xml b/widget/testdata/entry/select_multi_line_initial.xml index 14454e6675..e7d296c502 100644 --- a/widget/testdata/entry/select_multi_line_initial.xml +++ b/widget/testdata/entry/select_multi_line_initial.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/select_multi_line_pagedown.xml b/widget/testdata/entry/select_multi_line_pagedown.xml index 7c9e2afc8c..212417a729 100644 --- a/widget/testdata/entry/select_multi_line_pagedown.xml +++ b/widget/testdata/entry/select_multi_line_pagedown.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/select_multi_line_shift_pagedown.xml b/widget/testdata/entry/select_multi_line_shift_pagedown.xml index 8930c8ddd3..dc5b0e7a3f 100644 --- a/widget/testdata/entry/select_multi_line_shift_pagedown.xml +++ b/widget/testdata/entry/select_multi_line_shift_pagedown.xml @@ -9,9 +9,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/select_multi_line_shift_pageup.xml b/widget/testdata/entry/select_multi_line_shift_pageup.xml index ae6606ce5f..77f269b2eb 100644 --- a/widget/testdata/entry/select_multi_line_shift_pageup.xml +++ b/widget/testdata/entry/select_multi_line_shift_pageup.xml @@ -7,9 +7,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/select_select_left.xml b/widget/testdata/entry/select_select_left.xml index 5c80331cec..fb70d6c01e 100644 --- a/widget/testdata/entry/select_select_left.xml +++ b/widget/testdata/entry/select_select_left.xml @@ -3,14 +3,12 @@ - - - - - Testing - - + + + + Testing + diff --git a/widget/testdata/entry/select_selected.xml b/widget/testdata/entry/select_selected.xml index 93bf488983..bc8350bfbf 100644 --- a/widget/testdata/entry/select_selected.xml +++ b/widget/testdata/entry/select_selected.xml @@ -3,14 +3,12 @@ - - - - - Testing - - + + + + Testing + diff --git a/widget/testdata/entry/select_single_line_pagedown.xml b/widget/testdata/entry/select_single_line_pagedown.xml index ddaf0d08b3..b5e249a46d 100644 --- a/widget/testdata/entry/select_single_line_pagedown.xml +++ b/widget/testdata/entry/select_single_line_pagedown.xml @@ -3,13 +3,11 @@ - - - - Testing - - + + + Testing + diff --git a/widget/testdata/entry/select_single_line_shift_pagedown.xml b/widget/testdata/entry/select_single_line_shift_pagedown.xml index afaafca133..12bbe2ab9f 100644 --- a/widget/testdata/entry/select_single_line_shift_pagedown.xml +++ b/widget/testdata/entry/select_single_line_shift_pagedown.xml @@ -3,14 +3,12 @@ - - - - - Testing - - + + + + Testing + diff --git a/widget/testdata/entry/select_single_line_shift_pageup.xml b/widget/testdata/entry/select_single_line_shift_pageup.xml index 81202bba56..34ff760c90 100644 --- a/widget/testdata/entry/select_single_line_shift_pageup.xml +++ b/widget/testdata/entry/select_single_line_shift_pageup.xml @@ -3,14 +3,12 @@ - - - - - Testing - - + + + + Testing + diff --git a/widget/testdata/entry/selection_add_one_row_down.xml b/widget/testdata/entry/selection_add_one_row_down.xml index dd6ca93c89..21e76431ec 100644 --- a/widget/testdata/entry/selection_add_one_row_down.xml +++ b/widget/testdata/entry/selection_add_one_row_down.xml @@ -8,9 +8,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_add_to_end.xml b/widget/testdata/entry/selection_add_to_end.xml index a6345c5dd4..47f8c56ce9 100644 --- a/widget/testdata/entry/selection_add_to_end.xml +++ b/widget/testdata/entry/selection_add_to_end.xml @@ -7,9 +7,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_add_to_home.xml b/widget/testdata/entry/selection_add_to_home.xml index 7ab9ee56a4..afe1ae5643 100644 --- a/widget/testdata/entry/selection_add_to_home.xml +++ b/widget/testdata/entry/selection_add_to_home.xml @@ -7,9 +7,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_delete_and_add_down.xml b/widget/testdata/entry/selection_delete_and_add_down.xml index 37934f496f..24959edcaa 100644 --- a/widget/testdata/entry/selection_delete_and_add_down.xml +++ b/widget/testdata/entry/selection_delete_and_add_down.xml @@ -8,9 +8,9 @@ - Testing - Teng - Testing + Testing + Teng + Testing diff --git a/widget/testdata/entry/selection_delete_and_add_up.xml b/widget/testdata/entry/selection_delete_and_add_up.xml index b48b225151..f4c89a1302 100644 --- a/widget/testdata/entry/selection_delete_and_add_up.xml +++ b/widget/testdata/entry/selection_delete_and_add_up.xml @@ -8,9 +8,9 @@ - Testing - Teng - Testing + Testing + Teng + Testing diff --git a/widget/testdata/entry/selection_delete_multi_line.xml b/widget/testdata/entry/selection_delete_multi_line.xml index 9dfd9ca984..03f71782a7 100644 --- a/widget/testdata/entry/selection_delete_multi_line.xml +++ b/widget/testdata/entry/selection_delete_multi_line.xml @@ -6,9 +6,9 @@ - Testing - Teng - + Testing + Teng + diff --git a/widget/testdata/entry/selection_delete_reverse_multi_line.xml b/widget/testdata/entry/selection_delete_reverse_multi_line.xml index 0e5e8f3bb8..98035d5e56 100644 --- a/widget/testdata/entry/selection_delete_reverse_multi_line.xml +++ b/widget/testdata/entry/selection_delete_reverse_multi_line.xml @@ -6,9 +6,9 @@ - Testing - Testisting - + Testing + Testisting + diff --git a/widget/testdata/entry/selection_delete_single_line.xml b/widget/testdata/entry/selection_delete_single_line.xml index 165d65460a..8bb605d442 100644 --- a/widget/testdata/entry/selection_delete_single_line.xml +++ b/widget/testdata/entry/selection_delete_single_line.xml @@ -6,9 +6,9 @@ - Testing - Teng - Testing + Testing + Teng + Testing diff --git a/widget/testdata/entry/selection_deselect_backspace.xml b/widget/testdata/entry/selection_deselect_backspace.xml index ebd388447f..3dccd944d3 100644 --- a/widget/testdata/entry/selection_deselect_backspace.xml +++ b/widget/testdata/entry/selection_deselect_backspace.xml @@ -6,9 +6,9 @@ - Testing - Tsting - Testing + Testing + Tsting + Testing diff --git a/widget/testdata/entry/selection_deselect_delete.xml b/widget/testdata/entry/selection_deselect_delete.xml index 323ea0de36..2064d93a11 100644 --- a/widget/testdata/entry/selection_deselect_delete.xml +++ b/widget/testdata/entry/selection_deselect_delete.xml @@ -6,9 +6,9 @@ - Testing - Teting - Testing + Testing + Teting + Testing diff --git a/widget/testdata/entry/selection_deselect_select_backspace.xml b/widget/testdata/entry/selection_deselect_select_backspace.xml index 71987c19f3..2677f3e980 100644 --- a/widget/testdata/entry/selection_deselect_select_backspace.xml +++ b/widget/testdata/entry/selection_deselect_select_backspace.xml @@ -6,9 +6,9 @@ - Testing - Teing - Testing + Testing + Teing + Testing diff --git a/widget/testdata/entry/selection_end.xml b/widget/testdata/entry/selection_end.xml index 5b0963fd7f..de11767ce6 100644 --- a/widget/testdata/entry/selection_end.xml +++ b/widget/testdata/entry/selection_end.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_enter.xml b/widget/testdata/entry/selection_enter.xml index 8e664a932f..6e8f780519 100644 --- a/widget/testdata/entry/selection_enter.xml +++ b/widget/testdata/entry/selection_enter.xml @@ -6,10 +6,10 @@ - Testing - Te - ng - Testing + Testing + Te + ng + Testing diff --git a/widget/testdata/entry/selection_focus_gained.xml b/widget/testdata/entry/selection_focus_gained.xml index c375e53a8b..cdf57077d2 100644 --- a/widget/testdata/entry/selection_focus_gained.xml +++ b/widget/testdata/entry/selection_focus_gained.xml @@ -7,9 +7,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_focus_lost.xml b/widget/testdata/entry/selection_focus_lost.xml index 64f54edd48..2b485d61c0 100644 --- a/widget/testdata/entry/selection_focus_lost.xml +++ b/widget/testdata/entry/selection_focus_lost.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_home.xml b/widget/testdata/entry/selection_home.xml index 15d4505569..3f41f213d9 100644 --- a/widget/testdata/entry/selection_home.xml +++ b/widget/testdata/entry/selection_home.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_initial.xml b/widget/testdata/entry/selection_initial.xml index c375e53a8b..cdf57077d2 100644 --- a/widget/testdata/entry/selection_initial.xml +++ b/widget/testdata/entry/selection_initial.xml @@ -7,9 +7,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_initial_reverse.xml b/widget/testdata/entry/selection_initial_reverse.xml index 0bb4c92aa2..818648eed3 100644 --- a/widget/testdata/entry/selection_initial_reverse.xml +++ b/widget/testdata/entry/selection_initial_reverse.xml @@ -7,9 +7,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_remove_add_one_row_up.xml b/widget/testdata/entry/selection_remove_add_one_row_up.xml index f09f2b134a..5557f06fdd 100644 --- a/widget/testdata/entry/selection_remove_add_one_row_up.xml +++ b/widget/testdata/entry/selection_remove_add_one_row_up.xml @@ -8,9 +8,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_remove_one_row_up.xml b/widget/testdata/entry/selection_remove_one_row_up.xml index c375e53a8b..cdf57077d2 100644 --- a/widget/testdata/entry/selection_remove_one_row_up.xml +++ b/widget/testdata/entry/selection_remove_one_row_up.xml @@ -7,9 +7,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_replace.xml b/widget/testdata/entry/selection_replace.xml index 84c9ced714..5a9e8f6bf0 100644 --- a/widget/testdata/entry/selection_replace.xml +++ b/widget/testdata/entry/selection_replace.xml @@ -6,9 +6,9 @@ - Testing - Tehellong - Testing + Testing + Tehellong + Testing diff --git a/widget/testdata/entry/selection_snap_down.xml b/widget/testdata/entry/selection_snap_down.xml index 6fb69fd002..e9c9321742 100644 --- a/widget/testdata/entry/selection_snap_down.xml +++ b/widget/testdata/entry/selection_snap_down.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_snap_left.xml b/widget/testdata/entry/selection_snap_left.xml index bb0fb8a2bb..68a9994a4a 100644 --- a/widget/testdata/entry/selection_snap_left.xml +++ b/widget/testdata/entry/selection_snap_left.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_snap_right.xml b/widget/testdata/entry/selection_snap_right.xml index 017fcaa3e9..994b7d75ea 100644 --- a/widget/testdata/entry/selection_snap_right.xml +++ b/widget/testdata/entry/selection_snap_right.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/selection_snap_up.xml b/widget/testdata/entry/selection_snap_up.xml index 92a6bb3bd5..e947ce8b06 100644 --- a/widget/testdata/entry/selection_snap_up.xml +++ b/widget/testdata/entry/selection_snap_up.xml @@ -6,9 +6,9 @@ - Testing - Testing - Testing + Testing + Testing + Testing diff --git a/widget/testdata/entry/set_placeholder_replaced.xml b/widget/testdata/entry/set_placeholder_replaced.xml index 8598e25d9a..ebb08a66f8 100644 --- a/widget/testdata/entry/set_placeholder_replaced.xml +++ b/widget/testdata/entry/set_placeholder_replaced.xml @@ -3,11 +3,9 @@ - - - - Hi - + + + Hi diff --git a/widget/testdata/entry/set_placeholder_set.xml b/widget/testdata/entry/set_placeholder_set.xml index 4e7c3adae3..e9ba0df1e8 100644 --- a/widget/testdata/entry/set_placeholder_set.xml +++ b/widget/testdata/entry/set_placeholder_set.xml @@ -3,14 +3,12 @@ - - - - Test - - - - + + + Test + + + diff --git a/widget/testdata/entry/set_text_changed.xml b/widget/testdata/entry/set_text_changed.xml index b4b1a4c1ac..8fdaf5589b 100644 --- a/widget/testdata/entry/set_text_changed.xml +++ b/widget/testdata/entry/set_text_changed.xml @@ -3,11 +3,9 @@ - - - - Test - + + + Test diff --git a/widget/testdata/entry/set_text_style_bold.xml b/widget/testdata/entry/set_text_style_bold.xml index 9a6255936c..e71dc36bb3 100644 --- a/widget/testdata/entry/set_text_style_bold.xml +++ b/widget/testdata/entry/set_text_style_bold.xml @@ -3,11 +3,9 @@ - - - - Styled Text - + + + Styled Text diff --git a/widget/testdata/entry/set_text_style_italic.xml b/widget/testdata/entry/set_text_style_italic.xml index e9039eea6f..06e6d11fe6 100644 --- a/widget/testdata/entry/set_text_style_italic.xml +++ b/widget/testdata/entry/set_text_style_italic.xml @@ -3,11 +3,9 @@ - - - - Styled Text - + + + Styled Text diff --git a/widget/testdata/entry/set_text_style_monospace.xml b/widget/testdata/entry/set_text_style_monospace.xml index 5df4141ba0..b51ce1abff 100644 --- a/widget/testdata/entry/set_text_style_monospace.xml +++ b/widget/testdata/entry/set_text_style_monospace.xml @@ -3,11 +3,9 @@ - - - - Styled Text - + + + Styled Text diff --git a/widget/testdata/entry/tapped_focused.xml b/widget/testdata/entry/tapped_focused.xml index 728cae2f14..0fbd605162 100644 --- a/widget/testdata/entry/tapped_focused.xml +++ b/widget/testdata/entry/tapped_focused.xml @@ -6,9 +6,9 @@ - MMM - WWW - + MMM + WWW + diff --git a/widget/testdata/entry/tapped_initial.xml b/widget/testdata/entry/tapped_initial.xml index 8e81471a83..2ed9b08f5b 100644 --- a/widget/testdata/entry/tapped_initial.xml +++ b/widget/testdata/entry/tapped_initial.xml @@ -6,9 +6,9 @@ - MMM - WWW - + MMM + WWW + diff --git a/widget/testdata/entry/tapped_secondary_full_menu.xml b/widget/testdata/entry/tapped_secondary_full_menu.xml index 4c178cf416..99f95efac5 100644 --- a/widget/testdata/entry/tapped_secondary_full_menu.xml +++ b/widget/testdata/entry/tapped_secondary_full_menu.xml @@ -3,16 +3,14 @@ - - - - - - - - - + + + + + + + diff --git a/widget/testdata/entry/tapped_secondary_no_password_menu.xml b/widget/testdata/entry/tapped_secondary_no_password_menu.xml index f5f7a0adea..47b12478fa 100644 --- a/widget/testdata/entry/tapped_secondary_no_password_menu.xml +++ b/widget/testdata/entry/tapped_secondary_no_password_menu.xml @@ -3,14 +3,12 @@ - - - - - - - - + + + + + + diff --git a/widget/testdata/entry/tapped_secondary_password_menu.xml b/widget/testdata/entry/tapped_secondary_password_menu.xml index cc70974b55..e8964aeb69 100644 --- a/widget/testdata/entry/tapped_secondary_password_menu.xml +++ b/widget/testdata/entry/tapped_secondary_password_menu.xml @@ -3,16 +3,14 @@ - - - - - - - - - + + + + + + + diff --git a/widget/testdata/entry/tapped_secondary_read_menu.xml b/widget/testdata/entry/tapped_secondary_read_menu.xml index 11dafbe0b3..addba5b25c 100644 --- a/widget/testdata/entry/tapped_secondary_read_menu.xml +++ b/widget/testdata/entry/tapped_secondary_read_menu.xml @@ -3,14 +3,12 @@ - - - - - - - - + + + + + + diff --git a/widget/testdata/entry/tapped_tapped_2nd_m.xml b/widget/testdata/entry/tapped_tapped_2nd_m.xml index 4a6b9e4e7b..39eea02959 100644 --- a/widget/testdata/entry/tapped_tapped_2nd_m.xml +++ b/widget/testdata/entry/tapped_tapped_2nd_m.xml @@ -6,9 +6,9 @@ - MMM - WWW - + MMM + WWW + diff --git a/widget/testdata/entry/tapped_tapped_3rd_m.xml b/widget/testdata/entry/tapped_tapped_3rd_m.xml index 48791b96ea..2aad89bf5f 100644 --- a/widget/testdata/entry/tapped_tapped_3rd_m.xml +++ b/widget/testdata/entry/tapped_tapped_3rd_m.xml @@ -6,9 +6,9 @@ - MMM - WWW - + MMM + WWW + diff --git a/widget/testdata/entry/tapped_tapped_after_last_col.xml b/widget/testdata/entry/tapped_tapped_after_last_col.xml index 061322fb69..b4f6e78ae7 100644 --- a/widget/testdata/entry/tapped_tapped_after_last_col.xml +++ b/widget/testdata/entry/tapped_tapped_after_last_col.xml @@ -6,9 +6,9 @@ - MMM - WWW - + MMM + WWW + diff --git a/widget/testdata/entry/tapped_tapped_after_last_row.xml b/widget/testdata/entry/tapped_tapped_after_last_row.xml index 864fd1ab18..d5095460e8 100644 --- a/widget/testdata/entry/tapped_tapped_after_last_row.xml +++ b/widget/testdata/entry/tapped_tapped_after_last_row.xml @@ -6,9 +6,9 @@ - MMM - WWW - + MMM + WWW + diff --git a/widget/testdata/entry/validate_initial.xml b/widget/testdata/entry/validate_initial.xml index e11bd1d6e6..d0860b5e97 100644 --- a/widget/testdata/entry/validate_initial.xml +++ b/widget/testdata/entry/validate_initial.xml @@ -3,14 +3,12 @@ - - - - - - - - + + + + + + diff --git a/widget/testdata/entry/validate_invalid.xml b/widget/testdata/entry/validate_invalid.xml index f1ab8df5de..9b9285273a 100644 --- a/widget/testdata/entry/validate_invalid.xml +++ b/widget/testdata/entry/validate_invalid.xml @@ -3,11 +3,9 @@ - - - - 2020-02 - + + + 2020-02 diff --git a/widget/testdata/entry/validate_valid.xml b/widget/testdata/entry/validate_valid.xml index 6fc0c9ca6a..7938e84a66 100644 --- a/widget/testdata/entry/validate_valid.xml +++ b/widget/testdata/entry/validate_valid.xml @@ -3,13 +3,11 @@ - - - - 2020-02-12 - - + + + 2020-02-12 + diff --git a/widget/testdata/entry/wrap_multi_line_off.xml b/widget/testdata/entry/wrap_multi_line_off.xml index 72eacae2ea..527661a11d 100644 --- a/widget/testdata/entry/wrap_multi_line_off.xml +++ b/widget/testdata/entry/wrap_multi_line_off.xml @@ -3,13 +3,11 @@ - - - - A long text on short words w/o NLs or LFs. - - + + + A long text on short words w/o NLs or LFs. + diff --git a/widget/testdata/entry/wrap_multi_line_truncate.xml b/widget/testdata/entry/wrap_multi_line_truncate.xml new file mode 100644 index 0000000000..516a312956 --- /dev/null +++ b/widget/testdata/entry/wrap_multi_line_truncate.xml @@ -0,0 +1,16 @@ + + + + + + + + + A long text on short words w/o NLs or LFs. + + + + + + + diff --git a/widget/testdata/entry/wrap_multi_line_wrap_break.xml b/widget/testdata/entry/wrap_multi_line_wrap_break.xml index d60c5719c3..65eb38f625 100644 --- a/widget/testdata/entry/wrap_multi_line_wrap_break.xml +++ b/widget/testdata/entry/wrap_multi_line_wrap_break.xml @@ -6,9 +6,9 @@ - A long text on s - hort words w/o - NLs or LFs. + A long text on s + hort words w/o + NLs or LFs. diff --git a/widget/testdata/entry/wrap_multi_line_wrap_word.xml b/widget/testdata/entry/wrap_multi_line_wrap_word.xml index 5a8f5bdb57..39f8fc4538 100644 --- a/widget/testdata/entry/wrap_multi_line_wrap_word.xml +++ b/widget/testdata/entry/wrap_multi_line_wrap_word.xml @@ -6,9 +6,9 @@ - A long text on - short words - w/o NLs or LFs. + A long text on + short words + w/o NLs or LFs. diff --git a/widget/testdata/entry/wrap_single_line_off.xml b/widget/testdata/entry/wrap_single_line_off.xml index fdf8adc0a8..01e4efc7c2 100644 --- a/widget/testdata/entry/wrap_single_line_off.xml +++ b/widget/testdata/entry/wrap_single_line_off.xml @@ -3,13 +3,11 @@ - - - - Testing Wrapping - - + + + Testing Wrapping + diff --git a/widget/testdata/entry/wrap_single_line_truncate.xml b/widget/testdata/entry/wrap_single_line_truncate.xml new file mode 100644 index 0000000000..b9c274aeb8 --- /dev/null +++ b/widget/testdata/entry/wrap_single_line_truncate.xml @@ -0,0 +1,16 @@ + + + + + + + + + Testing Wrapping + + + + + + + diff --git a/widget/testdata/form/hint_initial.png b/widget/testdata/form/hint_initial.png index 5411c4eda5..4131fae84f 100644 Binary files a/widget/testdata/form/hint_initial.png and b/widget/testdata/form/hint_initial.png differ diff --git a/widget/testdata/form/hint_invalid.png b/widget/testdata/form/hint_invalid.png index f28c8bea85..de16457c11 100644 Binary files a/widget/testdata/form/hint_invalid.png and b/widget/testdata/form/hint_invalid.png differ diff --git a/widget/testdata/form/hint_valid.png b/widget/testdata/form/hint_valid.png index 57ee5d8d75..074296cb8c 100644 Binary files a/widget/testdata/form/hint_valid.png and b/widget/testdata/form/hint_valid.png differ diff --git a/widget/testdata/form/layout.xml b/widget/testdata/form/layout.xml index 51d45959ea..cbcf06d2a4 100644 --- a/widget/testdata/form/layout.xml +++ b/widget/testdata/form/layout.xml @@ -3,36 +3,36 @@ - - test1 + + test1 - - - - - - - + + + + + + + - - + + - - test2 + + test2 - - - - - - - + + + + + + + - - + + diff --git a/widget/testdata/form/theme_changed.png b/widget/testdata/form/theme_changed.png index 9fde4ccb52..f806ca71e6 100644 Binary files a/widget/testdata/form/theme_changed.png and b/widget/testdata/form/theme_changed.png differ diff --git a/widget/testdata/form/theme_initial.png b/widget/testdata/form/theme_initial.png index 2ecec5d044..3be28e5146 100644 Binary files a/widget/testdata/form/theme_initial.png and b/widget/testdata/form/theme_initial.png differ diff --git a/widget/testdata/form/validation_entry_first_type_initial.png b/widget/testdata/form/validation_entry_first_type_initial.png new file mode 100644 index 0000000000..d07826e0ca Binary files /dev/null and b/widget/testdata/form/validation_entry_first_type_initial.png differ diff --git a/widget/testdata/form/validation_entry_first_type_invalid.png b/widget/testdata/form/validation_entry_first_type_invalid.png new file mode 100644 index 0000000000..2b613f3bf1 Binary files /dev/null and b/widget/testdata/form/validation_entry_first_type_invalid.png differ diff --git a/widget/testdata/form/validation_entry_first_type_valid.png b/widget/testdata/form/validation_entry_first_type_valid.png new file mode 100644 index 0000000000..9a43ea3b5c Binary files /dev/null and b/widget/testdata/form/validation_entry_first_type_valid.png differ diff --git a/widget/testdata/form/validation_initial.png b/widget/testdata/form/validation_initial.png index 99d093b156..2b1b3937aa 100644 Binary files a/widget/testdata/form/validation_initial.png and b/widget/testdata/form/validation_initial.png differ diff --git a/widget/testdata/form/validation_invalid.png b/widget/testdata/form/validation_invalid.png index 6691e5e8d8..821fecba79 100644 Binary files a/widget/testdata/form/validation_invalid.png and b/widget/testdata/form/validation_invalid.png differ diff --git a/widget/testdata/form/validation_valid.png b/widget/testdata/form/validation_valid.png index 24a340d012..9043df7cc6 100644 Binary files a/widget/testdata/form/validation_valid.png and b/widget/testdata/form/validation_valid.png differ diff --git a/widget/testdata/label/default.xml b/widget/testdata/label/default.xml index bf64a1963d..c2fc62ab24 100644 --- a/widget/testdata/label/default.xml +++ b/widget/testdata/label/default.xml @@ -1,7 +1,7 @@ - + - - Hello + + Hello diff --git a/widget/testdata/label/truncate.xml b/widget/testdata/label/truncate.xml index d7dea3d8da..53dbeb434a 100644 --- a/widget/testdata/label/truncate.xml +++ b/widget/testdata/label/truncate.xml @@ -1,7 +1,7 @@ - + - - Hel + + Hell diff --git a/widget/testdata/list/initial.xml b/widget/testdata/list/initial.xml index 9d8a9abd85..e92687347a 100644 --- a/widget/testdata/list/initial.xml +++ b/widget/testdata/list/initial.xml @@ -8,8 +8,8 @@ - - Test Item 0 + + Test Item 0 @@ -18,8 +18,8 @@ - - Test Item 1 + + Test Item 1 @@ -28,8 +28,8 @@ - - Test Item 2 + + Test Item 2 @@ -38,8 +38,8 @@ - - Test Item 3 + + Test Item 3 @@ -48,8 +48,8 @@ - - Test Item 4 + + Test Item 4 @@ -58,8 +58,8 @@ - - Test Item 5 + + Test Item 5 @@ -68,8 +68,8 @@ - - Test Item 6 + + Test Item 6 @@ -78,8 +78,8 @@ - - Test Item 7 + + Test Item 7 @@ -88,8 +88,8 @@ - - Test Item 8 + + Test Item 8 @@ -98,8 +98,8 @@ - - Test Item 9 + + Test Item 9 diff --git a/widget/testdata/list/item_removed.xml b/widget/testdata/list/item_removed.xml index 107e8de667..78b6e5d159 100644 --- a/widget/testdata/list/item_removed.xml +++ b/widget/testdata/list/item_removed.xml @@ -8,8 +8,8 @@ - - Test Item 0 + + Test Item 0 @@ -18,8 +18,8 @@ - - Test Item 1 + + Test Item 1 diff --git a/widget/testdata/list/list_initial.png b/widget/testdata/list/list_initial.png index a44493f7b9..d4c56c4432 100644 Binary files a/widget/testdata/list/list_initial.png and b/widget/testdata/list/list_initial.png differ diff --git a/widget/testdata/list/list_theme_changed.png b/widget/testdata/list/list_theme_changed.png index bf93fee91c..371ff6f5af 100644 Binary files a/widget/testdata/list/list_theme_changed.png and b/widget/testdata/list/list_theme_changed.png differ diff --git a/widget/testdata/list/new_data.xml b/widget/testdata/list/new_data.xml index 052173c1ca..1779abd9a4 100644 --- a/widget/testdata/list/new_data.xml +++ b/widget/testdata/list/new_data.xml @@ -8,8 +8,8 @@ - - a + + a @@ -18,8 +18,8 @@ - - b + + b @@ -28,8 +28,8 @@ - - c + + c @@ -38,8 +38,8 @@ - - d + + d @@ -48,8 +48,8 @@ - - e + + e @@ -58,8 +58,8 @@ - - f + + f @@ -68,8 +68,8 @@ - - g + + g @@ -78,8 +78,8 @@ - - h + + h @@ -88,8 +88,8 @@ - - i + + i @@ -98,8 +98,8 @@ - - j + + j diff --git a/widget/testdata/list/offset_changed.xml b/widget/testdata/list/offset_changed.xml index 2671f26b12..43e6e12a7a 100644 --- a/widget/testdata/list/offset_changed.xml +++ b/widget/testdata/list/offset_changed.xml @@ -3,23 +3,13 @@ - - - - - - - Test Item 5 - - - - - Test Item 6 + + Test Item 6 @@ -28,8 +18,8 @@ - - Test Item 7 + + Test Item 7 @@ -38,8 +28,8 @@ - - Test Item 8 + + Test Item 8 @@ -48,8 +38,8 @@ - - Test Item 9 + + Test Item 9 @@ -58,8 +48,8 @@ - - Test Item 10 + + Test Item 10 @@ -68,8 +58,8 @@ - - Test Item 11 + + Test Item 11 @@ -78,8 +68,8 @@ - - Test Item 12 + + Test Item 12 @@ -88,8 +78,8 @@ - - Test Item 13 + + Test Item 13 @@ -98,17 +88,24 @@ - - Test Item 14 + + Test Item 14 + + + + + + + + + + Test Item 15 - - - @@ -133,6 +130,9 @@ + + + diff --git a/widget/testdata/list/resized.xml b/widget/testdata/list/resized.xml index 29c3b9cf7f..0f8b84dfe8 100644 --- a/widget/testdata/list/resized.xml +++ b/widget/testdata/list/resized.xml @@ -8,8 +8,8 @@ - - Test Item 0 + + Test Item 0 @@ -18,8 +18,8 @@ - - Test Item 1 + + Test Item 1 @@ -28,8 +28,8 @@ - - Test Item 2 + + Test Item 2 @@ -38,8 +38,8 @@ - - Test Item 3 + + Test Item 3 @@ -48,8 +48,8 @@ - - Test Item 4 + + Test Item 4 @@ -58,8 +58,8 @@ - - Test Item 5 + + Test Item 5 @@ -68,8 +68,8 @@ - - Test Item 6 + + Test Item 6 @@ -78,8 +78,8 @@ - - Test Item 7 + + Test Item 7 @@ -88,8 +88,8 @@ - - Test Item 8 + + Test Item 8 @@ -98,8 +98,8 @@ - - Test Item 9 + + Test Item 9 @@ -108,8 +108,8 @@ - - Test Item 10 + + Test Item 10 @@ -118,8 +118,8 @@ - - Test Item 11 + + Test Item 11 @@ -128,8 +128,8 @@ - - Test Item 12 + + Test Item 12 @@ -138,8 +138,8 @@ - - Test Item 13 + + Test Item 13 diff --git a/widget/testdata/list/small.xml b/widget/testdata/list/small.xml index 107e8de667..e5261b537f 100644 --- a/widget/testdata/list/small.xml +++ b/widget/testdata/list/small.xml @@ -8,8 +8,8 @@ - - Test Item 0 + + Test Item 0 @@ -18,8 +18,8 @@ - - Test Item 1 + + Test Item 1 diff --git a/widget/testdata/menu/mobile/drag_bottom.xml b/widget/testdata/menu/mobile/drag_bottom.xml index a7362ccdb6..289b3256a0 100644 --- a/widget/testdata/menu/mobile/drag_bottom.xml +++ b/widget/testdata/menu/mobile/drag_bottom.xml @@ -17,6 +17,7 @@ + A @@ -39,7 +40,8 @@ - + + diff --git a/widget/testdata/menu/mobile/drag_middle.xml b/widget/testdata/menu/mobile/drag_middle.xml index 049e71592d..8d36554f3a 100644 --- a/widget/testdata/menu/mobile/drag_middle.xml +++ b/widget/testdata/menu/mobile/drag_middle.xml @@ -17,6 +17,7 @@ + A @@ -39,7 +40,8 @@ - + + diff --git a/widget/testdata/menu/mobile/drag_top.xml b/widget/testdata/menu/mobile/drag_top.xml index c41d20236a..88b592b9de 100644 --- a/widget/testdata/menu/mobile/drag_top.xml +++ b/widget/testdata/menu/mobile/drag_top.xml @@ -17,6 +17,7 @@ + A @@ -39,7 +40,8 @@ - + + diff --git a/widget/testdata/menu/mobile/layout_background_reset.xml b/widget/testdata/menu/mobile/layout_background_reset.xml index 847d4d9353..1ce9d74b5f 100644 --- a/widget/testdata/menu/mobile/layout_background_reset.xml +++ b/widget/testdata/menu/mobile/layout_background_reset.xml @@ -17,6 +17,7 @@ + A diff --git a/widget/testdata/menu/mobile/layout_no_space_on_both_sides.xml b/widget/testdata/menu/mobile/layout_no_space_on_both_sides.xml index 1f63833302..e5ac53f3d1 100644 --- a/widget/testdata/menu/mobile/layout_no_space_on_both_sides.xml +++ b/widget/testdata/menu/mobile/layout_no_space_on_both_sides.xml @@ -17,6 +17,7 @@ + A @@ -47,6 +48,7 @@ + subitem A @@ -74,6 +76,7 @@ + subsubitem A (long) diff --git a/widget/testdata/menu/mobile/layout_no_space_on_right.xml b/widget/testdata/menu/mobile/layout_no_space_on_right.xml index cd24de2b52..da42e5ad26 100644 --- a/widget/testdata/menu/mobile/layout_no_space_on_right.xml +++ b/widget/testdata/menu/mobile/layout_no_space_on_right.xml @@ -17,6 +17,7 @@ + A @@ -47,6 +48,7 @@ + subitem A @@ -74,6 +76,7 @@ + subsubitem A (long) diff --git a/widget/testdata/menu/mobile/layout_normal.xml b/widget/testdata/menu/mobile/layout_normal.xml index 847d4d9353..1ce9d74b5f 100644 --- a/widget/testdata/menu/mobile/layout_normal.xml +++ b/widget/testdata/menu/mobile/layout_normal.xml @@ -17,6 +17,7 @@ + A diff --git a/widget/testdata/menu/mobile/layout_normal_with_submenus.xml b/widget/testdata/menu/mobile/layout_normal_with_submenus.xml index 49acd9f3c2..e3907f7da1 100644 --- a/widget/testdata/menu/mobile/layout_normal_with_submenus.xml +++ b/widget/testdata/menu/mobile/layout_normal_with_submenus.xml @@ -17,6 +17,7 @@ + A @@ -47,6 +48,7 @@ + subitem A @@ -74,6 +76,7 @@ + subsubitem A (long) diff --git a/widget/testdata/menu/mobile/layout_theme_changed.xml b/widget/testdata/menu/mobile/layout_theme_changed.xml index 847d4d9353..1ce9d74b5f 100644 --- a/widget/testdata/menu/mobile/layout_theme_changed.xml +++ b/widget/testdata/menu/mobile/layout_theme_changed.xml @@ -17,6 +17,7 @@ + A diff --git a/widget/testdata/menu/mobile/layout_window_too_short.xml b/widget/testdata/menu/mobile/layout_window_too_short.xml index c3d102cafe..b660ad083b 100644 --- a/widget/testdata/menu/mobile/layout_window_too_short.xml +++ b/widget/testdata/menu/mobile/layout_window_too_short.xml @@ -17,6 +17,7 @@ + A @@ -34,7 +35,8 @@ - + + diff --git a/widget/testdata/menu/mobile/layout_window_too_short_for_submenu.xml b/widget/testdata/menu/mobile/layout_window_too_short_for_submenu.xml index 6265fedaae..d9525e1fea 100644 --- a/widget/testdata/menu/mobile/layout_window_too_short_for_submenu.xml +++ b/widget/testdata/menu/mobile/layout_window_too_short_for_submenu.xml @@ -17,6 +17,7 @@ + A @@ -47,6 +48,7 @@ + subitem A @@ -74,6 +76,7 @@ + subsubitem A (long) diff --git a/widget/testdata/password_entry/concealed.xml b/widget/testdata/password_entry/concealed.xml index a6419329fe..0473cd9dc9 100644 --- a/widget/testdata/password_entry/concealed.xml +++ b/widget/testdata/password_entry/concealed.xml @@ -6,7 +6,7 @@ - ••••••• + ••••••• diff --git a/widget/testdata/password_entry/initial.xml b/widget/testdata/password_entry/initial.xml index 72bf2cfadd..f8079fb4c2 100644 --- a/widget/testdata/password_entry/initial.xml +++ b/widget/testdata/password_entry/initial.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/password_entry/obfuscation_typed.xml b/widget/testdata/password_entry/obfuscation_typed.xml index a6419329fe..0473cd9dc9 100644 --- a/widget/testdata/password_entry/obfuscation_typed.xml +++ b/widget/testdata/password_entry/obfuscation_typed.xml @@ -6,7 +6,7 @@ - ••••••• + ••••••• diff --git a/widget/testdata/password_entry/placeholder_initial.xml b/widget/testdata/password_entry/placeholder_initial.xml index 7b619ab68b..4ba7987e9f 100644 --- a/widget/testdata/password_entry/placeholder_initial.xml +++ b/widget/testdata/password_entry/placeholder_initial.xml @@ -6,10 +6,10 @@ - Password + Password - + diff --git a/widget/testdata/password_entry/placeholder_typed.xml b/widget/testdata/password_entry/placeholder_typed.xml index a6419329fe..0473cd9dc9 100644 --- a/widget/testdata/password_entry/placeholder_typed.xml +++ b/widget/testdata/password_entry/placeholder_typed.xml @@ -6,7 +6,7 @@ - ••••••• + ••••••• diff --git a/widget/testdata/password_entry/revealed.xml b/widget/testdata/password_entry/revealed.xml index e84b0aa3d4..fdd761f9d3 100644 --- a/widget/testdata/password_entry/revealed.xml +++ b/widget/testdata/password_entry/revealed.xml @@ -6,7 +6,7 @@ - Hié™שרה + Hié™שרה diff --git a/widget/testdata/popup/modal-onshow-theme-changed.png b/widget/testdata/popup/modal-onshow-theme-changed.png new file mode 100644 index 0000000000..7eb9f2eb4d Binary files /dev/null and b/widget/testdata/popup/modal-onshow-theme-changed.png differ diff --git a/widget/testdata/popup/modal-onshow-theme-default.png b/widget/testdata/popup/modal-onshow-theme-default.png new file mode 100644 index 0000000000..6d5b4ab05b Binary files /dev/null and b/widget/testdata/popup/modal-onshow-theme-default.png differ diff --git a/widget/testdata/popup/modal.xml b/widget/testdata/popup/modal.xml index 0964ada44f..4e1dc93b03 100644 --- a/widget/testdata/popup/modal.xml +++ b/widget/testdata/popup/modal.xml @@ -4,20 +4,20 @@ - + - - - - - + + + + + - - - Hi + + + Hi diff --git a/widget/testdata/popup/normal-onshow-theme-changed.png b/widget/testdata/popup/normal-onshow-theme-changed.png new file mode 100644 index 0000000000..e91095e387 Binary files /dev/null and b/widget/testdata/popup/normal-onshow-theme-changed.png differ diff --git a/widget/testdata/popup/normal-onshow-theme-default.png b/widget/testdata/popup/normal-onshow-theme-default.png new file mode 100644 index 0000000000..6eb39805cf Binary files /dev/null and b/widget/testdata/popup/normal-onshow-theme-default.png differ diff --git a/widget/testdata/popup/normal.xml b/widget/testdata/popup/normal.xml index aa8ce53ccc..84c5ee792a 100644 --- a/widget/testdata/popup/normal.xml +++ b/widget/testdata/popup/normal.xml @@ -4,19 +4,19 @@ - + - - - - - + + + + + - - - Hi + + + Hi diff --git a/widget/testdata/select_entry/disableable_disabled.xml b/widget/testdata/select_entry/disableable_disabled.xml index 4c253ca86b..cefd82dbb2 100644 --- a/widget/testdata/select_entry/disableable_disabled.xml +++ b/widget/testdata/select_entry/disableable_disabled.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/select_entry/disableable_enabled.xml b/widget/testdata/select_entry/disableable_enabled.xml index 591c716a5b..debb22e079 100644 --- a/widget/testdata/select_entry/disableable_enabled.xml +++ b/widget/testdata/select_entry/disableable_enabled.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/select_entry/disableable_enabled_opened.xml b/widget/testdata/select_entry/disableable_enabled_opened.xml index 7578659afd..b28966444b 100644 --- a/widget/testdata/select_entry/disableable_enabled_opened.xml +++ b/widget/testdata/select_entry/disableable_enabled_opened.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/select_entry/disableable_enabled_tapped.xml b/widget/testdata/select_entry/disableable_enabled_tapped.xml index 669e4436be..f267fee874 100644 --- a/widget/testdata/select_entry/disableable_enabled_tapped.xml +++ b/widget/testdata/select_entry/disableable_enabled_tapped.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/select_entry/dropdown_B_opened.xml b/widget/testdata/select_entry/dropdown_B_opened.xml index fc71f31dbc..e63d15a096 100644 --- a/widget/testdata/select_entry/dropdown_B_opened.xml +++ b/widget/testdata/select_entry/dropdown_B_opened.xml @@ -6,7 +6,7 @@ - B + B diff --git a/widget/testdata/select_entry/dropdown_empty_opened.xml b/widget/testdata/select_entry/dropdown_empty_opened.xml index 7578659afd..b28966444b 100644 --- a/widget/testdata/select_entry/dropdown_empty_opened.xml +++ b/widget/testdata/select_entry/dropdown_empty_opened.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/select_entry/dropdown_empty_opened_shrunk.xml b/widget/testdata/select_entry/dropdown_empty_opened_shrunk.xml index 761b846ef2..f5658a30b6 100644 --- a/widget/testdata/select_entry/dropdown_empty_opened_shrunk.xml +++ b/widget/testdata/select_entry/dropdown_empty_opened_shrunk.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/select_entry/dropdown_empty_setopts.xml b/widget/testdata/select_entry/dropdown_empty_setopts.xml index 7fe743aac4..b5caf53fc9 100644 --- a/widget/testdata/select_entry/dropdown_empty_setopts.xml +++ b/widget/testdata/select_entry/dropdown_empty_setopts.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/select_entry/dropdown_initial.xml b/widget/testdata/select_entry/dropdown_initial.xml index 591c716a5b..debb22e079 100644 --- a/widget/testdata/select_entry/dropdown_initial.xml +++ b/widget/testdata/select_entry/dropdown_initial.xml @@ -6,10 +6,10 @@ - + - + diff --git a/widget/testdata/select_entry/dropdown_tapped_B.xml b/widget/testdata/select_entry/dropdown_tapped_B.xml index 210dd3fa7d..1212265e2e 100644 --- a/widget/testdata/select_entry/dropdown_tapped_B.xml +++ b/widget/testdata/select_entry/dropdown_tapped_B.xml @@ -6,7 +6,7 @@ - B + B diff --git a/widget/testdata/select_entry/dropdown_tapped_C.xml b/widget/testdata/select_entry/dropdown_tapped_C.xml index ed9e14a87a..64d71e3732 100644 --- a/widget/testdata/select_entry/dropdown_tapped_C.xml +++ b/widget/testdata/select_entry/dropdown_tapped_C.xml @@ -6,7 +6,7 @@ - C + C diff --git a/widget/testdata/table/col_size.png b/widget/testdata/table/col_size.png index ec9c2fb5db..d69bd15d26 100644 Binary files a/widget/testdata/table/col_size.png and b/widget/testdata/table/col_size.png differ diff --git a/widget/testdata/table/desktop/hovered.xml b/widget/testdata/table/desktop/hovered.xml index adf2713f2a..8a09fce9c9 100644 --- a/widget/testdata/table/desktop/hovered.xml +++ b/widget/testdata/table/desktop/hovered.xml @@ -2,38 +2,38 @@ - - - Cell 0, 0 + + + Cell 0, 0 - - Cell 0, 1 + + Cell 0, 1 - - Cell 1, 0 + + Cell 1, 0 - - Cell 1, 1 + + Cell 1, 1 - - Cell 2, 0 + + Cell 2, 0 - - Cell 2, 1 + + Cell 2, 1 - - + + - + - + diff --git a/widget/testdata/table/desktop/hovered_out.xml b/widget/testdata/table/desktop/hovered_out.xml index 0347602679..357c4bddb4 100644 --- a/widget/testdata/table/desktop/hovered_out.xml +++ b/widget/testdata/table/desktop/hovered_out.xml @@ -2,30 +2,30 @@ - - - Cell 0, 0 + + + Cell 0, 0 - - Cell 0, 1 + + Cell 0, 1 - - Cell 1, 0 + + Cell 1, 0 - - Cell 1, 1 + + Cell 1, 1 - - + + - + diff --git a/widget/testdata/table/selected.xml b/widget/testdata/table/selected.xml index f6e47cab17..4e6f96de23 100644 --- a/widget/testdata/table/selected.xml +++ b/widget/testdata/table/selected.xml @@ -2,36 +2,36 @@ - - - Cell 0, 0 + + + Cell 0, 0 - - Cell 0, 1 + + Cell 0, 1 - - Cell 1, 0 + + Cell 1, 0 - - Cell 1, 1 + + Cell 1, 1 - - Cell 2, 0 + + Cell 2, 0 - - Cell 2, 1 + + Cell 2, 1 - - Cell 3, 0 + + Cell 3, 0 - - Cell 3, 1 + + Cell 3, 1 - - Cell 4, 0 + + Cell 4, 0 - - Cell 4, 1 + + Cell 4, 1 @@ -43,17 +43,17 @@ - - + + - + - + diff --git a/widget/testdata/table/selected_scrolled.xml b/widget/testdata/table/selected_scrolled.xml index 2daace7a37..46e362c394 100644 --- a/widget/testdata/table/selected_scrolled.xml +++ b/widget/testdata/table/selected_scrolled.xml @@ -2,30 +2,30 @@ - - - Cell 1, 2 + + + Cell 1, 2 - - Cell 1, 3 + + Cell 1, 3 - - Cell 2, 2 + + Cell 2, 2 - - Cell 2, 3 + + Cell 2, 3 - - Cell 3, 2 + + Cell 3, 2 - - Cell 3, 3 + + Cell 3, 3 - - Cell 4, 2 + + Cell 4, 2 - - Cell 4, 3 + + Cell 4, 3 @@ -37,8 +37,8 @@ - - + + @@ -48,9 +48,9 @@ - + - + diff --git a/widget/testdata/table/theme_changed.png b/widget/testdata/table/theme_changed.png index 26a87a8fe1..1e7166fcb1 100644 Binary files a/widget/testdata/table/theme_changed.png and b/widget/testdata/table/theme_changed.png differ diff --git a/widget/testdata/table/theme_initial.png b/widget/testdata/table/theme_initial.png index 58b6d682ed..11c0cbecad 100644 Binary files a/widget/testdata/table/theme_initial.png and b/widget/testdata/table/theme_initial.png differ diff --git a/widget/testdata/tree/layout_multiple.xml b/widget/testdata/tree/layout_multiple.xml index 998ca2f7b8..b71f4797c4 100644 --- a/widget/testdata/tree/layout_multiple.xml +++ b/widget/testdata/tree/layout_multiple.xml @@ -5,7 +5,7 @@ - A + A @@ -16,7 +16,7 @@ - B + B @@ -27,7 +27,7 @@ - 44444444444444444444 + 44444444444444444444 diff --git a/widget/testdata/tree/layout_multiple_branch.xml b/widget/testdata/tree/layout_multiple_branch.xml index 121b57f251..fba6ebd14f 100644 --- a/widget/testdata/tree/layout_multiple_branch.xml +++ b/widget/testdata/tree/layout_multiple_branch.xml @@ -5,7 +5,7 @@ - A + A @@ -16,7 +16,7 @@ - B + B diff --git a/widget/testdata/tree/layout_multiple_branch_opened.xml b/widget/testdata/tree/layout_multiple_branch_opened.xml index 68fd5ea9cf..b63e3c5420 100644 --- a/widget/testdata/tree/layout_multiple_branch_opened.xml +++ b/widget/testdata/tree/layout_multiple_branch_opened.xml @@ -2,65 +2,65 @@ - - - - A + + + + A - - + + - - - 11111 + + + 11111 - - + + - - - B + + + B - - + + - - - 2222222222 + + + 2222222222 - - + + - - - C + + + C - - + + - - - 333333333333333 + + + 333333333333333 - - + + diff --git a/widget/testdata/tree/layout_multiple_branch_opened_leaf_selected.xml b/widget/testdata/tree/layout_multiple_branch_opened_leaf_selected.xml index 51b150883b..0b1989d7c8 100644 --- a/widget/testdata/tree/layout_multiple_branch_opened_leaf_selected.xml +++ b/widget/testdata/tree/layout_multiple_branch_opened_leaf_selected.xml @@ -2,66 +2,66 @@ - - - - A + + + + A - - + + - - - 11111 + + + 11111 - - + + - - - B + + + B - - + + - - - 2222222222 + + + 2222222222 - - + + - - - C + + + C - - + + - - - 333333333333333 + + + 333333333333333 - - + + diff --git a/widget/testdata/tree/layout_multiple_branch_opened_selected.xml b/widget/testdata/tree/layout_multiple_branch_opened_selected.xml index 585d6c1abe..f356ebd5de 100644 --- a/widget/testdata/tree/layout_multiple_branch_opened_selected.xml +++ b/widget/testdata/tree/layout_multiple_branch_opened_selected.xml @@ -2,66 +2,66 @@ - - - - A + + + + A - - + + - - - 11111 + + + 11111 - - + + - - - B + + + B - - + + - - - 2222222222 + + + 2222222222 - - + + - - - C + + + C - - + + - - - 333333333333333 + + + 333333333333333 - - + + diff --git a/widget/testdata/tree/layout_multiple_branch_selected.xml b/widget/testdata/tree/layout_multiple_branch_selected.xml index e54c5fb6ad..fc41d2776b 100644 --- a/widget/testdata/tree/layout_multiple_branch_selected.xml +++ b/widget/testdata/tree/layout_multiple_branch_selected.xml @@ -5,7 +5,7 @@ - A + A @@ -16,7 +16,7 @@ - B + B diff --git a/widget/testdata/tree/layout_multiple_leaf.xml b/widget/testdata/tree/layout_multiple_leaf.xml index cdc7d2aadf..f0e7e1d8d1 100644 --- a/widget/testdata/tree/layout_multiple_leaf.xml +++ b/widget/testdata/tree/layout_multiple_leaf.xml @@ -5,7 +5,7 @@ - 11111 + 11111 @@ -13,7 +13,7 @@ - 2222222222 + 2222222222 @@ -21,7 +21,7 @@ - 333333333333333 + 333333333333333 @@ -29,7 +29,7 @@ - 44444444444444444444 + 44444444444444444444 diff --git a/widget/testdata/tree/layout_multiple_leaf_selected.xml b/widget/testdata/tree/layout_multiple_leaf_selected.xml index 0368416e01..bf27e102c6 100644 --- a/widget/testdata/tree/layout_multiple_leaf_selected.xml +++ b/widget/testdata/tree/layout_multiple_leaf_selected.xml @@ -5,7 +5,7 @@ - 11111 + 11111 @@ -13,7 +13,7 @@ - 2222222222 + 2222222222 @@ -22,7 +22,7 @@ - 333333333333333 + 333333333333333 @@ -30,7 +30,7 @@ - 44444444444444444444 + 44444444444444444444 diff --git a/widget/testdata/tree/layout_multiple_selected.xml b/widget/testdata/tree/layout_multiple_selected.xml index 29ee7d9a6e..4c1d48b445 100644 --- a/widget/testdata/tree/layout_multiple_selected.xml +++ b/widget/testdata/tree/layout_multiple_selected.xml @@ -5,7 +5,7 @@ - A + A @@ -16,7 +16,7 @@ - B + B @@ -27,7 +27,7 @@ - 44444444444444444444 + 44444444444444444444 diff --git a/widget/testdata/tree/layout_single_branch.xml b/widget/testdata/tree/layout_single_branch.xml index 6675755a03..d6284b8afd 100644 --- a/widget/testdata/tree/layout_single_branch.xml +++ b/widget/testdata/tree/layout_single_branch.xml @@ -5,7 +5,7 @@ - A + A diff --git a/widget/testdata/tree/layout_single_branch_opened.xml b/widget/testdata/tree/layout_single_branch_opened.xml index d3a05492a9..290ddbfd14 100644 --- a/widget/testdata/tree/layout_single_branch_opened.xml +++ b/widget/testdata/tree/layout_single_branch_opened.xml @@ -5,7 +5,7 @@ - A + A @@ -16,7 +16,7 @@ - 11111 + 11111 diff --git a/widget/testdata/tree/layout_single_branch_opened_leaf_selected.xml b/widget/testdata/tree/layout_single_branch_opened_leaf_selected.xml index 4c2501ba20..2d481e1eb9 100644 --- a/widget/testdata/tree/layout_single_branch_opened_leaf_selected.xml +++ b/widget/testdata/tree/layout_single_branch_opened_leaf_selected.xml @@ -5,7 +5,7 @@ - A + A @@ -16,7 +16,7 @@ - 11111 + 11111 diff --git a/widget/testdata/tree/layout_single_branch_opened_selected.xml b/widget/testdata/tree/layout_single_branch_opened_selected.xml index b6790e4e3e..ee60bf29db 100644 --- a/widget/testdata/tree/layout_single_branch_opened_selected.xml +++ b/widget/testdata/tree/layout_single_branch_opened_selected.xml @@ -5,7 +5,7 @@ - A + A @@ -17,7 +17,7 @@ - 11111 + 11111 diff --git a/widget/testdata/tree/layout_single_branch_selected.xml b/widget/testdata/tree/layout_single_branch_selected.xml index c8a7b29972..97d7d20f1e 100644 --- a/widget/testdata/tree/layout_single_branch_selected.xml +++ b/widget/testdata/tree/layout_single_branch_selected.xml @@ -5,7 +5,7 @@ - A + A diff --git a/widget/testdata/tree/layout_single_leaf.xml b/widget/testdata/tree/layout_single_leaf.xml index bd9046f555..a8a42438a9 100644 --- a/widget/testdata/tree/layout_single_leaf.xml +++ b/widget/testdata/tree/layout_single_leaf.xml @@ -5,7 +5,7 @@ - 11111 + 11111 diff --git a/widget/testdata/tree/layout_single_leaf_selected.xml b/widget/testdata/tree/layout_single_leaf_selected.xml index e3dc9684d8..094f375770 100644 --- a/widget/testdata/tree/layout_single_leaf_selected.xml +++ b/widget/testdata/tree/layout_single_leaf_selected.xml @@ -5,7 +5,7 @@ - 11111 + 11111 diff --git a/widget/testdata/tree/move_initial.xml b/widget/testdata/tree/move_initial.xml index 7d5b1f9045..83852c4b67 100644 --- a/widget/testdata/tree/move_initial.xml +++ b/widget/testdata/tree/move_initial.xml @@ -5,7 +5,7 @@ - foo + foo @@ -16,7 +16,7 @@ - foobar + foobar diff --git a/widget/testdata/tree/move_moved.xml b/widget/testdata/tree/move_moved.xml index 7f2e5fc459..72c8488efe 100644 --- a/widget/testdata/tree/move_moved.xml +++ b/widget/testdata/tree/move_moved.xml @@ -5,7 +5,7 @@ - foo + foo @@ -16,7 +16,7 @@ - foobar + foobar diff --git a/widget/testdata/tree/refresh_initial.png b/widget/testdata/tree/refresh_initial.png index d1f7b34a07..ba603a01ac 100644 Binary files a/widget/testdata/tree/refresh_initial.png and b/widget/testdata/tree/refresh_initial.png differ diff --git a/widget/testdata/tree/refresh_replaced.png b/widget/testdata/tree/refresh_replaced.png index b16f524641..392087ba5e 100644 Binary files a/widget/testdata/tree/refresh_replaced.png and b/widget/testdata/tree/refresh_replaced.png differ diff --git a/widget/testdata/tree/theme_changed.png b/widget/testdata/tree/theme_changed.png index f815c4a644..3329071632 100644 Binary files a/widget/testdata/tree/theme_changed.png and b/widget/testdata/tree/theme_changed.png differ diff --git a/widget/testdata/tree/theme_initial.png b/widget/testdata/tree/theme_initial.png index 5655b9fd4c..ce113d1659 100644 Binary files a/widget/testdata/tree/theme_initial.png and b/widget/testdata/tree/theme_initial.png differ diff --git a/widget/text.go b/widget/text.go index 94e68192b8..3d98346df8 100644 --- a/widget/text.go +++ b/widget/text.go @@ -291,7 +291,7 @@ func (r *textRenderer) Layout(size fyne.Size) { xPos := theme.Padding() + r.provider.extraPad.Width yPos := theme.Padding() + r.provider.extraPad.Height lineHeight := r.provider.charMinSize().Height - lineSize := fyne.NewSize(size.Width-theme.Padding()*2, lineHeight) + lineSize := fyne.NewSize(size.Width-yPos*2, lineHeight) for i := 0; i < len(r.texts); i++ { text := r.texts[i] text.Resize(lineSize)