diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c7805aa7d..89c1ba2af0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ More detailed release notes can be found on the [releases page](https://github.c * Fix possible race conditions for canvas capture * Improvements to `fyne get` command downloader * Fix tree, so it refreshes visible nodes on Refresh() +* Incorrect clipping behaviour with nested scroll containers (#1682) ## 1.4.2 - 9 December 2020 diff --git a/go.mod b/go.mod index 58fb75270f..76ff9402a4 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/akavel/rsrc v0.8.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.4.9 - github.com/fyne-io/mobile v0.1.2-0.20201211141148-290e90fd7a4f + github.com/fyne-io/mobile v0.1.2 github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3 github.com/godbus/dbus/v5 v5.0.3 diff --git a/go.sum b/go.sum index a851055b56..95ac3751fb 100644 --- a/go.sum +++ b/go.sum @@ -10,8 +10,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d h1:WfVxpuVm+5Gr3ipAoWrxV8lJFYkaBWoEwFRrWThWRSU= github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/fyne-io/mobile v0.1.2-0.20201211141148-290e90fd7a4f h1:bpfV54yP8w21/AjChjuwYwRSsVt28EdG6ase0XJ1epc= -github.com/fyne-io/mobile v0.1.2-0.20201211141148-290e90fd7a4f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= +github.com/fyne-io/mobile v0.1.2 h1:0HaXDtOOwyOTn3Umi0uKVCOgJtfX73c6unC4U8i5VZU= +github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= diff --git a/internal/clip.go b/internal/clip.go new file mode 100644 index 0000000000..66bb0310a6 --- /dev/null +++ b/internal/clip.go @@ -0,0 +1,81 @@ +package internal + +import "fyne.io/fyne" + +// ClipStack keeps track of the areas that should be clipped when drawing a canvas. +// If no clips are present then adding one will be added as-is. +// Subsequent items pushed will be completely within the previous clip. +type ClipStack struct { + clips []*ClipItem +} + +// Pop removes the current top clip and returns it. +func (c *ClipStack) Pop() *ClipItem { + if len(c.clips) == 0 { + return nil + } + + ret := c.clips[len(c.clips)-1] + c.clips = c.clips[:len(c.clips)-1] + return ret +} + +// Length returns the number of items in this clip stack. 0 means no clip. +func (c *ClipStack) Length() int { + return len(c.clips) +} + +// Push a new clip onto this stack at position and size specified. +// The returned clip item is the result of calculating the intersection of the requested clip and it's parent. +func (c *ClipStack) Push(p fyne.Position, s fyne.Size) *ClipItem { + outer := c.Top() + inner := outer.Intersect(p, s) + + c.clips = append(c.clips, inner) + return inner +} + +// Top returns the current clip item - it will always be within the bounds of any parent clips. +func (c *ClipStack) Top() *ClipItem { + if len(c.clips) == 0 { + return nil + } + + return c.clips[len(c.clips)-1] +} + +// ClipItem represents a single clip in a clip stack, denoted by a size and position. +type ClipItem struct { + pos fyne.Position + size fyne.Size +} + +// Rect returns the position and size parameters of the clip. +func (i *ClipItem) Rect() (fyne.Position, fyne.Size) { + return i.pos, i.size +} + +// Intersect returns a new clip item that is the intersection of the requested parameters and this clip. +func (i *ClipItem) Intersect(p fyne.Position, s fyne.Size) *ClipItem { + ret := &ClipItem{p, s} + if i == nil { + return ret + } + + if ret.pos.X < i.pos.X { + ret.pos.X = i.pos.X + ret.size.Width -= i.pos.X - p.X + } + if ret.pos.Y < i.pos.Y { + ret.pos.Y = i.pos.Y + ret.size.Height -= i.pos.Y - p.Y + } + + if p.X+s.Width > i.pos.X+i.size.Width { + ret.size.Width = (i.pos.X + i.size.Width) - ret.pos.X + } + if p.Y+s.Height > i.pos.Y+i.size.Height { + ret.size.Height = (i.pos.Y + i.size.Height) - ret.pos.Y + } + return ret +} diff --git a/internal/clip_test.go b/internal/clip_test.go new file mode 100644 index 0000000000..3eca38a238 --- /dev/null +++ b/internal/clip_test.go @@ -0,0 +1,62 @@ +package internal + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "fyne.io/fyne" +) + +func TestClipStack_Intersect(t *testing.T) { + p1 := fyne.NewPos(5, 25) + s1 := fyne.NewSize(100, 100) + c := &ClipStack{ + clips: []*ClipItem{ + {p1, s1}, + }, + } + + p2 := fyne.NewPos(25, 0) + s2 := fyne.NewSize(50, 50) + i := c.Push(p2, s2) + + assert.Equal(t, fyne.NewPos(25, 25), i.pos) + assert.Equal(t, fyne.NewSize(50, 25), i.size) + assert.Equal(t, 2, len(c.clips)) + + _ = c.Pop() + p2 = fyne.NewPos(50, 50) + s2 = fyne.NewSize(150, 50) + i = c.Push(p2, s2) + + assert.Equal(t, fyne.NewPos(50, 50), i.pos) + assert.Equal(t, fyne.NewSize(55, 50), i.size) + assert.Equal(t, 2, len(c.clips)) +} + +func TestClipStack_Pop(t *testing.T) { + p := fyne.NewPos(5, 5) + s := fyne.NewSize(100, 100) + c := &ClipStack{ + clips: []*ClipItem{ + {p, s}, + }, + } + + i := c.Pop() + assert.Equal(t, p, i.pos) + assert.Equal(t, s, i.size) + assert.Equal(t, 0, len(c.clips)) +} + +func TestClipStack_Push(t *testing.T) { + c := &ClipStack{} + p := fyne.NewPos(5, 5) + s := fyne.NewSize(100, 100) + + i := c.Push(p, s) + assert.Equal(t, p, i.pos) + assert.Equal(t, s, i.size) + assert.Equal(t, 1, len(c.clips)) +} diff --git a/internal/driver/glfw/canvas.go b/internal/driver/glfw/canvas.go index cd201cf5a2..abce5a7f18 100644 --- a/internal/driver/glfw/canvas.go +++ b/internal/driver/glfw/canvas.go @@ -405,6 +405,7 @@ func (c *glCanvas) overlayChanged() { } func (c *glCanvas) paint(size fyne.Size) { + clips := &internal.ClipStack{} if c.Content() == nil { return } @@ -413,18 +414,21 @@ func (c *glCanvas) paint(size fyne.Size) { paint := func(node *renderCacheNode, pos fyne.Position) { obj := node.obj - // TODO should this be somehow not scroll container specific? - if _, ok := obj.(*widget.ScrollContainer); ok { - c.painter.StartClipping( - fyne.NewPos(pos.X, c.Size().Height-pos.Y-obj.Size().Height), - obj.Size(), - ) + if _, ok := obj.(fyne.Scrollable); ok { + inner := clips.Push(pos, obj.Size()) + c.painter.StartClipping(inner.Rect()) } c.painter.Paint(obj, pos, size) } afterPaint := func(node *renderCacheNode) { - if _, ok := node.obj.(*widget.ScrollContainer); ok { - c.painter.StopClipping() + if _, ok := node.obj.(fyne.Scrollable); ok { + clips.Pop() + if top := clips.Top(); top != nil { + c.painter.StartClipping(top.Rect()) + } else { + c.painter.StopClipping() + + } } } diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 0642d0d7ec..a070c66c5e 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -68,7 +68,7 @@ func (c *mobileCanvas) InteractiveArea() (fyne.Position, fyne.Size) { func (c *mobileCanvas) SetContent(content fyne.CanvasObject) { c.content = content - c.sizeContent(c.Size().Max(content.MinSize())) + c.sizeContent(c.Size()) // fixed window size for mobile, cannot stretch to new content } // Deprecated: Use Overlays() instead. diff --git a/internal/driver/gomobile/canvas_test.go b/internal/driver/gomobile/canvas_test.go index 3ffe6a5527..8f1290766f 100644 --- a/internal/driver/gomobile/canvas_test.go +++ b/internal/driver/gomobile/canvas_test.go @@ -251,6 +251,7 @@ func TestCanvas_Focusable(t *testing.T) { content := newFocusableEntry() c := NewCanvas().(*mobileCanvas) c.SetContent(content) + c.resize(fyne.NewSize(25, 25)) c.tapDown(fyne.NewPos(10, 10), 0) assert.Equal(t, 1, content.focusedTimes) diff --git a/internal/driver/gomobile/driver.go b/internal/driver/gomobile/driver.go index fea8869b49..5640f74a2f 100644 --- a/internal/driver/gomobile/driver.go +++ b/internal/driver/gomobile/driver.go @@ -5,15 +5,6 @@ import ( "strconv" "time" - "fyne.io/fyne" - "fyne.io/fyne/canvas" - "fyne.io/fyne/internal" - "fyne.io/fyne/internal/driver" - "fyne.io/fyne/internal/painter" - pgl "fyne.io/fyne/internal/painter/gl" - "fyne.io/fyne/theme" - "fyne.io/fyne/widget" - "github.com/fyne-io/mobile/app" "github.com/fyne-io/mobile/event/key" "github.com/fyne-io/mobile/event/lifecycle" @@ -21,6 +12,14 @@ import ( "github.com/fyne-io/mobile/event/size" "github.com/fyne-io/mobile/event/touch" "github.com/fyne-io/mobile/gl" + + "fyne.io/fyne" + "fyne.io/fyne/canvas" + "fyne.io/fyne/internal" + "fyne.io/fyne/internal/driver" + "fyne.io/fyne/internal/painter" + pgl "fyne.io/fyne/internal/painter/gl" + "fyne.io/fyne/theme" ) const tapSecondaryDelay = 300 * time.Millisecond @@ -186,6 +185,7 @@ func (d *mobileDriver) onStop() { } func (d *mobileDriver) paintWindow(window fyne.Window, size fyne.Size) { + clips := &internal.ClipStack{} canvas := window.Canvas().(*mobileCanvas) r, g, b, a := theme.BackgroundColor().RGBA() @@ -194,19 +194,20 @@ func (d *mobileDriver) paintWindow(window fyne.Window, size fyne.Size) { d.glctx.Clear(gl.COLOR_BUFFER_BIT) paint := func(obj fyne.CanvasObject, pos fyne.Position, _ fyne.Position, _ fyne.Size) bool { - // TODO should this be somehow not scroll container specific? - if _, ok := obj.(*widget.ScrollContainer); ok { - canvas.painter.StartClipping( - fyne.NewPos(pos.X, canvas.Size().Height-pos.Y-obj.Size().Height), - obj.Size(), - ) + if _, ok := obj.(fyne.Scrollable); ok { + inner := clips.Push(pos, obj.Size()) + canvas.painter.StartClipping(inner.Rect()) } canvas.painter.Paint(obj, pos, size) return false } afterPaint := func(obj, _ fyne.CanvasObject) { - if _, ok := obj.(*widget.ScrollContainer); ok { + if _, ok := obj.(fyne.Scrollable); ok { canvas.painter.StopClipping() + clips.Pop() + if top := clips.Top(); top != nil { + canvas.painter.StartClipping(top.Rect()) + } } } diff --git a/internal/painter/gl/painter.go b/internal/painter/gl/painter.go index 7cb065e47b..e62ac2a939 100644 --- a/internal/painter/gl/painter.go +++ b/internal/painter/gl/painter.go @@ -53,7 +53,7 @@ func (p *glPainter) Clear() { func (p *glPainter) StartClipping(pos fyne.Position, size fyne.Size) { x := p.textureScaleInt(pos.X) - y := p.textureScaleInt(pos.Y) + y := p.textureScaleInt(p.canvas.Size().Height - pos.Y - size.Height) w := p.textureScaleInt(size.Width) h := p.textureScaleInt(size.Height) p.glScissorOpen(int32(x), int32(y), int32(w), int32(h)) diff --git a/vendor/github.com/fyne-io/mobile/app/darwin_ios.go b/vendor/github.com/fyne-io/mobile/app/darwin_ios.go index 689dd7cc0d..eb65c66bc6 100644 --- a/vendor/github.com/fyne-io/mobile/app/darwin_ios.go +++ b/vendor/github.com/fyne-io/mobile/app/darwin_ios.go @@ -83,8 +83,8 @@ var DisplayMetrics struct { //export setDisplayMetrics func setDisplayMetrics(width, height int, scale int) { - DisplayMetrics.WidthPx = width * scale - DisplayMetrics.HeightPx = height * scale + DisplayMetrics.WidthPx = width + DisplayMetrics.HeightPx = height } //export setScreen @@ -126,16 +126,15 @@ func updateConfig(width, height, orientation int32) { o = size.OrientationPortrait case C.UIDeviceOrientationLandscapeLeft, C.UIDeviceOrientationLandscapeRight: o = size.OrientationLandscape + width, height = height, width } - widthPx := screenScale * int(width) - heightPx := screenScale * int(height) insets := C.getDevicePadding() theApp.eventsIn <- size.Event{ - WidthPx: widthPx, - HeightPx: heightPx, - WidthPt: geom.Pt(float32(widthPx) / pixelsPerPt), - HeightPt: geom.Pt(float32(heightPx) / pixelsPerPt), + WidthPx: int(width), + HeightPx: int(height), + WidthPt: geom.Pt(float32(width) / pixelsPerPt), + HeightPt: geom.Pt(float32(height) / pixelsPerPt), InsetTopPx: int(float32(insets.top) * float32(screenScale)), InsetBottomPx: int(float32(insets.bottom) * float32(screenScale)), InsetLeftPx: int(float32(insets.left) * float32(screenScale)), diff --git a/vendor/github.com/fyne-io/mobile/app/darwin_ios.m b/vendor/github.com/fyne-io/mobile/app/darwin_ios.m index c2c3bdbe79..7e9441a4a5 100644 --- a/vendor/github.com/fyne-io/mobile/app/darwin_ios.m +++ b/vendor/github.com/fyne-io/mobile/app/darwin_ios.m @@ -34,7 +34,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( if ([[UIScreen mainScreen] respondsToSelector:@selector(displayLinkWithTarget:selector:)]) { scale = (int)[UIScreen mainScreen].scale; // either 1.0, 2.0, or 3.0. } - CGSize size = [UIScreen mainScreen].bounds.size; + CGSize size = [UIScreen mainScreen].nativeBounds.size; setDisplayMetrics((int)size.width, (int)size.height, scale); lifecycleAlive(); @@ -135,7 +135,7 @@ - (void)viewDidLoad { } setScreen(scale); - CGSize size = [UIScreen mainScreen].bounds.size; + CGSize size = [UIScreen mainScreen].nativeBounds.size; UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; updateConfig((int)size.width, (int)size.height, orientation); @@ -144,11 +144,12 @@ - (void)viewDidLoad { [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; } -- (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { +- (void)viewWillTransitionToSize:(CGSize)ptSize withTransitionCoordinator:(id)coordinator { [coordinator animateAlongsideTransition:^(id context) { // TODO(crawshaw): come up with a plan to handle animations. } completion:^(id context) { UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; + CGSize size = [UIScreen mainScreen].nativeBounds.size; updateConfig((int)size.width, (int)size.height, orientation); }]; } diff --git a/vendor/modules.txt b/vendor/modules.txt index ceb2125bbe..9d2e3f48fe 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,7 +8,7 @@ github.com/akavel/rsrc/ico github.com/davecgh/go-spew/spew # github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify -# github.com/fyne-io/mobile v0.1.2-0.20201211141148-290e90fd7a4f +# github.com/fyne-io/mobile v0.1.2 github.com/fyne-io/mobile/app github.com/fyne-io/mobile/app/internal/callfn github.com/fyne-io/mobile/event/key