From 88cb4751810ab5df6f4421e3681306e32ed17fe5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Sun, 10 Jan 2021 15:43:34 +0100 Subject: [PATCH 1/4] sort gomobile/canvas.go contents --- internal/driver/gomobile/canvas.go | 350 ++++++++++++++--------------- 1 file changed, 175 insertions(+), 175 deletions(-) diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 79ff4fa087..2140c7a8c7 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -17,6 +17,10 @@ import ( "fyne.io/fyne/widget" ) +const ( + doubleClickDelay = 500 // ms (maximum interval between clicks for double click detection) +) + type mobileCanvas struct { content fyne.CanvasObject windowHead, menu fyne.CanvasObject @@ -45,94 +49,32 @@ type mobileCanvas struct { touchLastTapped fyne.CanvasObject } -const ( - doubleClickDelay = 500 // ms (maximum interval between clicks for double click detection) -) - -func (c *mobileCanvas) Content() fyne.CanvasObject { - return c.content -} - -func (c *mobileCanvas) InteractiveArea() (fyne.Position, fyne.Size) { - scale := fyne.CurrentDevice().SystemScaleForWindow(nil) // we don't need a window parameter on mobile - - dev, ok := fyne.CurrentDevice().(*device) - if !ok || dev.safeWidth == 0 || dev.safeHeight == 0 { - return fyne.NewPos(0, 0), c.Size() // running in test mode - } - - return fyne.NewPos(float32(dev.safeLeft)/scale, float32(dev.safeTop)/scale), - fyne.NewSize(float32(dev.safeWidth)/scale, float32(dev.safeHeight)/scale) -} - -func (c *mobileCanvas) SetContent(content fyne.CanvasObject) { - c.content = content +// 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 = fyne.CurrentDevice().SystemScaleForWindow(nil) // we don't need a window parameter on mobile + ret.refreshQueue = make(chan fyne.CanvasObject, 1024) + ret.touched = make(map[int]mobile.Touchable) + ret.lastTapDownPos = make(map[int]fyne.Position) + ret.lastTapDown = make(map[int]time.Time) + ret.minSizeCache = make(map[fyne.CanvasObject]fyne.Size) + ret.overlays = &internal.OverlayStack{Canvas: ret} - c.sizeContent(c.Size()) // fixed window size for mobile, cannot stretch to new content -} + ret.setupThemeListener() -func (c *mobileCanvas) Overlays() fyne.OverlayStack { - return c.overlays + return ret } -func (c *mobileCanvas) Refresh(obj fyne.CanvasObject) { - select { - case c.refreshQueue <- obj: - // all good - default: - // queue is full, ignore - } +func (c *mobileCanvas) AddShortcut(shortcut fyne.Shortcut, handler func(shortcut fyne.Shortcut)) { + c.shortcut.AddShortcut(shortcut, handler) } -func (c *mobileCanvas) sizeContent(size fyne.Size) { - if c.content == nil { // window may not be configured yet - return - } - c.size = size - - offset := fyne.NewPos(0, 0) - areaPos, areaSize := c.InteractiveArea() - - if c.windowHead != nil { - topHeight := c.windowHead.MinSize().Height - - if len(c.windowHead.(*widget.Box).Children) > 1 { - c.windowHead.Resize(fyne.NewSize(areaSize.Width, topHeight)) - offset = fyne.NewPos(0, topHeight) - } else { - c.windowHead.Resize(c.windowHead.MinSize()) - } - c.windowHead.Move(areaPos) - } - - topLeft := areaPos.Add(offset) - for _, overlay := range c.overlays.List() { - 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) - } else { - overlay.Resize(areaSize) - overlay.Move(topLeft) - } - } - - if c.padded { - c.content.Resize(areaSize.Subtract(fyne.NewSize(theme.Padding()*2, theme.Padding()*2))) - c.content.Move(topLeft.Add(fyne.NewPos(theme.Padding(), theme.Padding()))) - } else { - c.content.Resize(areaSize) - c.content.Move(topLeft) - } +func (c *mobileCanvas) Capture() image.Image { + return c.painter.Capture(c) } -func (c *mobileCanvas) resize(size fyne.Size) { - if size == c.size { - return - } - - c.sizeContent(size) +func (c *mobileCanvas) Content() fyne.CanvasObject { + return c.content } func (c *mobileCanvas) Focus(obj fyne.Focusable) { @@ -159,79 +101,79 @@ func (c *mobileCanvas) Focus(obj fyne.Focusable) { } } -func (c *mobileCanvas) Unfocus() { - if c.focused != nil { - c.focused.FocusLost() - hideVirtualKeyboard() - } - c.focused = nil -} - func (c *mobileCanvas) Focused() fyne.Focusable { return c.focused } -func (c *mobileCanvas) Size() fyne.Size { - return c.size -} +func (c *mobileCanvas) InteractiveArea() (fyne.Position, fyne.Size) { + scale := fyne.CurrentDevice().SystemScaleForWindow(nil) // we don't need a window parameter on mobile -func (c *mobileCanvas) Scale() float32 { - return c.scale + dev, ok := fyne.CurrentDevice().(*device) + if !ok || dev.safeWidth == 0 || dev.safeHeight == 0 { + return fyne.NewPos(0, 0), c.Size() // running in test mode + } + + return fyne.NewPos(float32(dev.safeLeft)/scale, float32(dev.safeTop)/scale), + fyne.NewSize(float32(dev.safeWidth)/scale, float32(dev.safeHeight)/scale) } -func (c *mobileCanvas) PixelCoordinateForPosition(pos fyne.Position) (int, int) { - return int(float32(pos.X) * c.scale), int(float32(pos.Y) * c.scale) +func (c *mobileCanvas) OnTypedKey() func(*fyne.KeyEvent) { + return c.onTypedKey } func (c *mobileCanvas) OnTypedRune() func(rune) { return c.onTypedRune } -func (c *mobileCanvas) SetOnTypedRune(typed func(rune)) { - c.onTypedRune = typed -} - -func (c *mobileCanvas) OnTypedKey() func(*fyne.KeyEvent) { - return c.onTypedKey +func (c *mobileCanvas) Overlays() fyne.OverlayStack { + return c.overlays } -func (c *mobileCanvas) SetOnTypedKey(typed func(*fyne.KeyEvent)) { - c.onTypedKey = typed +func (c *mobileCanvas) PixelCoordinateForPosition(pos fyne.Position) (int, int) { + return int(float32(pos.X) * c.scale), int(float32(pos.Y) * c.scale) } -func (c *mobileCanvas) AddShortcut(shortcut fyne.Shortcut, handler func(shortcut fyne.Shortcut)) { - c.shortcut.AddShortcut(shortcut, handler) +func (c *mobileCanvas) Refresh(obj fyne.CanvasObject) { + select { + case c.refreshQueue <- obj: + // all good + default: + // queue is full, ignore + } } func (c *mobileCanvas) RemoveShortcut(shortcut fyne.Shortcut) { c.shortcut.RemoveShortcut(shortcut) } -func (c *mobileCanvas) Capture() image.Image { - return c.painter.Capture(c) +func (c *mobileCanvas) Scale() float32 { + return c.scale } -func (c *mobileCanvas) minSizeChanged() bool { - if c.Content() == nil { - return false - } - minSizeChange := false +func (c *mobileCanvas) SetContent(content fyne.CanvasObject) { + c.content = content - ensureMinSize := func(obj, parent fyne.CanvasObject) { - if !obj.Visible() { - return - } - minSize := obj.MinSize() + c.sizeContent(c.Size()) // fixed window size for mobile, cannot stretch to new content +} - if minSize != c.minSizeCache[obj] { - minSizeChange = true +func (c *mobileCanvas) SetOnTypedKey(typed func(*fyne.KeyEvent)) { + c.onTypedKey = typed +} - c.minSizeCache[obj] = minSize - } - } - c.walkTree(nil, ensureMinSize) +func (c *mobileCanvas) SetOnTypedRune(typed func(rune)) { + c.onTypedRune = typed +} - return minSizeChange +func (c *mobileCanvas) Size() fyne.Size { + return c.size +} + +func (c *mobileCanvas) Unfocus() { + if c.focused != nil { + c.focused.FocusLost() + hideVirtualKeyboard() + } + c.focused = nil } func (c *mobileCanvas) ensureMinSize() { @@ -270,15 +212,35 @@ func (c *mobileCanvas) ensureMinSize() { } } -func updateLayout(objToLayout fyne.CanvasObject) { - switch cont := objToLayout.(type) { - case *fyne.Container: - if cont.Layout != nil { - cont.Layout.Layout(cont.Objects, cont.Size()) +func (c *mobileCanvas) findObjectAtPositionMatching(pos fyne.Position, test func(object fyne.CanvasObject) bool) (fyne.CanvasObject, fyne.Position, int) { + if c.menu != nil { + return driver.FindObjectAtPositionMatching(pos, test, c.overlays.Top(), c.menu) + } + + return driver.FindObjectAtPositionMatching(pos, test, c.overlays.Top(), c.windowHead, c.content) +} + +func (c *mobileCanvas) minSizeChanged() bool { + if c.Content() == nil { + return false + } + minSizeChange := false + + ensureMinSize := func(obj, parent fyne.CanvasObject) { + if !obj.Visible() { + return + } + minSize := obj.MinSize() + + if minSize != c.minSizeCache[obj] { + minSizeChange = true + + c.minSizeCache[obj] = minSize } - case fyne.Widget: - cache.Renderer(cont).Layout(cont.Size()) } + c.walkTree(nil, ensureMinSize) + + return minSizeChange } func (c *mobileCanvas) objectTrees() []fyne.CanvasObject { @@ -291,30 +253,71 @@ func (c *mobileCanvas) objectTrees() []fyne.CanvasObject { return trees } -func (c *mobileCanvas) walkTree( - beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool, - afterChildren func(fyne.CanvasObject, fyne.CanvasObject), -) { - driver.WalkVisibleObjectTree(c.content, beforeChildren, afterChildren) - if c.windowHead != nil { - driver.WalkVisibleObjectTree(c.windowHead, beforeChildren, afterChildren) +func (c *mobileCanvas) resize(size fyne.Size) { + if size == c.size { + return } - if c.menu != nil { - driver.WalkVisibleObjectTree(c.menu, beforeChildren, afterChildren) + + c.sizeContent(size) +} + +func (c *mobileCanvas) setupThemeListener() { + listener := make(chan fyne.Settings) + fyne.CurrentApp().Settings().AddChangeListener(listener) + go func() { + for { + <-listener + if c.menu != nil { + app.ApplyThemeTo(c.menu, c) // Ensure our menu gets the theme change message as it's out-of-tree + } + if c.windowHead != nil { + app.ApplyThemeTo(c.windowHead, c) // Ensure our child windows get the theme change message as it's out-of-tree + } + } + }() +} + +func (c *mobileCanvas) sizeContent(size fyne.Size) { + if c.content == nil { // window may not be configured yet + return } - for _, overlay := range c.overlays.List() { - if overlay != nil { - driver.WalkVisibleObjectTree(overlay, beforeChildren, afterChildren) + c.size = size + + offset := fyne.NewPos(0, 0) + areaPos, areaSize := c.InteractiveArea() + + if c.windowHead != nil { + topHeight := c.windowHead.MinSize().Height + + if len(c.windowHead.(*widget.Box).Children) > 1 { + c.windowHead.Resize(fyne.NewSize(areaSize.Width, topHeight)) + offset = fyne.NewPos(0, topHeight) + } else { + c.windowHead.Resize(c.windowHead.MinSize()) } + c.windowHead.Move(areaPos) } -} -func (c *mobileCanvas) findObjectAtPositionMatching(pos fyne.Position, test func(object fyne.CanvasObject) bool) (fyne.CanvasObject, fyne.Position, int) { - if c.menu != nil { - return driver.FindObjectAtPositionMatching(pos, test, c.overlays.Top(), c.menu) + topLeft := areaPos.Add(offset) + for _, overlay := range c.overlays.List() { + 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) + } else { + overlay.Resize(areaSize) + overlay.Move(topLeft) + } } - return driver.FindObjectAtPositionMatching(pos, test, c.overlays.Top(), c.windowHead, c.content) + if c.padded { + c.content.Resize(areaSize.Subtract(fyne.NewSize(theme.Padding()*2, theme.Padding()*2))) + c.content.Move(topLeft.Add(fyne.NewPos(theme.Padding(), theme.Padding()))) + } else { + c.content.Resize(areaSize) + c.content.Move(topLeft) + } } func (c *mobileCanvas) tapDown(pos fyne.Position, tapID int) { @@ -484,34 +487,31 @@ func (c *mobileCanvas) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEven c.touchLastTapped = nil } -func (c *mobileCanvas) setupThemeListener() { - listener := make(chan fyne.Settings) - fyne.CurrentApp().Settings().AddChangeListener(listener) - go func() { - for { - <-listener - if c.menu != nil { - app.ApplyThemeTo(c.menu, c) // Ensure our menu gets the theme change message as it's out-of-tree - } - if c.windowHead != nil { - app.ApplyThemeTo(c.windowHead, c) // Ensure our child windows get the theme change message as it's out-of-tree - } +func (c *mobileCanvas) walkTree( + beforeChildren func(fyne.CanvasObject, fyne.Position, fyne.Position, fyne.Size) bool, + afterChildren func(fyne.CanvasObject, fyne.CanvasObject), +) { + driver.WalkVisibleObjectTree(c.content, beforeChildren, afterChildren) + if c.windowHead != nil { + driver.WalkVisibleObjectTree(c.windowHead, beforeChildren, afterChildren) + } + if c.menu != nil { + driver.WalkVisibleObjectTree(c.menu, beforeChildren, afterChildren) + } + for _, overlay := range c.overlays.List() { + if overlay != nil { + driver.WalkVisibleObjectTree(overlay, beforeChildren, afterChildren) } - }() + } } -// 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 = fyne.CurrentDevice().SystemScaleForWindow(nil) // we don't need a window parameter on mobile - ret.refreshQueue = make(chan fyne.CanvasObject, 1024) - ret.touched = make(map[int]mobile.Touchable) - ret.lastTapDownPos = make(map[int]fyne.Position) - ret.lastTapDown = make(map[int]time.Time) - ret.minSizeCache = make(map[fyne.CanvasObject]fyne.Size) - ret.overlays = &internal.OverlayStack{Canvas: ret} - - ret.setupThemeListener() - - return ret +func updateLayout(objToLayout fyne.CanvasObject) { + switch cont := objToLayout.(type) { + case *fyne.Container: + if cont.Layout != nil { + cont.Layout.Layout(cont.Objects, cont.Size()) + } + case fyne.Widget: + cache.Renderer(cont).Layout(cont.Size()) + } } From 36d0ad23997e1582c9d243467a3e4c2f9ea72c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Sun, 6 Dec 2020 16:53:12 +0100 Subject: [PATCH 2/4] add FocusNext() and FocusPrevious() to fyne.Canvas --- canvas.go | 12 ++++++++++++ internal/driver/gomobile/canvas.go | 10 ++++++++++ test/testcanvas.go | 2 -- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/canvas.go b/canvas.go index 39b74ad35e..a8c33e5a6b 100644 --- a/canvas.go +++ b/canvas.go @@ -13,6 +13,18 @@ type Canvas interface { // Focus makes the provided item focused. // The item has to be added to the contents of the canvas before calling this. Focus(Focusable) + // FocusNext focuses the next focusable item. + // If no item is currently focused, the first focusable item is focused. + // If the last focusable item is currently focused, the first focusable item is focused. + // + // Since 2.0.0 + FocusNext() + // FocusPrevious focuses the previous focusable item. + // If no item is currently focused, the last focusable item is focused. + // If the first focusable item is currently focused, the last focusable item is focused. + // + // Since 2.0.0 + FocusPrevious() Unfocus() Focused() Focusable diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 2140c7a8c7..3354089762 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -21,6 +21,8 @@ const ( doubleClickDelay = 500 // ms (maximum interval between clicks for double click detection) ) +var _ fyne.Canvas = (*mobileCanvas)(nil) + type mobileCanvas struct { content fyne.CanvasObject windowHead, menu fyne.CanvasObject @@ -101,6 +103,14 @@ func (c *mobileCanvas) Focus(obj fyne.Focusable) { } } +func (c *mobileCanvas) FocusNext() { + // not yet implemented, see #1625 +} + +func (c *mobileCanvas) FocusPrevious() { + // not yet implemented, see #1625 +} + func (c *mobileCanvas) Focused() fyne.Focusable { return c.focused } diff --git a/test/testcanvas.go b/test/testcanvas.go index b58f0e2ddc..d7e3d12c31 100644 --- a/test/testcanvas.go +++ b/test/testcanvas.go @@ -20,8 +20,6 @@ var ( type WindowlessCanvas interface { fyne.Canvas - FocusNext() - FocusPrevious() Padded() bool Resize(fyne.Size) SetPadded(bool) From 56b4d9746895c7661e31f2fc43f856f8bbf86cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Sun, 10 Jan 2021 21:08:00 +0100 Subject: [PATCH 3/4] internal.OverlayStack should not call change hook when locked --- internal/overlay_stack.go | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/internal/overlay_stack.go b/internal/overlay_stack.go index ca8c5ff848..9de588eb24 100644 --- a/internal/overlay_stack.go +++ b/internal/overlay_stack.go @@ -23,12 +23,16 @@ var _ fyne.OverlayStack = (*OverlayStack)(nil) // // Implements: fyne.OverlayStack func (s *OverlayStack) Add(overlay fyne.CanvasObject) { - s.propertyLock.Lock() - defer s.propertyLock.Unlock() - if overlay == nil { return } + + if s.OnChange != nil { + defer s.OnChange() + } + + s.propertyLock.Lock() + defer s.propertyLock.Unlock() s.overlays = append(s.overlays, overlay) // TODO this should probably apply to all once #707 is addressed @@ -40,9 +44,6 @@ func (s *OverlayStack) Add(overlay fyne.CanvasObject) { } s.focusManagers = append(s.focusManagers, app.NewFocusManager(overlay)) - if s.OnChange != nil { - s.OnChange() - } } // List returns all overlays on the stack from bottom to top. @@ -67,6 +68,10 @@ func (s *OverlayStack) ListFocusManagers() []*app.FocusManager { // // Implements: fyne.OverlayStack func (s *OverlayStack) Remove(overlay fyne.CanvasObject) { + if s.OnChange != nil { + defer s.OnChange() + } + s.propertyLock.Lock() defer s.propertyLock.Unlock() @@ -77,9 +82,6 @@ func (s *OverlayStack) Remove(overlay fyne.CanvasObject) { break } } - if s.OnChange != nil { - s.OnChange() - } } // Top returns the top-most overlay of the stack. From 3722d14facd976e72b13466705ca3697af7e500e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tilo=20Pr=C3=BCtz?= Date: Sun, 10 Jan 2021 17:37:22 +0100 Subject: [PATCH 4/4] add focus managers to mobile canvas --- internal/driver/gomobile/canvas.go | 76 ++++++++++++++++++++---------- internal/driver/gomobile/menu.go | 6 +-- 2 files changed, 54 insertions(+), 28 deletions(-) diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 3354089762..251c31c1be 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -25,13 +25,14 @@ var _ fyne.Canvas = (*mobileCanvas)(nil) type mobileCanvas struct { content fyne.CanvasObject + contentFocusMgr *app.FocusManager windowHead, menu fyne.CanvasObject + menuFocusMgr *app.FocusManager overlays *internal.OverlayStack painter gl.Painter scale float32 size fyne.Size - focused fyne.Focusable touched map[int]mobile.Touchable padded bool @@ -60,7 +61,7 @@ func NewCanvas() fyne.Canvas { ret.lastTapDownPos = make(map[int]fyne.Position) ret.lastTapDown = make(map[int]time.Time) ret.minSizeCache = make(map[fyne.CanvasObject]fyne.Size) - ret.overlays = &internal.OverlayStack{Canvas: ret} + ret.overlays = &internal.OverlayStack{Canvas: ret, OnChange: ret.overlayChanged} ret.setupThemeListener() @@ -80,39 +81,35 @@ func (c *mobileCanvas) Content() fyne.CanvasObject { } func (c *mobileCanvas) Focus(obj fyne.Focusable) { - if c.focused == obj || obj == nil { + focusMgr := c.focusManager() + if focusMgr.Focus(obj) { // fast path – probably >99.9% of all cases + c.handleKeyboard(obj) return } - if dis, ok := obj.(fyne.Disableable); ok && dis.Disabled() { - c.Unfocus() - return - } - - if c.focused != nil { - c.focused.FocusLost() + focusMgrs := append([]*app.FocusManager{c.contentFocusMgr, c.menuFocusMgr}, c.overlays.ListFocusManagers()...) + for _, mgr := range focusMgrs { + if focusMgr != mgr { + if mgr.Focus(obj) { + c.handleKeyboard(obj) + return + } + } } - c.focused = obj - obj.FocusGained() - - if keyb, ok := obj.(mobile.Keyboardable); ok { - showVirtualKeyboard(keyb.Keyboard()) - } else { - hideVirtualKeyboard() - } + fyne.LogError("Failed to focus object which is not part of the canvas’ content, menu or overlays.", nil) } func (c *mobileCanvas) FocusNext() { - // not yet implemented, see #1625 + c.focusManager().FocusNext() } func (c *mobileCanvas) FocusPrevious() { - // not yet implemented, see #1625 + c.focusManager().FocusPrevious() } func (c *mobileCanvas) Focused() fyne.Focusable { - return c.focused + return c.focusManager().Focused() } func (c *mobileCanvas) InteractiveArea() (fyne.Position, fyne.Size) { @@ -162,7 +159,7 @@ func (c *mobileCanvas) Scale() float32 { func (c *mobileCanvas) SetContent(content fyne.CanvasObject) { c.content = content - + c.contentFocusMgr = app.NewFocusManager(c.content) c.sizeContent(c.Size()) // fixed window size for mobile, cannot stretch to new content } @@ -179,11 +176,9 @@ func (c *mobileCanvas) Size() fyne.Size { } func (c *mobileCanvas) Unfocus() { - if c.focused != nil { - c.focused.FocusLost() + if c.focusManager().Focus(nil) { hideVirtualKeyboard() } - c.focused = nil } func (c *mobileCanvas) ensureMinSize() { @@ -230,6 +225,24 @@ func (c *mobileCanvas) findObjectAtPositionMatching(pos fyne.Position, test func return driver.FindObjectAtPositionMatching(pos, test, c.overlays.Top(), c.windowHead, c.content) } +func (c *mobileCanvas) focusManager() *app.FocusManager { + if focusMgr := c.overlays.TopFocusManager(); focusMgr != nil { + return focusMgr + } + if c.menu != nil { + return c.menuFocusMgr + } + return c.contentFocusMgr +} + +func (c *mobileCanvas) handleKeyboard(obj fyne.Focusable) { + if keyb, ok := obj.(mobile.Keyboardable); ok { + showVirtualKeyboard(keyb.Keyboard()) + } else { + hideVirtualKeyboard() + } +} + func (c *mobileCanvas) minSizeChanged() bool { if c.Content() == nil { return false @@ -263,6 +276,10 @@ func (c *mobileCanvas) objectTrees() []fyne.CanvasObject { return trees } +func (c *mobileCanvas) overlayChanged() { + c.handleKeyboard(c.Focused()) +} + func (c *mobileCanvas) resize(size fyne.Size) { if size == c.size { return @@ -271,6 +288,15 @@ func (c *mobileCanvas) resize(size fyne.Size) { c.sizeContent(size) } +func (c *mobileCanvas) setMenu(menu fyne.CanvasObject) { + c.menu = menu + if menu != nil { + c.menuFocusMgr = app.NewFocusManager(c.menu) + } else { + c.menuFocusMgr = nil + } +} + func (c *mobileCanvas) setupThemeListener() { listener := make(chan fyne.Settings) fyne.CurrentApp().Settings().AddChangeListener(listener) diff --git a/internal/driver/gomobile/menu.go b/internal/driver/gomobile/menu.go index 90fdd88d0a..3176a8fd3e 100644 --- a/internal/driver/gomobile/menu.go +++ b/internal/driver/gomobile/menu.go @@ -30,7 +30,7 @@ func (m *menuLabel) Tapped(*fyne.PointEvent) { menu.OnDismiss = func() { menuDismiss() m.bar.Hide() // dismiss the overlay menu bar - m.canvas.menu = nil + m.canvas.setMenu(nil) } } @@ -50,14 +50,14 @@ func (c *mobileCanvas) showMenu(menu *fyne.MainMenu) { var panel *widget.Box top := widget.NewHBox(widget.NewButtonWithIcon("", theme.CancelIcon(), func() { panel.Hide() - c.menu = nil + c.setMenu(nil) })) panel = widget.NewVBox(top) for _, item := range menu.Items { panel.Append(newMenuLabel(item, panel, c)) } shadow := canvas.NewHorizontalGradient(theme.ShadowColor(), color.Transparent) - c.menu = container.NewWithoutLayout(panel, shadow) + c.setMenu(container.NewWithoutLayout(panel, shadow)) safePos, safeSize := c.InteractiveArea() panel.Move(safePos)