fckeuspy-go/vendor/fyne.io/fyne/v2/internal/driver/mobile/driver.go

667 lines
17 KiB
Go

package mobile
import (
"math"
"runtime"
"strconv"
"time"
"fyne.io/fyne/v2"
fynecanvas "fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/driver/mobile"
"fyne.io/fyne/v2/internal"
"fyne.io/fyne/v2/internal/animation"
intapp "fyne.io/fyne/v2/internal/app"
"fyne.io/fyne/v2/internal/async"
"fyne.io/fyne/v2/internal/build"
"fyne.io/fyne/v2/internal/cache"
intdriver "fyne.io/fyne/v2/internal/driver"
"fyne.io/fyne/v2/internal/driver/common"
"fyne.io/fyne/v2/internal/driver/mobile/app"
"fyne.io/fyne/v2/internal/driver/mobile/event/key"
"fyne.io/fyne/v2/internal/driver/mobile/event/lifecycle"
"fyne.io/fyne/v2/internal/driver/mobile/event/paint"
"fyne.io/fyne/v2/internal/driver/mobile/event/size"
"fyne.io/fyne/v2/internal/driver/mobile/event/touch"
"fyne.io/fyne/v2/internal/driver/mobile/gl"
"fyne.io/fyne/v2/internal/painter"
pgl "fyne.io/fyne/v2/internal/painter/gl"
"fyne.io/fyne/v2/internal/scale"
"fyne.io/fyne/v2/theme"
)
const (
tapMoveDecay = 0.92 // how much should the scroll continue decay on each frame?
tapMoveEndThreshold = 2.0 // at what offset will we stop decaying?
tapMoveThreshold = 4.0 // how far can we move before it is a drag
tapSecondaryDelay = 300 * time.Millisecond // how long before secondary tap
tapDoubleDelay = 500 * time.Millisecond // max duration between taps for a DoubleTap event
)
// Configuration is the system information about the current device
type Configuration struct {
SystemTheme fyne.ThemeVariant
}
// ConfiguredDriver is a simple type that allows packages to hook into configuration changes of this driver.
type ConfiguredDriver interface {
SetOnConfigurationChanged(func(*Configuration))
}
type driver struct {
app app.App
glctx gl.Context
windows []fyne.Window
device device
animation animation.Runner
currentSize size.Event
theme fyne.ThemeVariant
onConfigChanged func(*Configuration)
painting bool
running bool
queuedFuncs *async.UnboundedChan[func()]
}
// Declare conformity with Driver
var _ fyne.Driver = (*driver)(nil)
var _ ConfiguredDriver = (*driver)(nil)
func init() {
runtime.LockOSThread()
}
func (d *driver) DoFromGoroutine(fn func(), wait bool) {
caller := func() {
if d.queuedFuncs == nil {
fn() // before the app actually starts
return
}
var done chan struct{}
if wait {
done = common.DonePool.Get()
defer common.DonePool.Put(done)
}
d.queuedFuncs.In() <- func() {
fn()
if wait {
done <- struct{}{}
}
}
if wait {
<-done
}
}
if wait {
async.EnsureNotMain(caller)
} else {
caller()
}
}
func (d *driver) CreateWindow(title string) fyne.Window {
c := newCanvas(fyne.CurrentDevice()).(*canvas) // silence lint
ret := &window{title: title, canvas: c, isChild: len(d.windows) > 0}
c.setContent(&fynecanvas.Rectangle{FillColor: theme.Color(theme.ColorNameBackground)})
c.SetPainter(pgl.NewPainter(c, ret))
d.windows = append(d.windows, ret)
return ret
}
func (d *driver) AllWindows() []fyne.Window {
return d.windows
}
// currentWindow returns the most recently opened window - we can only show one at a time.
func (d *driver) currentWindow() *window {
if len(d.windows) == 0 {
return nil
}
var last *window
for i := len(d.windows) - 1; i >= 0; i-- {
last = d.windows[i].(*window)
if last.visible {
return last
}
}
return last
}
func (d *driver) Clipboard() fyne.Clipboard {
return NewClipboard()
}
func (d *driver) RenderedTextSize(text string, textSize float32, style fyne.TextStyle, source fyne.Resource) (size fyne.Size, baseline float32) {
return painter.RenderedTextSize(text, textSize, style, source)
}
func (d *driver) CanvasForObject(obj fyne.CanvasObject) fyne.Canvas {
if len(d.windows) == 0 {
return nil
}
// TODO figure out how we handle multiple windows...
return d.currentWindow().Canvas()
}
func (d *driver) AbsolutePositionForObject(co fyne.CanvasObject) fyne.Position {
c := d.CanvasForObject(co)
if c == nil {
return fyne.NewPos(0, 0)
}
mc := c.(*canvas)
pos := intdriver.AbsolutePositionForObject(co, mc.ObjectTrees())
inset, _ := c.InteractiveArea()
return pos.Subtract(inset)
}
func (d *driver) GoBack() {
app.GoBack()
}
func (d *driver) Quit() {
// Android and iOS guidelines say this should not be allowed!
}
func (d *driver) Run() {
if d.running {
return // Run was called twice.
}
d.running = true
app.Main(func(a app.App) {
async.SetMainGoroutine()
d.app = a
d.queuedFuncs = async.NewUnboundedChan[func()]()
fyne.CurrentApp().Settings().AddListener(func(s fyne.Settings) {
painter.ClearFontCache()
cache.ResetThemeCaches()
intapp.ApplySettingsWithCallback(s, fyne.CurrentApp(), func(w fyne.Window) {
c, ok := w.Canvas().(*canvas)
if !ok {
return
}
c.applyThemeOutOfTreeObjects()
})
})
draw := time.NewTicker(time.Second / 60)
defer func() {
l := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle)
// exhaust the event queue
go func() {
l.WaitForEvents()
d.queuedFuncs.Close()
}()
for fn := range d.queuedFuncs.Out() {
fn()
}
l.DestroyEventQueue()
}()
for {
select {
case <-draw.C:
d.sendPaintEvent()
case fn := <-d.queuedFuncs.Out():
fn()
case e, ok := <-a.Events():
if !ok {
return // events channel closed, app done
}
current := d.currentWindow()
if current == nil {
continue
}
c := current.Canvas().(*canvas)
switch e := a.Filter(e).(type) {
case lifecycle.Event:
d.handleLifecycle(e, current)
case size.Event:
if e.WidthPx <= 0 {
continue
}
d.currentSize = e
currentOrientation = e.Orientation
currentDPI = e.PixelsPerPt * 72
d.setTheme(e.DarkMode)
dev := &d.device
insetChange := dev.safeTop != e.InsetTopPx || dev.safeBottom != e.InsetBottomPx ||
dev.safeLeft != e.InsetLeftPx || dev.safeRight != e.InsetRightPx
dev.safeTop = e.InsetTopPx
dev.safeLeft = e.InsetLeftPx
dev.safeBottom = e.InsetBottomPx
dev.safeRight = e.InsetRightPx
c.scale = fyne.CurrentDevice().SystemScaleForWindow(nil)
c.Painter().SetFrameBufferScale(1.0)
if insetChange {
current.canvas.sizeContent(current.canvas.size) // even if size didn't change we invalidate
}
// make sure that we paint on the next frame
c.Content().Refresh()
case paint.Event:
d.handlePaint(e, current)
case touch.Event:
switch e.Type {
case touch.TypeBegin:
d.tapDownCanvas(current, e.X, e.Y, e.Sequence)
case touch.TypeMove:
d.tapMoveCanvas(current, e.X, e.Y, e.Sequence)
case touch.TypeEnd:
d.tapUpCanvas(current, e.X, e.Y, e.Sequence)
}
case key.Event:
if runtime.GOOS == "android" && e.Code == key.CodeDeleteBackspace && e.Rune < 0 && d.device.keyboardShown {
break // we are getting release/press on backspace during soft backspace
}
if e.Direction == key.DirPress {
d.typeDownCanvas(c, e.Rune, e.Code, e.Modifiers)
} else if e.Direction == key.DirRelease {
d.typeUpCanvas(c, e.Rune, e.Code, e.Modifiers)
}
}
}
}
})
}
func (*driver) SetDisableScreenBlanking(disable bool) {
setDisableScreenBlank(disable)
}
func (d *driver) handleLifecycle(e lifecycle.Event, w *window) {
c := w.Canvas().(*canvas)
switch e.Crosses(lifecycle.StageVisible) {
case lifecycle.CrossOn:
d.glctx, _ = e.DrawContext.(gl.Context)
d.onStart()
// this is a fix for some android phone to prevent the app from being drawn as a blank screen after being pushed in the background
c.Content().Refresh()
d.sendPaintEvent()
case lifecycle.CrossOff:
d.onStop()
d.glctx = nil
}
switch e.Crosses(lifecycle.StageFocused) {
case lifecycle.CrossOn: // foregrounding
if f := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).OnEnteredForeground(); f != nil {
f()
}
case lifecycle.CrossOff: // will enter background
if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
if d.glctx == nil {
return
}
s := fyne.NewSize(float32(d.currentSize.WidthPx)/c.scale, float32(d.currentSize.HeightPx)/c.scale)
d.paintWindow(w, s)
d.app.Publish()
}
if f := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).OnExitedForeground(); f != nil {
f()
}
}
}
func (d *driver) handlePaint(e paint.Event, w *window) {
c := w.Canvas().(*canvas)
if e.Window != 0 { // not all paint events come from hardware
w.handle = e.Window
}
d.painting = false
if d.glctx == nil || e.External {
return
}
if !c.initialized {
c.initialized = true
c.Painter().Init() // we cannot init until the context is set above
}
d.animation.TickAnimations()
canvasNeedRefresh := c.FreeDirtyTextures() > 0 || c.CheckDirtyAndClear()
if canvasNeedRefresh {
newSize := fyne.NewSize(float32(d.currentSize.WidthPx)/c.scale, float32(d.currentSize.HeightPx)/c.scale)
if c.EnsureMinSize() {
c.sizeContent(newSize) // force resize of content
} else { // if screen changed
w.Resize(newSize)
}
d.paintWindow(w, newSize)
d.app.Publish()
}
cache.Clean(canvasNeedRefresh)
}
func (d *driver) onStart() {
if f := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle).OnStarted(); f != nil {
f()
}
}
func (d *driver) onStop() {
l := fyne.CurrentApp().Lifecycle().(*intapp.Lifecycle)
if f := l.OnStopped(); f != nil {
l.QueueEvent(f)
}
}
func (d *driver) paintWindow(window fyne.Window, size fyne.Size) {
clips := &internal.ClipStack{}
c := window.Canvas().(*canvas)
r, g, b, a := theme.Color(theme.ColorNameBackground).RGBA()
max16bit := float32(255 * 255)
d.glctx.ClearColor(float32(r)/max16bit, float32(g)/max16bit, float32(b)/max16bit, float32(a)/max16bit)
d.glctx.Clear(gl.ColorBufferBit)
draw := func(node *common.RenderCacheNode, pos fyne.Position) {
obj := node.Obj()
if _, ok := obj.(fyne.Scrollable); ok {
inner := clips.Push(pos, obj.Size())
c.Painter().StartClipping(inner.Rect())
}
if size.Width <= 0 || size.Height <= 0 { // iconifying on Windows can do bad things
return
}
c.Painter().Paint(obj, pos, size)
}
afterDraw := func(node *common.RenderCacheNode, pos fyne.Position) {
if _, ok := node.Obj().(fyne.Scrollable); ok {
c.Painter().StopClipping()
clips.Pop()
if top := clips.Top(); top != nil {
c.Painter().StartClipping(top.Rect())
}
}
if build.Mode == fyne.BuildDebug {
c.DrawDebugOverlay(node.Obj(), pos, size)
}
}
c.WalkTrees(draw, afterDraw)
}
func (d *driver) sendPaintEvent() {
if d.painting {
return
}
d.app.Send(paint.Event{})
d.painting = true
}
func (d *driver) setTheme(dark bool) {
var mode fyne.ThemeVariant
if dark {
mode = theme.VariantDark
} else {
mode = theme.VariantLight
}
if d.theme != mode && d.onConfigChanged != nil {
d.onConfigChanged(&Configuration{SystemTheme: mode})
}
d.theme = mode
}
func (d *driver) tapDownCanvas(w *window, x, y float32, tapID touch.Sequence) {
tapX := scale.ToFyneCoordinate(w.canvas, int(x))
tapY := scale.ToFyneCoordinate(w.canvas, int(y))
pos := fyne.NewPos(tapX, tapY+tapYOffset)
w.canvas.tapDown(pos, int(tapID))
}
func (d *driver) tapMoveCanvas(w *window, x, y float32, tapID touch.Sequence) {
tapX := scale.ToFyneCoordinate(w.canvas, int(x))
tapY := scale.ToFyneCoordinate(w.canvas, int(y))
pos := fyne.NewPos(tapX, tapY+tapYOffset)
w.canvas.tapMove(pos, int(tapID), func(wid fyne.Draggable, ev *fyne.DragEvent) {
wid.Dragged(ev)
})
}
func (d *driver) tapUpCanvas(w *window, x, y float32, tapID touch.Sequence) {
tapX := scale.ToFyneCoordinate(w.canvas, int(x))
tapY := scale.ToFyneCoordinate(w.canvas, int(y))
pos := fyne.NewPos(tapX, tapY+tapYOffset)
w.canvas.tapUp(pos, int(tapID), func(wid fyne.Tappable, ev *fyne.PointEvent) {
wid.Tapped(ev)
}, func(wid fyne.SecondaryTappable, ev *fyne.PointEvent) {
wid.TappedSecondary(ev)
}, func(wid fyne.DoubleTappable, ev *fyne.PointEvent) {
wid.DoubleTapped(ev)
}, func(wid fyne.Draggable, ev *fyne.DragEvent) {
if math.Abs(float64(ev.Dragged.DX)) <= tapMoveEndThreshold && math.Abs(float64(ev.Dragged.DY)) <= tapMoveEndThreshold {
wid.DragEnd()
return
}
go func() {
for math.Abs(float64(ev.Dragged.DX)) > tapMoveEndThreshold || math.Abs(float64(ev.Dragged.DY)) > tapMoveEndThreshold {
if math.Abs(float64(ev.Dragged.DX)) > 0 {
ev.Dragged.DX *= tapMoveDecay
}
if math.Abs(float64(ev.Dragged.DY)) > 0 {
ev.Dragged.DY *= tapMoveDecay
}
d.DoFromGoroutine(func() {
wid.Dragged(ev)
}, true)
time.Sleep(time.Millisecond * 16)
}
d.DoFromGoroutine(wid.DragEnd, true)
}()
})
}
var keyCodeMap = map[key.Code]fyne.KeyName{
// non-printable
key.CodeEscape: fyne.KeyEscape,
key.CodeReturnEnter: fyne.KeyReturn,
key.CodeTab: fyne.KeyTab,
key.CodeDeleteBackspace: fyne.KeyBackspace,
key.CodeInsert: fyne.KeyInsert,
key.CodePageUp: fyne.KeyPageUp,
key.CodePageDown: fyne.KeyPageDown,
key.CodeHome: fyne.KeyHome,
key.CodeEnd: fyne.KeyEnd,
key.CodeLeftArrow: fyne.KeyLeft,
key.CodeRightArrow: fyne.KeyRight,
key.CodeUpArrow: fyne.KeyUp,
key.CodeDownArrow: fyne.KeyDown,
key.CodeF1: fyne.KeyF1,
key.CodeF2: fyne.KeyF2,
key.CodeF3: fyne.KeyF3,
key.CodeF4: fyne.KeyF4,
key.CodeF5: fyne.KeyF5,
key.CodeF6: fyne.KeyF6,
key.CodeF7: fyne.KeyF7,
key.CodeF8: fyne.KeyF8,
key.CodeF9: fyne.KeyF9,
key.CodeF10: fyne.KeyF10,
key.CodeF11: fyne.KeyF11,
key.CodeF12: fyne.KeyF12,
key.CodeKeypadEnter: fyne.KeyEnter,
// printable
key.CodeA: fyne.KeyA,
key.CodeB: fyne.KeyB,
key.CodeC: fyne.KeyC,
key.CodeD: fyne.KeyD,
key.CodeE: fyne.KeyE,
key.CodeF: fyne.KeyF,
key.CodeG: fyne.KeyG,
key.CodeH: fyne.KeyH,
key.CodeI: fyne.KeyI,
key.CodeJ: fyne.KeyJ,
key.CodeK: fyne.KeyK,
key.CodeL: fyne.KeyL,
key.CodeM: fyne.KeyM,
key.CodeN: fyne.KeyN,
key.CodeO: fyne.KeyO,
key.CodeP: fyne.KeyP,
key.CodeQ: fyne.KeyQ,
key.CodeR: fyne.KeyR,
key.CodeS: fyne.KeyS,
key.CodeT: fyne.KeyT,
key.CodeU: fyne.KeyU,
key.CodeV: fyne.KeyV,
key.CodeW: fyne.KeyW,
key.CodeX: fyne.KeyX,
key.CodeY: fyne.KeyY,
key.CodeZ: fyne.KeyZ,
key.Code0: fyne.Key0,
key.CodeKeypad0: fyne.Key0,
key.Code1: fyne.Key1,
key.CodeKeypad1: fyne.Key1,
key.Code2: fyne.Key2,
key.CodeKeypad2: fyne.Key2,
key.Code3: fyne.Key3,
key.CodeKeypad3: fyne.Key3,
key.Code4: fyne.Key4,
key.CodeKeypad4: fyne.Key4,
key.Code5: fyne.Key5,
key.CodeKeypad5: fyne.Key5,
key.Code6: fyne.Key6,
key.CodeKeypad6: fyne.Key6,
key.Code7: fyne.Key7,
key.CodeKeypad7: fyne.Key7,
key.Code8: fyne.Key8,
key.CodeKeypad8: fyne.Key8,
key.Code9: fyne.Key9,
key.CodeKeypad9: fyne.Key9,
key.CodeSemicolon: fyne.KeySemicolon,
key.CodeEqualSign: fyne.KeyEqual,
key.CodeSpacebar: fyne.KeySpace,
key.CodeApostrophe: fyne.KeyApostrophe,
key.CodeComma: fyne.KeyComma,
key.CodeHyphenMinus: fyne.KeyMinus,
key.CodeKeypadHyphenMinus: fyne.KeyMinus,
key.CodeFullStop: fyne.KeyPeriod,
key.CodeKeypadFullStop: fyne.KeyPeriod,
key.CodeSlash: fyne.KeySlash,
key.CodeLeftSquareBracket: fyne.KeyLeftBracket,
key.CodeBackslash: fyne.KeyBackslash,
key.CodeRightSquareBracket: fyne.KeyRightBracket,
key.CodeGraveAccent: fyne.KeyBackTick,
key.CodeBackButton: mobile.KeyBack,
}
func keyToName(code key.Code) fyne.KeyName {
ret, ok := keyCodeMap[code]
if !ok {
return ""
}
return ret
}
func runeToPrintable(r rune) rune {
if strconv.IsPrint(r) {
return r
}
return 0
}
func (d *driver) typeDownCanvas(canvas *canvas, r rune, code key.Code, mod key.Modifiers) {
keyName := keyToName(code)
switch keyName {
case fyne.KeyTab:
capture := false
if ent, ok := canvas.Focused().(fyne.Tabbable); ok {
capture = ent.AcceptsTab()
}
if !capture {
switch mod {
case 0:
canvas.FocusNext()
return
case key.ModShift:
canvas.FocusPrevious()
return
}
}
}
r = runeToPrintable(r)
keyEvent := &fyne.KeyEvent{Name: keyName}
if canvas.Focused() != nil {
if keyName != "" {
canvas.Focused().TypedKey(keyEvent)
}
if r > 0 {
canvas.Focused().TypedRune(r)
}
} else {
if keyName != "" {
if canvas.onTypedKey != nil {
canvas.onTypedKey(keyEvent)
} else if keyName == mobile.KeyBack {
d.GoBack()
}
}
if r > 0 && canvas.onTypedRune != nil {
canvas.onTypedRune(r)
}
}
}
func (d *driver) typeUpCanvas(_ *canvas, _ rune, _ key.Code, _ key.Modifiers) {
}
func (d *driver) Device() fyne.Device {
return &d.device
}
func (d *driver) SetOnConfigurationChanged(f func(*Configuration)) {
d.onConfigChanged = f
}
func (d *driver) DoubleTapDelay() time.Duration {
return tapDoubleDelay
}
// NewGoMobileDriver sets up a new Driver instance implemented using the Go
// Mobile extension and OpenGL bindings.
func NewGoMobileDriver() fyne.Driver {
d := &driver{
theme: fyne.ThemeVariant(2), // unspecified
}
registerRepository(d)
return d
}