diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d952fcac9..85c20f8166 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ 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). -## 1.2.2 - Ongoing +## 1.2.2 - 29 January 2020 ### Added @@ -12,16 +12,23 @@ More detailed release notes can be found on the [releases page](https://github.c ### Changed +* Scale calculations are now relative to system scale - the default "1" matches the system +* Update scale on Linux to be "auto" by default (and numbers are relative to 96DPI standard) (#595) +* When auto scaling check the monitor in the middle of the window, not top left * bundled files now have a standard header to optimise some tools like go report card * Shortcuts are now handled by the event queue - fixed possible deadlock ### Fixed -* Corrected visual behaviour of extended widgets including Entry, Select, Check and Radio -* Entries that are extended would crash on right click. +* Scroll horizontally when holding shift key (#579) +* Updating text and calling refresh for widget doesn't work (#607) +* Corrected visual behaviour of extended widgets including Entry, Select, Check, Radio and Icon (#615) +* Entries and Selects that are extended would crash on right click. * PasswordEntry created from Entry with Password = true has no revealer * Dialog width not always sufficient for title * Pasting unicode characters could panic (#597) +* Setting theme before application start panics on macOS (#626) +* MenuItem type conflicts with other projects (#632) ## 1.2.1 - 24 December 2019 diff --git a/README.md b/README.md index 80aa8be6e4..85695fefed 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

GoDoc Reference - 1.2.1 release + 1.2.2 release Join us on Slack Support Fyne.io
diff --git a/canvas.go b/canvas.go index bb54742004..88572c54d8 100644 --- a/canvas.go +++ b/canvas.go @@ -19,7 +19,7 @@ type Canvas interface { Scale() float32 // SetScale sets ths scale for this canvas only, overriding system and user settings. // - // Deprecated: SetScale will be replaced by system wide settings in the future + // Deprecated: Settings are now calculated solely on the user configuration and system setup. SetScale(float32) Overlay() CanvasObject diff --git a/cmd/fyne/install.go b/cmd/fyne/install.go index 6a23432050..1bb42bd398 100644 --- a/cmd/fyne/install.go +++ b/cmd/fyne/install.go @@ -49,7 +49,7 @@ func (i *installer) install() error { i.installDir = filepath.Join(os.Getenv("ProgramFiles"), p.name) runAsAdminWindows("mkdir", "\"\""+filepath.Join(os.Getenv("ProgramFiles"), p.name)+"\"\"") default: - return errors.New("Unsupported terget operating system \"" + p.os + "\"") + return errors.New("Unsupported target operating system \"" + p.os + "\"") } } diff --git a/cmd/fyne/internal/mobile/binres/binres.go b/cmd/fyne/internal/mobile/binres/binres.go index a8faeb0599..752bbb386c 100644 --- a/cmd/fyne/internal/mobile/binres/binres.go +++ b/cmd/fyne/internal/mobile/binres/binres.go @@ -273,6 +273,13 @@ func UnmarshalXML(r io.Reader, withIcon bool) (*XML, error) { }, Value: fmt.Sprintf("%v", MinSDK), }, + { + Name: xml.Name{ + Space: androidSchema, + Local: "targetSdkVersion", + }, + Value: "28", + }, }, } e := xml.EndElement{Name: xml.Name{Local: "uses-sdk"}} diff --git a/device.go b/device.go index 36ae06eea2..d8aed6cc2f 100644 --- a/device.go +++ b/device.go @@ -29,6 +29,7 @@ type Device interface { Orientation() DeviceOrientation IsMobile() bool HasKeyboard() bool + SystemScale() float32 } // CurrentDevice returns the device information for the current hardware (via the driver) diff --git a/internal/driver/glfw/canvas.go b/internal/driver/glfw/canvas.go index b8cd2fecac..3fab206720 100644 --- a/internal/driver/glfw/canvas.go +++ b/internal/driver/glfw/canvas.go @@ -180,22 +180,16 @@ func (c *glCanvas) Scale() float32 { // SetScale sets the render scale for this specific canvas // -// Deprecated: SetScale will be replaced by system wide settings in the future -func (c *glCanvas) SetScale(scale float32) { - c.setScaleValue(scale) - - c.context.RescaleContext() -} - -func (c *glCanvas) setScaleValue(scale float32) { - if scale == fyne.SettingsScaleAuto { - c.scale = c.detectedScale - } else if scale == 0 { +// Deprecated: Settings are now calculated solely on the user configuration and system setup. +func (c *glCanvas) SetScale(_ float32) { + if !c.context.(*window).visible { return - } else { - c.scale = scale } + + c.scale = c.context.(*window).calculatedScale() c.setDirty(true) + + c.context.RescaleContext() } func (c *glCanvas) OnTypedRune() func(rune) { diff --git a/internal/driver/glfw/canvas_test.go b/internal/driver/glfw/canvas_test.go index 041238ea88..ae10a32108 100644 --- a/internal/driver/glfw/canvas_test.go +++ b/internal/driver/glfw/canvas_test.go @@ -65,7 +65,6 @@ func Test_glCanvas_SetContent(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1) w.SetPadded(tt.padding) if tt.menu { w.SetMainMenu(fyne.NewMainMenu(fyne.NewMenu("Test", fyne.NewMenuItem("Test", func() {})))) @@ -118,7 +117,6 @@ func Test_glCanvas_ChildMinSizeChangeAffectsAncestorsUpToRoot(t *testing.T) { func Test_glCanvas_ChildMinSizeChangeAffectsAncestorsUpToScroll(t *testing.T) { w := d.CreateWindow("Test").(*window) c := w.Canvas().(*glCanvas) - c.SetScale(1) leftObj1 := canvas.NewRectangle(color.Black) leftObj1.SetMinSize(fyne.NewSize(50, 50)) leftObj2 := canvas.NewRectangle(color.Black) @@ -156,7 +154,6 @@ func Test_glCanvas_ChildMinSizeChangeAffectsAncestorsUpToScroll(t *testing.T) { func Test_glCanvas_ChildMinSizeChangesInDifferentScrollAffectAncestorsUpToScroll(t *testing.T) { w := d.CreateWindow("Test").(*window) c := w.Canvas().(*glCanvas) - c.SetScale(1) leftObj1 := canvas.NewRectangle(color.Black) leftObj1.SetMinSize(fyne.NewSize(50, 50)) leftObj2 := canvas.NewRectangle(color.Black) diff --git a/internal/driver/glfw/device.go b/internal/driver/glfw/device.go index 7a4f582b6b..2415f7d858 100644 --- a/internal/driver/glfw/device.go +++ b/internal/driver/glfw/device.go @@ -1,6 +1,10 @@ package glfw -import "fyne.io/fyne" +import ( + "runtime" + + "fyne.io/fyne" +) type glDevice struct { } @@ -19,3 +23,11 @@ func (*glDevice) IsMobile() bool { func (*glDevice) HasKeyboard() bool { return true // TODO actually check - we could be in tablet mode } + +func (*glDevice) SystemScale() float32 { + if runtime.GOOS == "windows" || runtime.GOOS == "darwin" { + return 1.0 + } + + return fyne.SettingsScaleAuto +} diff --git a/internal/driver/glfw/menu_darwin.m b/internal/driver/glfw/menu_darwin.m index 4991284821..0607321a94 100644 --- a/internal/driver/glfw/menu_darwin.m +++ b/internal/driver/glfw/menu_darwin.m @@ -5,12 +5,12 @@ extern void menu_callback(int); -@interface MenuItem : NSObject { +@interface FyneMenuItem : NSObject { } @end -@implementation MenuItem +@implementation FyneMenuItem - (void) tapped:(id) sender { menu_callback([sender tag]); } @@ -30,7 +30,7 @@ void createDarwinMenu(const char* label) { } void addDarwinMenuItem(const char* label, int id) { - MenuItem* tapper = [[MenuItem alloc] init]; // we cannot release this or the menu does not function... + FyneMenuItem* tapper = [[FyneMenuItem alloc] init]; // we cannot release this or the menu does not function... NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:[NSString stringWithUTF8String:label] action:@selector(tapped:) keyEquivalent:@""]; diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index 61dd3a47d8..5f74de8b13 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -263,13 +263,16 @@ func (w *window) SetOnClosed(closed func()) { } func (w *window) getMonitorForWindow() *glfw.Monitor { + xOff := w.xpos + (w.width / 2) + yOff := w.ypos + (w.height / 2) + for _, monitor := range glfw.GetMonitors() { x, y := monitor.GetPos() - if x > w.xpos || y > w.ypos { + if x > xOff || y > yOff { continue } - if x+monitor.GetVideoMode().Width <= w.xpos || y+monitor.GetVideoMode().Height <= w.ypos { + if x+monitor.GetVideoMode().Width <= xOff || y+monitor.GetVideoMode().Height <= yOff { continue } @@ -285,7 +288,7 @@ func (w *window) getMonitorForWindow() *glfw.Monitor { return monitor } -func (w *window) selectScale() float32 { +func (w *window) userScale() float32 { env := os.Getenv("FYNE_SCALE") if env != "" && env != "auto" { @@ -298,20 +301,30 @@ func (w *window) selectScale() float32 { if env != "auto" { setting := fyne.CurrentApp().Settings().Scale() - switch setting { - case fyne.SettingsScaleAuto: - // fall through - case 0.0: - if env == "" { - return 1.0 - } - // fall through - default: + if setting != fyne.SettingsScaleAuto && setting != 0.0 { return setting } } - return w.detectScale() + return 1.0 // user preference for auto is now passed as 1 so the system auto is picked up +} + +func calculateScale(user, system, detected float32) float32 { + if user == fyne.SettingsScaleAuto { + user = 1.0 + } + + if system == fyne.SettingsScaleAuto { + system = detected + } + + return system * user +} +func (w *window) calculatedScale() float32 { + val := calculateScale(w.userScale(), fyne.CurrentDevice().SystemScale(), w.detectScale()) + val = float32(math.Round(float64(val*10.0))) / 10.0 + + return val } func (w *window) detectScale() float32 { @@ -323,7 +336,7 @@ func (w *window) detectScale() float32 { if dpi > 1000 || dpi < 10 { dpi = 96 } - return float32(math.Round(float64(dpi)/144.0*10.0)) / 10.0 + return float32(float64(dpi) / 96.0) } func (w *window) Show() { @@ -434,22 +447,16 @@ func (w *window) destroy(d *gLDriver) { } func (w *window) moved(viewport *glfw.Window, x, y int) { - if w.canvas.scale != w.canvas.detectedScale { - return - } - // save coordinates w.xpos, w.ypos = x, y - scale := w.canvas.scale - newScale := w.detectScale() - if scale == newScale { - forceWindowRefresh(w) + + newDetected := w.detectScale() + if w.canvas.detectedScale == newDetected { return } - w.canvas.detectedScale = newScale - w.canvas.setScaleValue(newScale) - w.rescaleOnMain() + w.canvas.detectedScale = newDetected + go w.canvas.SetScale(fyne.SettingsScaleAuto) // scale value is ignored } func (w *window) resized(viewport *glfw.Window, width, height int) { @@ -683,6 +690,11 @@ func (w *window) mouseScrolled(viewport *glfw.Window, xoff float64, yoff float64 }) switch wid := co.(type) { case fyne.Scrollable: + if runtime.GOOS != "darwin" && xoff == 0 && + (viewport.GetKey(glfw.KeyLeftShift) == glfw.Press || + viewport.GetKey(glfw.KeyRightShift) == glfw.Press) { + xoff, yoff = yoff, xoff + } ev := &fyne.ScrollEvent{} ev.DeltaX = int(xoff * scrollSpeed) ev.DeltaY = int(yoff * scrollSpeed) @@ -1056,6 +1068,15 @@ func (w *window) RescaleContext() { func (w *window) rescaleOnMain() { w.fitContent() + if w.fullScreen { + w.width, w.height = w.viewport.GetSize() + scaledFull := fyne.NewSize( + internal.UnscaleInt(w.canvas, w.width), + internal.UnscaleInt(w.canvas, w.height)) + w.canvas.Resize(scaledFull) + return + } + size := w.canvas.size.Union(w.canvas.MinSize()) newWidth, newHeight := w.screenSize(size) w.viewport.SetSize(newWidth, newHeight) @@ -1114,7 +1135,7 @@ func (d *gLDriver) CreateWindow(title string) fyne.Window { ret.canvas.painter.Init() ret.canvas.context = ret ret.canvas.detectedScale = ret.detectScale() - ret.canvas.scale = ret.selectScale() + ret.canvas.scale = ret.calculatedScale() ret.SetIcon(ret.icon) d.windows = append(d.windows, ret) diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index e5a820b901..35c034b20a 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -39,7 +39,6 @@ func TestMain(m *testing.M) { func TestWindow_HandleHoverable(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1.0) h1 := &hoverableObject{Rectangle: canvas.NewRectangle(color.White)} h1.SetMinSize(fyne.NewSize(10, 10)) h2 := &hoverableObject{Rectangle: canvas.NewRectangle(color.Black)} @@ -81,7 +80,6 @@ func TestWindow_HandleHoverable(t *testing.T) { func TestWindow_HandleDragging(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1.0) d1 := &draggableObject{Rectangle: canvas.NewRectangle(color.White)} d1.SetMinSize(fyne.NewSize(10, 10)) d2 := &draggableObject{Rectangle: canvas.NewRectangle(color.Black)} @@ -180,7 +178,6 @@ func TestWindow_HandleDragging(t *testing.T) { func TestWindow_DragObjectThatMoves(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1.0) d1 := &draggableObject{Rectangle: canvas.NewRectangle(color.White)} d1.SetMinSize(fyne.NewSize(10, 10)) w.SetContent(widget.NewHBox(d1)) @@ -221,7 +218,6 @@ func TestWindow_DragObjectThatMoves(t *testing.T) { func TestWindow_DragIntoNewObjectKeepingFocus(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1.0) d1 := &draggableMouseableObject{Rectangle: canvas.NewRectangle(color.White)} d1.SetMinSize(fyne.NewSize(10, 10)) d2 := &draggableMouseableObject{Rectangle: canvas.NewRectangle(color.White)} @@ -261,7 +257,6 @@ func TestWindow_DragIntoNewObjectKeepingFocus(t *testing.T) { func TestWindow_NoDragEndWithoutDraggedEvent(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1.0) do := &draggableMouseableObject{Rectangle: canvas.NewRectangle(color.White)} do.SetMinSize(fyne.NewSize(10, 10)) w.SetContent(do) @@ -282,7 +277,6 @@ func TestWindow_NoDragEndWithoutDraggedEvent(t *testing.T) { func TestWindow_HoverableOnDragging(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1.0) dh := &draggableHoverableObject{Rectangle: canvas.NewRectangle(color.White)} dh.SetMinSize(fyne.NewSize(10, 10)) w.SetContent(dh) @@ -372,7 +366,6 @@ func TestWindow_Tapped(t *testing.T) { func TestWindow_TappedIgnoresScrollerClip(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1.0) fyne.CurrentApp().Settings().SetTheme(theme.DarkTheme()) rect := canvas.NewRectangle(color.White) rect.SetMinSize(fyne.NewSize(100, 100)) @@ -406,7 +399,6 @@ func TestWindow_TappedIgnoresScrollerClip(t *testing.T) { func TestWindow_TappedIgnoredWhenMovedOffOfTappable(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1.0) tapped := 0 b1 := widget.NewButton("Tap", func() { tapped = 1 }) b2 := widget.NewButton("Tap", func() { tapped = 2 }) @@ -568,6 +560,32 @@ func TestWindow_PixelSize(t *testing.T) { assert.Equal(t, internal.ScaleInt(w.Canvas(), 100), winH) } +var scaleTests = []struct { + user, system, detected, expected float32 + name string +}{ + {1.0, 1.0, 1.0, 1.0, "Windows with user setting 1.0"}, + {fyne.SettingsScaleAuto, 1.0, 1.0, 1.0, "Windows with user legacy setting auto"}, + {1.5, 1.0, 1.0, 1.5, "Windows with user setting 1.5"}, + + {1.0, fyne.SettingsScaleAuto, 1.0, 1.0, "Linux lowDPI with user setting 1.0"}, + {fyne.SettingsScaleAuto, fyne.SettingsScaleAuto, 1.0, 1.0, "Linux lowDPI with user legacy setting auto"}, + {1.5, fyne.SettingsScaleAuto, 1.0, 1.5, "Linux lowDPI with user setting 1.5"}, + + {1.0, fyne.SettingsScaleAuto, 2.0, 2.0, "Linux highDPI with user setting 1.0"}, + {fyne.SettingsScaleAuto, fyne.SettingsScaleAuto, 2.0, 2.0, "Linux highDPI with user legacy setting auto"}, + {1.5, fyne.SettingsScaleAuto, 2.0, 3.0, "Linux highDPI with user setting 1.5"}, +} + +func TestWindow_calculateScale(t *testing.T) { + for _, tt := range scaleTests { + t.Run(tt.name, func(t *testing.T) { + calculated := calculateScale(tt.user, tt.system, tt.detected) + assert.Equal(t, tt.expected, calculated) + }) + } +} + func TestWindow_Padded(t *testing.T) { w := d.CreateWindow("Test") content := canvas.NewRectangle(color.White) @@ -602,7 +620,6 @@ func TestWindow_SetPadded(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { w := d.CreateWindow("Test").(*window) - w.Canvas().SetScale(1) w.SetPadded(tt.padding) if tt.menu { w.SetMainMenu(fyne.NewMainMenu(fyne.NewMenu("Test", fyne.NewMenuItem("Test", func() {})))) diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 93ed774dbe..7644052ffc 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -127,14 +127,9 @@ func (c *mobileCanvas) Scale() float32 { return c.scale } -func (c *mobileCanvas) SetScale(scale float32) { - if scale == fyne.SettingsScaleAuto { - c.scale = deviceScale() - } else if scale == 0 { // not set in the config - return - } else { - c.scale = scale - } +// Deprecated: Settings are now calculated solely on the user configuration and system setup. +func (c *mobileCanvas) SetScale(_ float32) { + c.scale = fyne.CurrentDevice().SystemScale() } func (c *mobileCanvas) Overlay() fyne.CanvasObject { @@ -352,7 +347,7 @@ func (c *mobileCanvas) setupThemeListener() { // NewCanvas creates a new gomobile mobileCanvas. This is a mobileCanvas that will render on a mobile device using OpenGL. func NewCanvas() fyne.Canvas { ret := &mobileCanvas{padded: true} - ret.scale = deviceScale() + ret.scale = fyne.CurrentDevice().SystemScale() ret.refreshQueue = make(chan fyne.CanvasObject, 1024) ret.touched = make(map[int]mobile.Touchable) ret.lastTapDownPos = make(map[int]fyne.Position) diff --git a/internal/driver/gomobile/canvas_android.go b/internal/driver/gomobile/canvas_android.go index 2afc596c79..da8bec1e2a 100644 --- a/internal/driver/gomobile/canvas_android.go +++ b/internal/driver/gomobile/canvas_android.go @@ -8,6 +8,6 @@ func devicePadding() (topLeft, bottomRight fyne.Size) { return fyne.NewSize(0, 48), fyne.NewSize(0, 0) } -func deviceScale() float32 { +func (*device) SystemScale() float32 { return 3 // TODO detect device DPI and pick from 1, 1.5, 2, 3 and 4 } diff --git a/internal/driver/gomobile/canvas_desktop.go b/internal/driver/gomobile/canvas_desktop.go index 4be3d7f2d6..25763c9880 100644 --- a/internal/driver/gomobile/canvas_desktop.go +++ b/internal/driver/gomobile/canvas_desktop.go @@ -3,9 +3,6 @@ package gomobile import ( - "os" - "strconv" - "fyne.io/fyne" ) @@ -13,15 +10,6 @@ func devicePadding() (topLeft, bottomRight fyne.Size) { return fyne.NewSize(0, 0), fyne.NewSize(0, 0) } -func deviceScale() float32 { - env := os.Getenv("FYNE_SCALE") - if env != "" && env != "auto" { - scale, err := strconv.ParseFloat(env, 32) - if err == nil && scale != 0 { - return float32(scale) - } - fyne.LogError("Error reading scale", err) - } - - return 1 +func (*device) SystemScale() float32 { + return 2 } diff --git a/internal/driver/gomobile/canvas_ios.go b/internal/driver/gomobile/canvas_ios.go index 07d8361baa..e1d349b191 100644 --- a/internal/driver/gomobile/canvas_ios.go +++ b/internal/driver/gomobile/canvas_ios.go @@ -14,6 +14,6 @@ func devicePadding() (topLeft, bottomRight fyne.Size) { } } -func deviceScale() float32 { +func (*device) SystemScale() float32 { return 3 // TODO return 2 for < iPhone X, Xs but 3 for Plus/Max size } diff --git a/internal/driver/gomobile/device.go b/internal/driver/gomobile/device.go index 5096e0158b..f33d776db3 100644 --- a/internal/driver/gomobile/device.go +++ b/internal/driver/gomobile/device.go @@ -14,7 +14,7 @@ var currentOrientation size.Orientation // Declare conformity with Device var _ fyne.Device = (*device)(nil) -func (device) Orientation() fyne.DeviceOrientation { +func (*device) Orientation() fyne.DeviceOrientation { switch currentOrientation { case size.OrientationLandscape: return fyne.OrientationHorizontalLeft @@ -23,11 +23,11 @@ func (device) Orientation() fyne.DeviceOrientation { } } -func (device) IsMobile() bool { +func (*device) IsMobile() bool { return true } -func (device) HasKeyboard() bool { +func (*device) HasKeyboard() bool { return false } diff --git a/test/device.go b/test/device.go index 5b15fed469..8638a9334d 100644 --- a/test/device.go +++ b/test/device.go @@ -19,3 +19,7 @@ func (d *device) IsMobile() bool { func (d *device) HasKeyboard() bool { return false } + +func (d *device) SystemScale() float32 { + return 1 +} diff --git a/widget/entry.go b/widget/entry.go index 98fb32956e..664a6648ff 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -172,6 +172,9 @@ func (e *entryRenderer) BackgroundColor() color.Color { } func (e *entryRenderer) Refresh() { + if e.entry.Text != string(e.entry.textProvider().buffer) { + e.entry.textProvider().SetText(e.entry.Text) + } if e.entry.textProvider().len() == 0 && e.entry.Visible() { e.entry.placeholderProvider().Show() } else if e.entry.placeholderProvider().Visible() { diff --git a/widget/entry_test.go b/widget/entry_test.go index e20f089ff6..2c6caa6ed5 100644 --- a/widget/entry_test.go +++ b/widget/entry_test.go @@ -119,6 +119,16 @@ func TestEntry_SetText_Overflow(t *testing.T) { assert.Equal(t, "", entry.Text) } +func TestEntry_SetText_Manual(t *testing.T) { + entry := NewEntry() + provider := entry.textProvider() + assert.Equal(t, "", string(provider.buffer)) + + entry.Text = "Test" + entry.Refresh() + assert.Equal(t, "Test", string(provider.buffer)) +} + func TestEntry_OnKeyDown(t *testing.T) { entry := NewEntry() diff --git a/widget/icon.go b/widget/icon.go index 95676cf6fa..419868006f 100644 --- a/widget/icon.go +++ b/widget/icon.go @@ -46,7 +46,7 @@ func (i *iconRenderer) Refresh() { } i.Layout(i.image.Size()) - canvas.Refresh(i.image) + canvas.Refresh(i.image.super()) } func (i *iconRenderer) Destroy() { @@ -62,7 +62,7 @@ type Icon struct { // SetResource updates the resource rendered in this icon widget func (i *Icon) SetResource(res fyne.Resource) { i.Resource = res - i.refresh(i) + i.Refresh() } // MinSize returns the size that this widget should not shrink below @@ -84,6 +84,7 @@ func (i *Icon) CreateRenderer() fyne.WidgetRenderer { // NewIcon returns a new icon widget that displays a themed icon resource func NewIcon(res fyne.Resource) *Icon { icon := &Icon{} + icon.ExtendBaseWidget(icon) icon.SetResource(res) // force the image conversion return icon diff --git a/widget/icon_extend_test.go b/widget/icon_extend_test.go new file mode 100644 index 0000000000..bfa1f7ae88 --- /dev/null +++ b/widget/icon_extend_test.go @@ -0,0 +1,29 @@ +package widget + +import ( + "testing" + + "fyne.io/fyne/canvas" + "fyne.io/fyne/internal/cache" + "fyne.io/fyne/theme" + "github.com/stretchr/testify/assert" +) + +type extendedIcon struct { + Icon +} + +func newExtendedIcon() *extendedIcon { + icon := &extendedIcon{} + icon.ExtendBaseWidget(icon) + return icon +} + +func TestIcon_Extended_SetResource(t *testing.T) { + icon := newExtendedIcon() + icon.SetResource(theme.FyneLogo()) + + objs := cache.Renderer(icon).Objects() + assert.Equal(t, 1, len(objs)) + assert.Equal(t, theme.FyneLogo(), objs[0].(*canvas.Image).Resource) +} diff --git a/widget/icon_test.go b/widget/icon_test.go index 51d367fa45..3f0f8c0dee 100644 --- a/widget/icon_test.go +++ b/widget/icon_test.go @@ -4,13 +4,14 @@ import ( "testing" "fyne.io/fyne/canvas" + "fyne.io/fyne/internal/cache" "fyne.io/fyne/theme" "github.com/stretchr/testify/assert" ) func TestNewIcon(t *testing.T) { icon := NewIcon(theme.ConfirmIcon()) - render := Renderer(icon) + render := cache.Renderer(icon) assert.Equal(t, 1, len(render.Objects())) obj := render.Objects()[0] @@ -23,7 +24,7 @@ func TestNewIcon(t *testing.T) { func TestIcon_Nil(t *testing.T) { icon := NewIcon(nil) - render := Renderer(icon) + render := cache.Renderer(icon) assert.Equal(t, 0, len(render.Objects())) } @@ -38,7 +39,7 @@ func TestIcon_MinSize(t *testing.T) { func TestIconRenderer_ApplyTheme(t *testing.T) { icon := NewIcon(theme.CancelIcon()) - render := Renderer(icon).(*iconRenderer) + render := cache.Renderer(icon).(*iconRenderer) visible := render.objects[0].Visible() render.Refresh() diff --git a/widget/radio.go b/widget/radio.go index 82dc0ad0a7..cf3409af01 100644 --- a/widget/radio.go +++ b/widget/radio.go @@ -143,7 +143,7 @@ func (r *radioRenderer) Refresh() { if r.radio.Disabled() { item.focusIndicator.FillColor = theme.BackgroundColor() - } else if r.radio.hoveredItemIndex == i { + } else if r.radio.hovered && r.radio.hoveredItemIndex == i { item.focusIndicator.FillColor = theme.HoverColor() } else { item.focusIndicator.FillColor = theme.BackgroundColor() @@ -171,6 +171,7 @@ type Radio struct { Horizontal bool hoveredItemIndex int + hovered bool } // indexByPosition returns the item index for a specified position or noRadioItemIndex if any @@ -194,12 +195,14 @@ func (r *Radio) MouseIn(event *desktop.MouseEvent) { } r.hoveredItemIndex = r.indexByPosition(event.Position) + r.hovered = true r.Refresh() } // MouseOut is called when a desktop pointer exits the widget func (r *Radio) MouseOut() { r.hoveredItemIndex = noRadioItemIndex + r.hovered = false r.Refresh() } @@ -210,6 +213,7 @@ func (r *Radio) MouseMoved(event *desktop.MouseEvent) { } r.hoveredItemIndex = r.indexByPosition(event.Position) + r.hovered = true r.Refresh() } @@ -228,7 +232,7 @@ func (r *Radio) Tapped(event *fyne.PointEvent) { index := r.indexByPosition(event.Position) - if index == noRadioItemIndex { // in the padding + if index < 0 || index >= len(r.Options) { // in the padding return } clicked := r.Options[index] @@ -314,12 +318,9 @@ func (r *Radio) removeDuplicateOptions() { // NewRadio creates a new radio widget with the set options and change handler func NewRadio(options []string, changed func(string)) *Radio { r := &Radio{ - DisableableWidget{}, - options, - "", - changed, - false, - noRadioItemIndex, + DisableableWidget: DisableableWidget{}, + Options: options, + OnChanged: changed, } r.removeDuplicateOptions() diff --git a/widget/radio_extended_test.go b/widget/radio_extended_test.go new file mode 100644 index 0000000000..7eaaf39b67 --- /dev/null +++ b/widget/radio_extended_test.go @@ -0,0 +1,194 @@ +package widget + +import ( + "testing" + + "fyne.io/fyne" + "fyne.io/fyne/driver/desktop" + "fyne.io/fyne/internal/cache" + _ "fyne.io/fyne/test" + "fyne.io/fyne/theme" + "github.com/stretchr/testify/assert" +) + +type extendedRadio struct { + Radio +} + +func newExtendedRadio(opts []string, f func(string)) *extendedRadio { + ret := &extendedRadio{} + ret.Options = opts + ret.OnChanged = f + ret.ExtendBaseWidget(ret) + + return ret +} + +func TestRadio_Extended_BackgroundStyle(t *testing.T) { + radio := newExtendedRadio([]string{"Hi"}, nil) + bg := cache.Renderer(radio).BackgroundColor() + + assert.Equal(t, bg, theme.BackgroundColor()) +} + +func TestRadio_Extended_Selected(t *testing.T) { + selected := "" + radio := newExtendedRadio([]string{"Hi"}, func(sel string) { + selected = sel + }) + radio.Tapped(&fyne.PointEvent{Position: fyne.NewPos(theme.Padding(), theme.Padding())}) + + assert.Equal(t, "Hi", selected) +} + +func TestRadio_Extended_Unselected(t *testing.T) { + selected := "Hi" + radio := newExtendedRadio([]string{"Hi"}, func(sel string) { + selected = sel + }) + radio.Selected = selected + radio.Tapped(&fyne.PointEvent{Position: fyne.NewPos(theme.Padding(), theme.Padding())}) + + assert.Equal(t, "", selected) +} + +func TestRadio_Extended_Append(t *testing.T) { + radio := newExtendedRadio([]string{"Hi"}, nil) + + assert.Equal(t, 1, len(radio.Options)) + assert.Equal(t, 1, len(cache.Renderer(radio).(*radioRenderer).items)) + + radio.Options = append(radio.Options, "Another") + radio.Refresh() + + assert.Equal(t, 2, len(radio.Options)) + assert.Equal(t, 2, len(cache.Renderer(radio).(*radioRenderer).items)) +} + +func TestRadio_Extended_Remove(t *testing.T) { + radio := newExtendedRadio([]string{"Hi", "Another"}, nil) + + assert.Equal(t, 2, len(radio.Options)) + assert.Equal(t, 2, len(cache.Renderer(radio).(*radioRenderer).items)) + + radio.Options = radio.Options[:1] + radio.Refresh() + + assert.Equal(t, 1, len(radio.Options)) + assert.Equal(t, 1, len(cache.Renderer(radio).(*radioRenderer).items)) +} + +func TestRadio_Extended_SetSelected(t *testing.T) { + radio := newExtendedRadio([]string{"Hi", "Another"}, nil) + + radio.SetSelected("Another") + + assert.Equal(t, "Another", radio.Selected) +} + +func TestRadio_Extended_Disable(t *testing.T) { + selected := "" + radio := newExtendedRadio([]string{"Hi"}, func(sel string) { + selected = sel + }) + + radio.Disable() + radio.Tapped(&fyne.PointEvent{Position: fyne.NewPos(theme.Padding(), theme.Padding())}) + + assert.Equal(t, "", selected, "Radio should have been disabled.") +} + +func TestRadio_Extended_Enable(t *testing.T) { + selected := "" + radio := newExtendedRadio([]string{"Hi"}, func(sel string) { + selected = sel + }) + + radio.Disable() + radio.Tapped(&fyne.PointEvent{Position: fyne.NewPos(theme.Padding(), theme.Padding())}) + assert.Equal(t, "", selected, "Radio should have been disabled.") + + radio.Enable() + radio.Tapped(&fyne.PointEvent{Position: fyne.NewPos(theme.Padding(), theme.Padding())}) + assert.Equal(t, "Hi", selected, "Radio should have been re-enabled.") +} + +func TestRadio_Extended_Hovered(t *testing.T) { + tests := []struct { + name string + options []string + isHorizontal bool + }{ + { + name: "Horizontal", + options: []string{"Hi", "Another"}, + isHorizontal: true, + }, + { + name: "Vertical", + options: []string{"Hi", "Another"}, + isHorizontal: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + radio := newExtendedRadio(tt.options, nil) + radio.Horizontal = tt.isHorizontal + render := cache.Renderer(radio).(*radioRenderer) + + assert.Equal(t, false, radio.hovered) + assert.Equal(t, theme.BackgroundColor(), render.items[0].focusIndicator.FillColor) + assert.Equal(t, theme.BackgroundColor(), render.items[1].focusIndicator.FillColor) + + radio.SetSelected("Hi") + assert.Equal(t, theme.BackgroundColor(), render.items[0].focusIndicator.FillColor) + assert.Equal(t, theme.BackgroundColor(), render.items[1].focusIndicator.FillColor) + + radio.SetSelected("Another") + assert.Equal(t, theme.BackgroundColor(), render.items[0].focusIndicator.FillColor) + assert.Equal(t, theme.BackgroundColor(), render.items[1].focusIndicator.FillColor) + + radio.MouseIn(&desktop.MouseEvent{ + PointEvent: fyne.PointEvent{ + Position: fyne.NewPos(theme.Padding(), theme.Padding()), + }, + }) + assert.Equal(t, 0, radio.hoveredItemIndex) + assert.Equal(t, true, radio.hovered) + assert.Equal(t, theme.HoverColor(), render.items[0].focusIndicator.FillColor) + assert.Equal(t, theme.BackgroundColor(), render.items[1].focusIndicator.FillColor) + + var mouseMove fyne.Position + if tt.isHorizontal { + mouseMove = fyne.NewPos(radio.MinSize().Width-theme.Padding(), theme.Padding()) + } else { + mouseMove = fyne.NewPos(theme.Padding(), radio.MinSize().Height-theme.Padding()) + } + + radio.MouseMoved(&desktop.MouseEvent{ + PointEvent: fyne.PointEvent{ + Position: mouseMove, + }, + }) + assert.Equal(t, 1, radio.hoveredItemIndex) + assert.Equal(t, theme.BackgroundColor(), render.items[0].focusIndicator.FillColor) + assert.Equal(t, theme.HoverColor(), render.items[1].focusIndicator.FillColor) + }) + } +} + +func TestRadioRenderer_Extended_ApplyTheme(t *testing.T) { + radio := newExtendedRadio([]string{"Test"}, func(string) {}) + render := cache.Renderer(radio).(*radioRenderer) + + item := render.items[0] + textSize := item.label.TextSize + customTextSize := textSize + withTestTheme(func() { + render.applyTheme() + customTextSize = item.label.TextSize + }) + + assert.NotEqual(t, textSize, customTextSize) +} diff --git a/widget/radio_test.go b/widget/radio_test.go index 700831aaf3..f6ac37e3e1 100644 --- a/widget/radio_test.go +++ b/widget/radio_test.go @@ -257,7 +257,7 @@ func TestRadio_Hovered(t *testing.T) { radio.Horizontal = tt.isHorizontal render := Renderer(radio).(*radioRenderer) - assert.Equal(t, noRadioItemIndex, radio.hoveredItemIndex) + assert.Equal(t, false, radio.hovered) assert.Equal(t, theme.BackgroundColor(), render.items[0].focusIndicator.FillColor) assert.Equal(t, theme.BackgroundColor(), render.items[1].focusIndicator.FillColor) @@ -291,6 +291,7 @@ func TestRadio_Hovered(t *testing.T) { }, }) assert.Equal(t, 1, radio.hoveredItemIndex) + assert.Equal(t, true, radio.hovered) assert.Equal(t, theme.BackgroundColor(), render.items[0].focusIndicator.FillColor) assert.Equal(t, theme.HoverColor(), render.items[1].focusIndicator.FillColor) })