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 @@
-
+
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)
})