From 0e479d58b5d4dbcc4220a68b80aa3d38e8afa1c5 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Thu, 1 Apr 2021 17:16:31 +0100 Subject: [PATCH 01/57] Fix new staticheck warning --- cmd/fyne/commands/release.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cmd/fyne/commands/release.go b/cmd/fyne/commands/release.go index 7950f5128b..39865edb25 100644 --- a/cmd/fyne/commands/release.go +++ b/cmd/fyne/commands/release.go @@ -172,11 +172,9 @@ func (r *releaser) packageIOSRelease() error { } func (r *releaser) packageMacOSRelease() error { - appCert := r.certificate // try to derive two certificates from one name (they will be consistent) - if strings.Contains(appCert, "Installer") { - appCert = strings.Replace(appCert, "Installer", "Application", 1) - } - installCert := strings.Replace(appCert, "Application", "Installer", 1) + // try to derive two certificates from one name (they will be consistent) + appCert := strings.Replace(r.certificate, "Installer", "Application", 1) + installCert := strings.Replace(r.certificate, "Application", "Installer", 1) unsignedPath := r.name + "-unsigned.pkg" defer os.RemoveAll(r.name + ".app") // this was the output of package and it can get in the way of future builds From d83384a6d6ba7ebf3773583d21f299154fe1b203 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Thu, 1 Apr 2021 17:34:50 +0100 Subject: [PATCH 02/57] Add icon parameter to get command --- cmd/fyne/commands/get.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/fyne/commands/get.go b/cmd/fyne/commands/get.go index 449afe260b..e475ccf70c 100644 --- a/cmd/fyne/commands/get.go +++ b/cmd/fyne/commands/get.go @@ -1,6 +1,7 @@ package commands import ( + "flag" "fmt" "os" "os/exec" @@ -33,6 +34,7 @@ func (g *Getter) Get(pkg string) error { // AddFlags adds available flags to the current flags parser func (g *Getter) AddFlags() { + flag.StringVar(&g.icon, "icon", "Icon.png", "The name of the application icon file") } // PrintHelp prints help for this command when used in a command-line context From ade74596bf215aeefe6d663152aa9d31a00f53ef Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sun, 4 Apr 2021 20:11:03 +0100 Subject: [PATCH 03/57] 10x faster TextGrid refresh :) Filling in the recreshCell code - will need a better approach if the cache access is blocked --- widget/textgrid.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/widget/textgrid.go b/widget/textgrid.go index 3788ee775f..c4e6075c26 100644 --- a/widget/textgrid.go +++ b/widget/textgrid.go @@ -2,6 +2,7 @@ package widget import ( "fmt" + "fyne.io/fyne/v2/internal/cache" "image/color" "math" "strings" @@ -296,8 +297,8 @@ func (t *TextGrid) ensureCells(row, col int) { } func (t *TextGrid) refreshCell(row, col int) { - // TODO trigger single cell refresh - t.Refresh() + r := cache.Renderer(t).(*textGridRenderer) + r.refreshCell(row, col) } // NewTextGrid creates a new empty TextGrid widget. @@ -331,6 +332,15 @@ func (t *textGridRenderer) appendTextCell(str rune) { t.objects = append(t.objects, bg, text) } +func (t *textGridRenderer) refreshCell(row, col int) { + pos := row*t.cols+col + text := t.objects[pos*2+1].(*canvas.Text) + rect := t.objects[pos*2].(*canvas.Rectangle) + + canvas.Refresh(text) + canvas.Refresh(rect) +} + func (t *textGridRenderer) setCellRune(str rune, pos int, style, rowStyle TextGridStyle) { if str == 0 { str = ' ' From 061f033a95315176dc2fd2f5a9d30a3e0238671f Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sun, 4 Apr 2021 20:58:02 +0100 Subject: [PATCH 04/57] Fix missed styling. Hooks were off, fixed --- widget/textgrid.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/widget/textgrid.go b/widget/textgrid.go index c4e6075c26..2c5f933dd2 100644 --- a/widget/textgrid.go +++ b/widget/textgrid.go @@ -2,11 +2,12 @@ package widget import ( "fmt" - "fyne.io/fyne/v2/internal/cache" "image/color" "math" "strings" + "fyne.io/fyne/v2/internal/cache" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/theme" @@ -333,7 +334,7 @@ func (t *textGridRenderer) appendTextCell(str rune) { } func (t *textGridRenderer) refreshCell(row, col int) { - pos := row*t.cols+col + pos := row*t.cols + col text := t.objects[pos*2+1].(*canvas.Text) rect := t.objects[pos*2].(*canvas.Rectangle) From 5b9ab47536e47de55887eea74e6540f1fa873aee Mon Sep 17 00:00:00 2001 From: Jacalz Date: Sun, 4 Apr 2021 16:52:39 +0200 Subject: [PATCH 05/57] Update godbus/dbus for FreeBSD 13 compilation fix Fyne can not be compiled on FreeBSD 13 without this change due to a missing include. Fixes #2130 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 7648888d0f..e54cf3e053 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 - github.com/godbus/dbus/v5 v5.0.3 + github.com/godbus/dbus/v5 v5.0.4 github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526 github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca diff --git a/go.sum b/go.sum index 74eb47698a..61900d05ce 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,8 @@ github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluN github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526 h1:NfuKjkj/Xc2z1xZIj+EmNCm5p1nKJPyw3F4E20usXvg= From 3ea95aaf56dea51e092552a8d526fb56998cafa9 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sun, 4 Apr 2021 20:59:23 +0100 Subject: [PATCH 06/57] Update vendor to match --- vendor/github.com/godbus/dbus/v5/.travis.yml | 50 ------ .../github.com/godbus/dbus/v5/README.markdown | 4 +- vendor/github.com/godbus/dbus/v5/auth.go | 2 +- vendor/github.com/godbus/dbus/v5/call.go | 9 + vendor/github.com/godbus/dbus/v5/conn.go | 159 ++++++++++++------ vendor/github.com/godbus/dbus/v5/dbus.go | 4 + .../godbus/dbus/v5/default_handler.go | 22 ++- vendor/github.com/godbus/dbus/v5/export.go | 61 +++++-- vendor/github.com/godbus/dbus/v5/match.go | 27 +++ vendor/github.com/godbus/dbus/v5/object.go | 65 ++----- vendor/github.com/godbus/dbus/v5/sequence.go | 24 +++ .../godbus/dbus/v5/sequential_handler.go | 125 ++++++++++++++ vendor/github.com/godbus/dbus/v5/sig.go | 2 +- .../dbus/v5/transport_unixcred_freebsd.go | 1 + vendor/github.com/godbus/dbus/v5/variant.go | 6 + vendor/modules.txt | 2 +- 16 files changed, 382 insertions(+), 181 deletions(-) delete mode 100644 vendor/github.com/godbus/dbus/v5/.travis.yml create mode 100644 vendor/github.com/godbus/dbus/v5/sequence.go create mode 100644 vendor/github.com/godbus/dbus/v5/sequential_handler.go diff --git a/vendor/github.com/godbus/dbus/v5/.travis.yml b/vendor/github.com/godbus/dbus/v5/.travis.yml deleted file mode 100644 index dd67672048..0000000000 --- a/vendor/github.com/godbus/dbus/v5/.travis.yml +++ /dev/null @@ -1,50 +0,0 @@ -dist: bionic -language: go -go_import_path: github.com/godbus/dbus - -go: - - 1.11.x - - 1.12.x - - 1.13.x - - tip - -matrix: - fast_finish: true - allow_failures: - - go: tip - -addons: - apt: - packages: - - dbus - - dbus-x11 - -before_install: - - export GO111MODULE=on - -script: - - go test -v -race -mod=readonly ./... # Run all the tests with the race detector enabled - - go vet ./... # go vet is the official Go static analyzer - -jobs: - include: - # The build matrix doesn't cover build stages, so manually expand - # the jobs with anchors - - &multiarch - stage: "Multiarch Test" - go: 1.11.x - env: TARGETS="386 arm arm64 ppc64le" - before_install: - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - script: - - | - set -e - for target in $TARGETS; do - printf "\e[1mRunning test suite under ${target}.\e[0m\n" - GOARCH="$target" go test -v ./... - printf "\n\n" - done - - <<: *multiarch - go: 1.12.x - - <<: *multiarch - go: 1.13.x diff --git a/vendor/github.com/godbus/dbus/v5/README.markdown b/vendor/github.com/godbus/dbus/v5/README.markdown index fd29648752..1fb2eacaa1 100644 --- a/vendor/github.com/godbus/dbus/v5/README.markdown +++ b/vendor/github.com/godbus/dbus/v5/README.markdown @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/godbus/dbus.svg?branch=master)](https://travis-ci.org/godbus/dbus) +![Build Status](https://github.com/godbus/dbus/workflows/Go/badge.svg) dbus ---- @@ -32,6 +32,8 @@ gives a short overview over the basic usage. #### Projects using godbus - [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library. - [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API. +- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players. +- [iwd](https://github.com/shibumi/iwd) go bindings for the internet wireless daemon "iwd". Please note that the API is considered unstable for now and may change without further notice. diff --git a/vendor/github.com/godbus/dbus/v5/auth.go b/vendor/github.com/godbus/dbus/v5/auth.go index 31abac629d..283487a0e3 100644 --- a/vendor/github.com/godbus/dbus/v5/auth.go +++ b/vendor/github.com/godbus/dbus/v5/auth.go @@ -37,7 +37,7 @@ const ( // Auth defines the behaviour of an authentication mechanism. type Auth interface { - // Return the name of the mechnism, the argument to the first AUTH command + // Return the name of the mechanism, the argument to the first AUTH command // and the next status. FirstData() (name, resp []byte, status AuthStatus) diff --git a/vendor/github.com/godbus/dbus/v5/call.go b/vendor/github.com/godbus/dbus/v5/call.go index 2cb189012e..b06b063580 100644 --- a/vendor/github.com/godbus/dbus/v5/call.go +++ b/vendor/github.com/godbus/dbus/v5/call.go @@ -24,6 +24,15 @@ type Call struct { // Holds the response once the call is done. Body []interface{} + // ResponseSequence stores the sequence number of the DBus message containing + // the call response (or error). This can be compared to the sequence number + // of other call responses and signals on this connection to determine their + // relative ordering on the underlying DBus connection. + // For errors, ResponseSequence is populated only if the error came from a + // DBusMessage that was received or if there was an error receiving. In case of + // failure to make the call, ResponseSequence will be NoSequence. + ResponseSequence Sequence + // tracks context and canceler ctx context.Context ctxCanceler context.CancelFunc diff --git a/vendor/github.com/godbus/dbus/v5/conn.go b/vendor/github.com/godbus/dbus/v5/conn.go index b55bc99c85..29fe018ad8 100644 --- a/vendor/github.com/godbus/dbus/v5/conn.go +++ b/vendor/github.com/godbus/dbus/v5/conn.go @@ -45,6 +45,7 @@ type Conn struct { serialGen SerialGenerator inInt Interceptor outInt Interceptor + auth []Auth names *nameTracker calls *callTracker @@ -59,7 +60,8 @@ type Conn struct { func SessionBus() (conn *Conn, err error) { sessionBusLck.Lock() defer sessionBusLck.Unlock() - if sessionBus != nil { + if sessionBus != nil && + sessionBus.Connected() { return sessionBus, nil } defer func() { @@ -67,19 +69,7 @@ func SessionBus() (conn *Conn, err error) { sessionBus = conn } }() - conn, err = SessionBusPrivate() - if err != nil { - return - } - if err = conn.Auth(nil); err != nil { - conn.Close() - conn = nil - return - } - if err = conn.Hello(); err != nil { - conn.Close() - conn = nil - } + conn, err = ConnectSessionBus() return } @@ -116,7 +106,8 @@ func SessionBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Co func SystemBus() (conn *Conn, err error) { systemBusLck.Lock() defer systemBusLck.Unlock() - if systemBus != nil { + if systemBus != nil && + systemBus.Connected() { return systemBus, nil } defer func() { @@ -124,20 +115,42 @@ func SystemBus() (conn *Conn, err error) { systemBus = conn } }() - conn, err = SystemBusPrivate() + conn, err = ConnectSystemBus() + return +} + +// ConnectSessionBus connects to the session bus. +func ConnectSessionBus(opts ...ConnOption) (*Conn, error) { + address, err := getSessionBusAddress() if err != nil { - return + return nil, err } - if err = conn.Auth(nil); err != nil { - conn.Close() - conn = nil - return + return Connect(address, opts...) +} + +// ConnectSystemBus connects to the system bus. +func ConnectSystemBus(opts ...ConnOption) (*Conn, error) { + return Connect(getSystemBusPlatformAddress(), opts...) +} + +// Connect connects to the given address. +// +// Returned connection is ready to use and doesn't require calling +// Auth and Hello methods to make it usable. +func Connect(address string, opts ...ConnOption) (*Conn, error) { + conn, err := Dial(address, opts...) + if err != nil { + return nil, err + } + if err = conn.Auth(conn.auth); err != nil { + _ = conn.Close() + return nil, err } if err = conn.Hello(); err != nil { - conn.Close() - conn = nil + _ = conn.Close() + return nil, err } - return + return conn, nil } // SystemBusPrivate returns a new private connection to the system bus. @@ -197,6 +210,14 @@ func WithSerialGenerator(gen SerialGenerator) ConnOption { } } +// WithAuth sets authentication methods for the auth conversation. +func WithAuth(methods ...Auth) ConnOption { + return func(conn *Conn) error { + conn.auth = methods + return nil + } +} + // Interceptor intercepts incoming and outgoing messages. type Interceptor func(msg *Message) @@ -309,6 +330,11 @@ func (conn *Conn) Context() context.Context { return conn.ctx } +// Connected returns whether conn is connected +func (conn *Conn) Connected() bool { + return conn.ctx.Err() == nil +} + // Eavesdrop causes conn to send all incoming messages to the given channel // without further processing. Method replies, errors and signals will not be // sent to the appropriate channels and method calls will not be handled. If nil @@ -342,8 +368,9 @@ func (conn *Conn) Hello() error { } // inWorker runs in an own goroutine, reading incoming messages from the -// transport and dispatching them appropiately. +// transport and dispatching them appropriately. func (conn *Conn) inWorker() { + sequenceGen := newSequenceGenerator() for { msg, err := conn.ReadMessage() if err != nil { @@ -352,7 +379,7 @@ func (conn *Conn) inWorker() { // anything but to shut down all stuff and returns errors to all // pending replies. conn.Close() - conn.calls.finalizeAllWithError(err) + conn.calls.finalizeAllWithError(sequenceGen, err) return } // invalid messages are ignored @@ -381,13 +408,14 @@ func (conn *Conn) inWorker() { if conn.inInt != nil { conn.inInt(msg) } + sequence := sequenceGen.next() switch msg.Type { case TypeError: - conn.serialGen.RetireSerial(conn.calls.handleDBusError(msg)) + conn.serialGen.RetireSerial(conn.calls.handleDBusError(sequence, msg)) case TypeMethodReply: - conn.serialGen.RetireSerial(conn.calls.handleReply(msg)) + conn.serialGen.RetireSerial(conn.calls.handleReply(sequence, msg)) case TypeSignal: - conn.handleSignal(msg) + conn.handleSignal(sequence, msg) case TypeMethodCall: go conn.handleCall(msg) } @@ -395,7 +423,7 @@ func (conn *Conn) inWorker() { } } -func (conn *Conn) handleSignal(msg *Message) { +func (conn *Conn) handleSignal(sequence Sequence, msg *Message) { iface := msg.Headers[FieldInterface].value.(string) member := msg.Headers[FieldMember].value.(string) // as per http://dbus.freedesktop.org/doc/dbus-specification.html , @@ -421,10 +449,11 @@ func (conn *Conn) handleSignal(msg *Message) { } } signal := &Signal{ - Sender: sender, - Path: msg.Headers[FieldPath].value.(ObjectPath), - Name: iface + "." + member, - Body: msg.Body, + Sender: sender, + Path: msg.Headers[FieldPath].value.(ObjectPath), + Name: iface + "." + member, + Body: msg.Body, + Sequence: sequence, } conn.signalHandler.DeliverSignal(iface, member, signal) } @@ -442,6 +471,9 @@ func (conn *Conn) Object(dest string, path ObjectPath) BusObject { } func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) { + if msg.serial == 0 { + msg.serial = conn.getSerial() + } if conn.outInt != nil { conn.outInt(msg) } @@ -473,16 +505,16 @@ func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call { if ctx == nil { panic("nil context") } + if ch == nil { + ch = make(chan *Call, 1) + } else if cap(ch) == 0 { + panic("dbus: unbuffered channel passed to (*Conn).Send") + } var call *Call ctx, canceler := context.WithCancel(ctx) msg.serial = conn.getSerial() if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 { - if ch == nil { - ch = make(chan *Call, 5) - } else if cap(ch) == 0 { - panic("dbus: unbuffered channel passed to (*Conn).Send") - } call = new(Call) call.Destination, _ = msg.Headers[FieldDestination].value.(string) call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath) @@ -504,7 +536,8 @@ func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call { }) } else { canceler() - call = &Call{Err: nil} + call = &Call{Err: nil, Done: ch} + ch <- call conn.sendMessageAndIfClosed(msg, func() { call = &Call{Err: ErrClosed} }) @@ -529,7 +562,6 @@ func (conn *Conn) sendError(err error, dest string, serial uint32) { } msg := new(Message) msg.Type = TypeError - msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) if dest != "" { msg.Headers[FieldDestination] = MakeVariant(dest) @@ -548,7 +580,6 @@ func (conn *Conn) sendError(err error, dest string, serial uint32) { func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { msg := new(Message) msg.Type = TypeMethodReply - msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) if dest != "" { msg.Headers[FieldDestination] = MakeVariant(dest) @@ -564,8 +595,14 @@ func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { // AddMatchSignal registers the given match rule to receive broadcast // signals based on their contents. func (conn *Conn) AddMatchSignal(options ...MatchOption) error { + return conn.AddMatchSignalContext(context.Background(), options...) +} + +// AddMatchSignalContext acts like AddMatchSignal but takes a context. +func (conn *Conn) AddMatchSignalContext(ctx context.Context, options ...MatchOption) error { options = append([]MatchOption{withMatchType("signal")}, options...) - return conn.busObj.Call( + return conn.busObj.CallWithContext( + ctx, "org.freedesktop.DBus.AddMatch", 0, formatMatchOptions(options), ).Store() @@ -573,8 +610,14 @@ func (conn *Conn) AddMatchSignal(options ...MatchOption) error { // RemoveMatchSignal removes the first rule that matches previously registered with AddMatchSignal. func (conn *Conn) RemoveMatchSignal(options ...MatchOption) error { + return conn.RemoveMatchSignalContext(context.Background(), options...) +} + +// RemoveMatchSignalContext acts like RemoveMatchSignal but takes a context. +func (conn *Conn) RemoveMatchSignalContext(ctx context.Context, options ...MatchOption) error { options = append([]MatchOption{withMatchType("signal")}, options...) - return conn.busObj.Call( + return conn.busObj.CallWithContext( + ctx, "org.freedesktop.DBus.RemoveMatch", 0, formatMatchOptions(options), ).Store() @@ -639,10 +682,11 @@ func (e Error) Error() string { // Signal represents a D-Bus message of type Signal. The name member is given in // "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost. type Signal struct { - Sender string - Path ObjectPath - Name string - Body []interface{} + Sender string + Path ObjectPath + Name string + Body []interface{} + Sequence Sequence } // transport is a D-Bus transport. @@ -825,25 +869,25 @@ func (tracker *callTracker) track(sn uint32, call *Call) { tracker.lck.Unlock() } -func (tracker *callTracker) handleReply(msg *Message) uint32 { +func (tracker *callTracker) handleReply(sequence Sequence, msg *Message) uint32 { serial := msg.Headers[FieldReplySerial].value.(uint32) tracker.lck.RLock() _, ok := tracker.calls[serial] tracker.lck.RUnlock() if ok { - tracker.finalizeWithBody(serial, msg.Body) + tracker.finalizeWithBody(serial, sequence, msg.Body) } return serial } -func (tracker *callTracker) handleDBusError(msg *Message) uint32 { +func (tracker *callTracker) handleDBusError(sequence Sequence, msg *Message) uint32 { serial := msg.Headers[FieldReplySerial].value.(uint32) tracker.lck.RLock() _, ok := tracker.calls[serial] tracker.lck.RUnlock() if ok { name, _ := msg.Headers[FieldErrorName].value.(string) - tracker.finalizeWithError(serial, Error{name, msg.Body}) + tracker.finalizeWithError(serial, sequence, Error{name, msg.Body}) } return serial } @@ -856,7 +900,7 @@ func (tracker *callTracker) handleSendError(msg *Message, err error) { _, ok := tracker.calls[msg.serial] tracker.lck.RUnlock() if ok { - tracker.finalizeWithError(msg.serial, err) + tracker.finalizeWithError(msg.serial, NoSequence, err) } } @@ -871,7 +915,7 @@ func (tracker *callTracker) finalize(sn uint32) { } } -func (tracker *callTracker) finalizeWithBody(sn uint32, body []interface{}) { +func (tracker *callTracker) finalizeWithBody(sn uint32, sequence Sequence, body []interface{}) { tracker.lck.Lock() c, ok := tracker.calls[sn] if ok { @@ -880,11 +924,12 @@ func (tracker *callTracker) finalizeWithBody(sn uint32, body []interface{}) { tracker.lck.Unlock() if ok { c.Body = body + c.ResponseSequence = sequence c.done() } } -func (tracker *callTracker) finalizeWithError(sn uint32, err error) { +func (tracker *callTracker) finalizeWithError(sn uint32, sequence Sequence, err error) { tracker.lck.Lock() c, ok := tracker.calls[sn] if ok { @@ -893,11 +938,12 @@ func (tracker *callTracker) finalizeWithError(sn uint32, err error) { tracker.lck.Unlock() if ok { c.Err = err + c.ResponseSequence = sequence c.done() } } -func (tracker *callTracker) finalizeAllWithError(err error) { +func (tracker *callTracker) finalizeAllWithError(sequenceGen *sequenceGenerator, err error) { tracker.lck.Lock() closedCalls := make([]*Call, 0, len(tracker.calls)) for sn := range tracker.calls { @@ -907,6 +953,7 @@ func (tracker *callTracker) finalizeAllWithError(err error) { tracker.lck.Unlock() for _, call := range closedCalls { call.Err = err + call.ResponseSequence = sequenceGen.next() call.done() } } diff --git a/vendor/github.com/godbus/dbus/v5/dbus.go b/vendor/github.com/godbus/dbus/v5/dbus.go index 428923d266..ddf3b7afde 100644 --- a/vendor/github.com/godbus/dbus/v5/dbus.go +++ b/vendor/github.com/godbus/dbus/v5/dbus.go @@ -28,6 +28,7 @@ var ( interfaceType = reflect.TypeOf((*interface{})(nil)).Elem() unixFDType = reflect.TypeOf(UnixFD(0)) unixFDIndexType = reflect.TypeOf(UnixFDIndex(0)) + errType = reflect.TypeOf((*error)(nil)).Elem() ) // An InvalidTypeError signals that a value which cannot be represented in the @@ -63,6 +64,9 @@ func storeInterfaces(src, dest interface{}) error { func store(dest, src reflect.Value) error { if dest.Kind() == reflect.Ptr { + if dest.IsNil() { + dest.Set(reflect.New(dest.Type().Elem())) + } return store(dest.Elem(), src) } switch src.Kind() { diff --git a/vendor/github.com/godbus/dbus/v5/default_handler.go b/vendor/github.com/godbus/dbus/v5/default_handler.go index 6d8bf32f9f..13132c6b47 100644 --- a/vendor/github.com/godbus/dbus/v5/default_handler.go +++ b/vendor/github.com/godbus/dbus/v5/default_handler.go @@ -126,14 +126,28 @@ func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) { } ret := m.Value.Call(params) - - err := ret[t.NumOut()-1].Interface().(*Error) - ret = ret[:t.NumOut()-1] + var err error + nilErr := false // The reflection will find almost-nils, let's only pass back clean ones! + if t.NumOut() > 0 { + if e, ok := ret[t.NumOut()-1].Interface().(*Error); ok { // godbus *Error + nilErr = ret[t.NumOut()-1].IsNil() + ret = ret[:t.NumOut()-1] + err = e + } else if ret[t.NumOut()-1].Type().Implements(errType) { // Go error + i := ret[t.NumOut()-1].Interface() + if i == nil { + nilErr = ret[t.NumOut()-1].IsNil() + } else { + err = i.(error) + } + ret = ret[:t.NumOut()-1] + } + } out := make([]interface{}, len(ret)) for i, val := range ret { out[i] = val.Interface() } - if err == nil { + if nilErr || err == nil { //concrete type to interface nil is a special case return out, nil } diff --git a/vendor/github.com/godbus/dbus/v5/export.go b/vendor/github.com/godbus/dbus/v5/export.go index c277ab1426..2447b51d46 100644 --- a/vendor/github.com/godbus/dbus/v5/export.go +++ b/vendor/github.com/godbus/dbus/v5/export.go @@ -69,6 +69,22 @@ func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Va return methods } +func getAllMethods(in interface{}, mapping map[string]string) map[string]reflect.Value { + if in == nil { + return nil + } + methods := make(map[string]reflect.Value) + val := reflect.ValueOf(in) + typ := val.Type() + for i := 0; i < typ.NumMethod(); i++ { + methtype := typ.Method(i) + method := val.Method(i) + // map names while building table + methods[computeMethodName(methtype.Name, mapping)] = method + } + return methods +} + func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) { pointers := make([]interface{}, m.NumArguments()) decode := make([]interface{}, 0, len(body)) @@ -159,7 +175,6 @@ func (conn *Conn) handleCall(msg *Message) { if msg.Flags&FlagNoReplyExpected == 0 { reply := new(Message) reply.Type = TypeMethodReply - reply.serial = conn.getSerial() reply.Headers = make(map[HeaderField]Variant) if hasSender { reply.Headers[FieldDestination] = msg.Headers[FieldSender] @@ -195,7 +210,6 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro } msg := new(Message) msg.Type = TypeSignal - msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) msg.Headers[FieldInterface] = MakeVariant(iface) msg.Headers[FieldMember] = MakeVariant(member) @@ -247,6 +261,18 @@ func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error { return conn.ExportWithMap(v, nil, path, iface) } +// ExportAll registers all exported methods defined by the given object on +// the message bus. +// +// Unlike Export there is no requirement to have the last parameter as type +// *Error. If you want to be able to return error then you can append an error +// type parameter to your method signature. If the error returned is not nil, +// it is sent back to the caller as an error. Otherwise, a method reply is +// sent with the other return values as its body. +func (conn *Conn) ExportAll(v interface{}, path ObjectPath, iface string) error { + return conn.export(getAllMethods(v, nil), path, iface, false) +} + // ExportWithMap works exactly like Export but provides the ability to remap // method names (e.g. export a lower-case method). // @@ -299,19 +325,22 @@ func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path } func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error { - out := make(map[string]reflect.Value) - for name, method := range methods { - rval := reflect.ValueOf(method) - if rval.Kind() != reflect.Func { - continue - } - t := rval.Type() - // only track valid methods must return *Error as last arg - if t.NumOut() == 0 || - t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) { - continue + var out map[string]reflect.Value + if methods != nil { + out = make(map[string]reflect.Value) + for name, method := range methods { + rval := reflect.ValueOf(method) + if rval.Kind() != reflect.Func { + continue + } + t := rval.Type() + // only track valid methods must return *Error as last arg + if t.NumOut() == 0 || + t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) { + continue + } + out[name] = rval } - out[name] = rval } return conn.export(out, path, iface, includeSubtree) } @@ -327,12 +356,12 @@ func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) err return nil } -// exportWithMap is the worker function for all exports/registrations. +// export is the worker function for all exports/registrations. func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error { h, ok := conn.handler.(*defaultHandler) if !ok { return fmt.Errorf( - `dbus: export only allowed on the default hander handler have %T"`, + `dbus: export only allowed on the default handler. Received: %T"`, conn.handler) } diff --git a/vendor/github.com/godbus/dbus/v5/match.go b/vendor/github.com/godbus/dbus/v5/match.go index 086ee336a9..5a607e53e4 100644 --- a/vendor/github.com/godbus/dbus/v5/match.go +++ b/vendor/github.com/godbus/dbus/v5/match.go @@ -1,6 +1,7 @@ package dbus import ( + "strconv" "strings" ) @@ -60,3 +61,29 @@ func WithMatchPathNamespace(namespace ObjectPath) MatchOption { func WithMatchDestination(destination string) MatchOption { return WithMatchOption("destination", destination) } + +// WithMatchArg sets argN match option, range of N is 0 to 63. +func WithMatchArg(argIdx int, value string) MatchOption { + if argIdx < 0 || argIdx > 63 { + panic("range of argument index is 0 to 63") + } + return WithMatchOption("arg"+strconv.Itoa(argIdx), value) +} + +// WithMatchArgPath sets argN path match option, range of N is 0 to 63. +func WithMatchArgPath(argIdx int, path string) MatchOption { + if argIdx < 0 || argIdx > 63 { + panic("range of argument index is 0 to 63") + } + return WithMatchOption("arg"+strconv.Itoa(argIdx)+"path", path) +} + +// WithMatchArg0Namespace sets arg0namespace match option. +func WithMatchArg0Namespace(arg0Namespace string) MatchOption { + return WithMatchOption("arg0namespace", arg0Namespace) +} + +// WithMatchEavesdrop sets eavesdrop match option. +func WithMatchEavesdrop(eavesdrop bool) MatchOption { + return WithMatchOption("eavesdrop", strconv.FormatBool(eavesdrop)) +} diff --git a/vendor/github.com/godbus/dbus/v5/object.go b/vendor/github.com/godbus/dbus/v5/object.go index 8acd7fc8b1..664abb7fba 100644 --- a/vendor/github.com/godbus/dbus/v5/object.go +++ b/vendor/github.com/godbus/dbus/v5/object.go @@ -16,6 +16,7 @@ type BusObject interface { AddMatchSignal(iface, member string, options ...MatchOption) *Call RemoveMatchSignal(iface, member string, options ...MatchOption) *Call GetProperty(p string) (Variant, error) + StoreProperty(p string, value interface{}) error SetProperty(p string, v interface{}) error Destination() string Path() ObjectPath @@ -109,7 +110,6 @@ func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch method = method[i+1:] msg := new(Message) msg.Type = TypeMethodCall - msg.serial = o.conn.getSerial() msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected) msg.Headers = make(map[HeaderField]Variant) msg.Headers[FieldPath] = MakeVariant(o.path) @@ -122,68 +122,31 @@ func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch if len(args) > 0 { msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...)) } - if msg.Flags&FlagNoReplyExpected == 0 { - if ch == nil { - ch = make(chan *Call, 1) - } else if cap(ch) == 0 { - panic("dbus: unbuffered channel passed to (*Object).Go") - } - ctx, cancel := context.WithCancel(ctx) - call := &Call{ - Destination: o.dest, - Path: o.path, - Method: method, - Args: args, - Done: ch, - ctxCanceler: cancel, - ctx: ctx, - } - o.conn.calls.track(msg.serial, call) - o.conn.sendMessageAndIfClosed(msg, func() { - o.conn.calls.handleSendError(msg, ErrClosed) - cancel() - }) - go func() { - <-ctx.Done() - o.conn.calls.handleSendError(msg, ctx.Err()) - }() - - return call - } - done := make(chan *Call, 1) - call := &Call{ - Err: nil, - Done: done, - } - defer func() { - call.Done <- call - close(done) - }() - o.conn.sendMessageAndIfClosed(msg, func() { - call.Err = ErrClosed - }) - return call + return o.conn.SendWithContext(ctx, msg, ch) } // GetProperty calls org.freedesktop.DBus.Properties.Get on the given // object. The property name must be given in interface.member notation. func (o *Object) GetProperty(p string) (Variant, error) { + var result Variant + err := o.StoreProperty(p, &result) + return result, err +} + +// StoreProperty calls org.freedesktop.DBus.Properties.Get on the given +// object. The property name must be given in interface.member notation. +// It stores the returned property into the provided value. +func (o *Object) StoreProperty(p string, value interface{}) error { idx := strings.LastIndex(p, ".") if idx == -1 || idx+1 == len(p) { - return Variant{}, errors.New("dbus: invalid property " + p) + return errors.New("dbus: invalid property " + p) } iface := p[:idx] prop := p[idx+1:] - result := Variant{} - err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result) - - if err != nil { - return Variant{}, err - } - - return result, nil + return o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop). + Store(value) } // SetProperty calls org.freedesktop.DBus.Properties.Set on the given diff --git a/vendor/github.com/godbus/dbus/v5/sequence.go b/vendor/github.com/godbus/dbus/v5/sequence.go new file mode 100644 index 0000000000..89435d3933 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/sequence.go @@ -0,0 +1,24 @@ +package dbus + +// Sequence represents the value of a monotonically increasing counter. +type Sequence uint64 + +const ( + // NoSequence indicates the absence of a sequence value. + NoSequence Sequence = 0 +) + +// sequenceGenerator represents a monotonically increasing counter. +type sequenceGenerator struct { + nextSequence Sequence +} + +func (generator *sequenceGenerator) next() Sequence { + result := generator.nextSequence + generator.nextSequence++ + return result +} + +func newSequenceGenerator() *sequenceGenerator { + return &sequenceGenerator{nextSequence: 1} +} diff --git a/vendor/github.com/godbus/dbus/v5/sequential_handler.go b/vendor/github.com/godbus/dbus/v5/sequential_handler.go new file mode 100644 index 0000000000..ef2fcdba17 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/sequential_handler.go @@ -0,0 +1,125 @@ +package dbus + +import ( + "sync" +) + +// NewSequentialSignalHandler returns an instance of a new +// signal handler that guarantees sequential processing of signals. It is a +// guarantee of this signal handler that signals will be written to +// channels in the order they are received on the DBus connection. +func NewSequentialSignalHandler() SignalHandler { + return &sequentialSignalHandler{} +} + +type sequentialSignalHandler struct { + mu sync.RWMutex + closed bool + signals []*sequentialSignalChannelData +} + +func (sh *sequentialSignalHandler) DeliverSignal(intf, name string, signal *Signal) { + sh.mu.RLock() + defer sh.mu.RUnlock() + if sh.closed { + return + } + for _, scd := range sh.signals { + scd.deliver(signal) + } +} + +func (sh *sequentialSignalHandler) Terminate() { + sh.mu.Lock() + defer sh.mu.Unlock() + if sh.closed { + return + } + + for _, scd := range sh.signals { + scd.close() + close(scd.ch) + } + sh.closed = true + sh.signals = nil +} + +func (sh *sequentialSignalHandler) AddSignal(ch chan<- *Signal) { + sh.mu.Lock() + defer sh.mu.Unlock() + if sh.closed { + return + } + sh.signals = append(sh.signals, newSequentialSignalChannelData(ch)) +} + +func (sh *sequentialSignalHandler) RemoveSignal(ch chan<- *Signal) { + sh.mu.Lock() + defer sh.mu.Unlock() + if sh.closed { + return + } + for i := len(sh.signals) - 1; i >= 0; i-- { + if ch == sh.signals[i].ch { + sh.signals[i].close() + copy(sh.signals[i:], sh.signals[i+1:]) + sh.signals[len(sh.signals)-1] = nil + sh.signals = sh.signals[:len(sh.signals)-1] + } + } +} + +type sequentialSignalChannelData struct { + ch chan<- *Signal + in chan *Signal + done chan struct{} +} + +func newSequentialSignalChannelData(ch chan<- *Signal) *sequentialSignalChannelData { + scd := &sequentialSignalChannelData{ + ch: ch, + in: make(chan *Signal), + done: make(chan struct{}), + } + go scd.bufferSignals() + return scd +} + +func (scd *sequentialSignalChannelData) bufferSignals() { + defer close(scd.done) + + // Ensure that signals are delivered to scd.ch in the same + // order they are received from scd.in. + var queue []*Signal + for { + if len(queue) == 0 { + signal, ok := <- scd.in + if !ok { + return + } + queue = append(queue, signal) + } + select { + case scd.ch <- queue[0]: + copy(queue, queue[1:]) + queue[len(queue)-1] = nil + queue = queue[:len(queue)-1] + case signal, ok := <-scd.in: + if !ok { + return + } + queue = append(queue, signal) + } + } +} + +func (scd *sequentialSignalChannelData) deliver(signal *Signal) { + scd.in <- signal +} + +func (scd *sequentialSignalChannelData) close() { + close(scd.in) + // Ensure that bufferSignals() has exited and won't attempt + // any future sends on scd.ch + <-scd.done +} diff --git a/vendor/github.com/godbus/dbus/v5/sig.go b/vendor/github.com/godbus/dbus/v5/sig.go index c1b809202c..2d326cebc0 100644 --- a/vendor/github.com/godbus/dbus/v5/sig.go +++ b/vendor/github.com/godbus/dbus/v5/sig.go @@ -137,7 +137,7 @@ func ParseSignatureMust(s string) Signature { return sig } -// Empty retruns whether the signature is the empty signature. +// Empty returns whether the signature is the empty signature. func (s Signature) Empty() bool { return s.str == "" } diff --git a/vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go b/vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go index 0fc5b92739..1b5ed2089d 100644 --- a/vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go +++ b/vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go @@ -10,6 +10,7 @@ package dbus /* const int sizeofPtr = sizeof(void*); #define _WANT_UCRED +#include #include */ import "C" diff --git a/vendor/github.com/godbus/dbus/v5/variant.go b/vendor/github.com/godbus/dbus/v5/variant.go index 5b51828c82..f1e81f3ede 100644 --- a/vendor/github.com/godbus/dbus/v5/variant.go +++ b/vendor/github.com/godbus/dbus/v5/variant.go @@ -142,3 +142,9 @@ func (v Variant) String() string { func (v Variant) Value() interface{} { return v.value } + +// Store converts the variant into a native go type using the same +// mechanism as the "Store" function. +func (v Variant) Store(value interface{}) error { + return storeInterfaces(v.value, value) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 96f646bd03..f9a242d171 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -33,7 +33,7 @@ github.com/go-gl/glfw/v3.3/glfw/glfw/deps/mingw github.com/go-gl/glfw/v3.3/glfw/glfw/deps/vs2008 github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW github.com/go-gl/glfw/v3.3/glfw/glfw/src -# github.com/godbus/dbus/v5 v5.0.3 +# github.com/godbus/dbus/v5 v5.0.4 github.com/godbus/dbus/v5 # github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff github.com/goki/freetype From ef3ba20814ade16e9520a6b2f1412f740438ee85 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 6 Apr 2021 11:15:44 +0100 Subject: [PATCH 07/57] Fix possble nil --- widget/entry.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/widget/entry.go b/widget/entry.go index 9b838a9d24..8e58391b35 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -1546,6 +1546,10 @@ func (r *entryContentRenderer) moveCursor() { } func (r *entryContentRenderer) updateScrollDirections() { + if r.content.scroll == nil { // not scrolling + return + } + switch r.content.entry.Wrapping { case fyne.TextWrapOff: r.content.scroll.Direction = widget.ScrollNone From e33c72cf48d42102e42a927b74e17f3c828ea6b7 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 6 Apr 2021 11:45:31 +0100 Subject: [PATCH 08/57] Sanity check refresh request for textgrid cell --- widget/textgrid.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/widget/textgrid.go b/widget/textgrid.go index 2c5f933dd2..40392ea878 100644 --- a/widget/textgrid.go +++ b/widget/textgrid.go @@ -335,6 +335,10 @@ func (t *textGridRenderer) appendTextCell(str rune) { func (t *textGridRenderer) refreshCell(row, col int) { pos := row*t.cols + col + if pos*2+1 >= len(t.objects) { + return + } + text := t.objects[pos*2+1].(*canvas.Text) rect := t.objects[pos*2].(*canvas.Rectangle) From c527d0fe547f225f9bf557d1ae4d2e4e50843da7 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 12 Apr 2021 19:47:49 +0100 Subject: [PATCH 09/57] Re-use the underling image in Icon widget --- widget/icon.go | 24 +++++++++++++++--------- widget/icon_internal_test.go | 3 ++- widget/testdata/icon/layout_empty.xml | 1 + 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/widget/icon.go b/widget/icon.go index b2c6dd7e72..e01bd045a5 100644 --- a/widget/icon.go +++ b/widget/icon.go @@ -9,6 +9,8 @@ import ( type iconRenderer struct { widget.BaseRenderer + raster *canvas.Image + image *Icon } @@ -26,14 +28,15 @@ func (i *iconRenderer) Layout(size fyne.Size) { } func (i *iconRenderer) Refresh() { - if i.image.Resource != i.image.cachedRes { - i.image.propertyLock.RLock() - i.updateObjects() - i.image.cachedRes = i.image.Resource - i.image.propertyLock.RUnlock() + if i.image.Resource == i.image.cachedRes { + return } - i.Layout(i.image.Size()) + i.image.propertyLock.RLock() + i.raster.Resource = i.image.Resource + i.image.cachedRes = i.image.Resource + i.image.propertyLock.RUnlock() + canvas.Refresh(i.image.super()) } @@ -58,7 +61,6 @@ type Icon struct { // SetResource updates the resource rendered in this icon widget func (i *Icon) SetResource(res fyne.Resource) { i.Resource = res - i.cachedRes = nil i.Refresh() } @@ -73,8 +75,12 @@ func (i *Icon) CreateRenderer() fyne.WidgetRenderer { i.ExtendBaseWidget(i) i.propertyLock.RLock() defer i.propertyLock.RUnlock() - r := &iconRenderer{image: i} - r.updateObjects() + + img := canvas.NewImageFromResource(i.Resource) + img.FillMode = canvas.ImageFillContain + r := &iconRenderer{image: i, raster: img} + r.SetObjects([]fyne.CanvasObject{img}) + i.cachedRes = i.Resource return r } diff --git a/widget/icon_internal_test.go b/widget/icon_internal_test.go index fd88d9ab12..07733d66fe 100644 --- a/widget/icon_internal_test.go +++ b/widget/icon_internal_test.go @@ -26,7 +26,8 @@ func TestIcon_Nil(t *testing.T) { icon := NewIcon(nil) render := test.WidgetRenderer(icon) - assert.Equal(t, 0, len(render.Objects())) + assert.Equal(t, 1, len(render.Objects())) + assert.Nil(t, render.Objects()[0].(*canvas.Image).Resource) } func TestIcon_MinSize(t *testing.T) { diff --git a/widget/testdata/icon/layout_empty.xml b/widget/testdata/icon/layout_empty.xml index fee0aa6145..3a6aac5ba4 100644 --- a/widget/testdata/icon/layout_empty.xml +++ b/widget/testdata/icon/layout_empty.xml @@ -2,6 +2,7 @@ + From 55ca1389b6407f5bcc1d8d2b80e1e8769c8cd61d Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 12 Apr 2021 19:54:31 +0100 Subject: [PATCH 10/57] Remove dead code too --- widget/icon.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/widget/icon.go b/widget/icon.go index e01bd045a5..df70b20fd4 100644 --- a/widget/icon.go +++ b/widget/icon.go @@ -40,16 +40,6 @@ func (i *iconRenderer) Refresh() { canvas.Refresh(i.image.super()) } -func (i *iconRenderer) updateObjects() { - var objects []fyne.CanvasObject - if i.image.Resource != nil { - raster := canvas.NewImageFromResource(i.image.Resource) - raster.FillMode = canvas.ImageFillContain - objects = append(objects, raster) - } - i.SetObjects(objects) -} - // Icon widget is a basic image component that load's its resource to match the theme. type Icon struct { BaseWidget From e040bd852194e834a91b3dc563578526b8b32e6a Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 20 Apr 2021 17:03:38 +0100 Subject: [PATCH 11/57] Don't remove windows build artefact as that is the final package name --- cmd/fyne/commands/package.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/fyne/commands/package.go b/cmd/fyne/commands/package.go index 86556640fa..eb09ced601 100644 --- a/cmd/fyne/commands/package.go +++ b/cmd/fyne/commands/package.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "log" + "runtime" "strconv" "strings" @@ -106,7 +107,9 @@ func (p *packager) doPackage() error { if !util.Exists(p.exe) { return fmt.Errorf("unable to build directory to expected executable, %s", p.exe) } - defer p.removeBuild() + if runtime.GOOS != "windows" { + defer p.removeBuild() + } } switch p.os { From 71280c2ec1d790f1829d79740094f897b9ea1b65 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 2 Apr 2021 19:02:33 +0100 Subject: [PATCH 12/57] Fix crash in label with binding Moved to a different setup model where we don't recreate listeners all the time. Fixes #2125 --- widget/label.go | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/widget/label.go b/widget/label.go index ccc6ada36b..8a4817ab78 100644 --- a/widget/label.go +++ b/widget/label.go @@ -55,18 +55,7 @@ func NewLabelWithStyle(text string, alignment fyne.TextAlign, style fyne.TextSty func (l *Label) Bind(data binding.String) { l.Unbind() l.textSource = data - l.textListener = binding.NewDataListener(func() { - val, err := l.textSource.Get() - if err != nil { - fyne.LogError("Error getting current data value", err) - return - } - - l.Text = val - if cache.IsRendered(l) { - l.Refresh() - } - }) + l.createListener() data.AddListener(l.textListener) } @@ -114,10 +103,31 @@ func (l *Label) Unbind() { } l.textSource.RemoveListener(l.textListener) - l.textListener = nil l.textSource = nil } +func (l *Label) createListener() { + if l.textListener != nil { + return + } + + l.textListener = binding.NewDataListener(func() { + if l.textSource == nil { + return + } + val, err := l.textSource.Get() + if err != nil { + fyne.LogError("Error getting current data value", err) + return + } + + l.Text = val + if cache.IsRendered(l) { + l.Refresh() + } + }) +} + // textAlign tells the rendering textProvider our alignment func (l *Label) textAlign() fyne.TextAlign { return l.Alignment From 6fa432dd354b416438e5b9a2170d984d5121fa48 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 7 Apr 2021 10:47:17 +0100 Subject: [PATCH 13/57] Additional safety on nil checks --- widget/label.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/widget/label.go b/widget/label.go index 8a4817ab78..f748af3a22 100644 --- a/widget/label.go +++ b/widget/label.go @@ -98,11 +98,12 @@ func (l *Label) SetText(text string) { // // Since: 2.0 func (l *Label) Unbind() { - if l.textSource == nil || l.textListener == nil { + src := l.textSource + if src == nil { return } - l.textSource.RemoveListener(l.textListener) + src.RemoveListener(l.textListener) l.textSource = nil } @@ -112,10 +113,11 @@ func (l *Label) createListener() { } l.textListener = binding.NewDataListener(func() { - if l.textSource == nil { + src := l.textSource + if src == nil { return } - val, err := l.textSource.Get() + val, err := src.Get() if err != nil { fyne.LogError("Error getting current data value", err) return From 9d4731d52e0b557b333a0014b46438c1d58f24f5 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Sun, 4 Apr 2021 16:52:39 +0200 Subject: [PATCH 14/57] Update godbus/dbus for FreeBSD 13 compilation fix Fyne can not be compiled on FreeBSD 13 without this change due to a missing include. Fixes #2130 From 6e1463316d0fb1b378da006f9eaf701d70f8e653 Mon Sep 17 00:00:00 2001 From: FPabl0 Date: Sun, 4 Apr 2021 18:22:50 -0500 Subject: [PATCH 15/57] dataList should notify only once when change --- data/binding/bindlists_test.go | 40 ++++++++++++++++++++++++++++++++++ data/binding/listbinding.go | 4 ---- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/data/binding/bindlists_test.go b/data/binding/bindlists_test.go index 4aadc8d51a..1a0fa9c7bb 100644 --- a/data/binding/bindlists_test.go +++ b/data/binding/bindlists_test.go @@ -160,3 +160,43 @@ func TestFloatList_Set(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 5.3, v) } + +func TestFloatList_NotifyOnlyOnceWhenChange(t *testing.T) { + f := NewFloatList() + triggered := 0 + f.AddListener(NewDataListener(func() { + triggered++ + })) + waitForItems() + assert.Equal(t, 1, triggered) + + triggered = 0 + f.Set([]float64{55, 77}) + waitForItems() + assert.Equal(t, 1, triggered) + + triggered = 0 + f.SetValue(0, 5) + waitForItems() + assert.Zero(t, triggered) + + triggered = 0 + f.Set([]float64{101, 98}) + waitForItems() + assert.Zero(t, triggered) + + triggered = 0 + f.Append(88) + waitForItems() + assert.Equal(t, 1, triggered) + + triggered = 0 + f.Prepend(23) + waitForItems() + assert.Equal(t, 1, triggered) + + triggered = 0 + f.Set([]float64{32}) + waitForItems() + assert.Equal(t, 1, triggered) +} diff --git a/data/binding/listbinding.go b/data/binding/listbinding.go index dfcffc937c..bedfc30df5 100644 --- a/data/binding/listbinding.go +++ b/data/binding/listbinding.go @@ -30,12 +30,8 @@ func (b *listBase) Length() int { func (b *listBase) appendItem(i DataItem) { b.items = append(b.items, i) - - b.trigger() } func (b *listBase) deleteItem(i int) { b.items = append(b.items[:i], b.items[i+1:]...) - - b.trigger() } From b971a6a9bdf39cd58fe75ed06e936612689c1306 Mon Sep 17 00:00:00 2001 From: FPabl0 Date: Mon, 5 Apr 2021 13:58:03 -0500 Subject: [PATCH 16/57] do not show virtual keyboard if the canvas object (widget) is disabled --- internal/driver/gomobile/canvas.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 1824df2525..1506a1670e 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -253,7 +253,11 @@ func (c *mobileCanvas) focusManager() *app.FocusManager { } func (c *mobileCanvas) handleKeyboard(obj fyne.Focusable) { - if keyb, ok := obj.(mobile.Keyboardable); ok { + isDisabled := false + if disWid, ok := obj.(fyne.Disableable); ok { + isDisabled = disWid.Disabled() + } + if keyb, ok := obj.(mobile.Keyboardable); ok && !isDisabled { showVirtualKeyboard(keyb.Keyboard()) } else { hideVirtualKeyboard() From f87060c5f170c8824229ab418c749fa833deb721 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 6 Apr 2021 11:45:31 +0100 Subject: [PATCH 17/57] Sanity check refresh request for textgrid cell From cce521c43628899ae93e138067a9c1f8659b5270 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 9 Apr 2021 19:38:51 +0100 Subject: [PATCH 18/57] Consistent cancel checks --- cmd/fyne_demo/tutorials/dialog.go | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/cmd/fyne_demo/tutorials/dialog.go b/cmd/fyne_demo/tutorials/dialog.go index a829d0148a..1c86704905 100644 --- a/cmd/fyne_demo/tutorials/dialog.go +++ b/cmd/fyne_demo/tutorials/dialog.go @@ -47,13 +47,14 @@ func dialogScreen(win fyne.Window) fyne.CanvasObject { }), widget.NewButton("File Open With Filter (.jpg or .png)", func() { fd := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) { - if err == nil && reader == nil { - return - } if err != nil { dialog.ShowError(err, win) return } + if reader == nil { + log.Println("Cancelled") + return + } imageOpened(reader) }, win) @@ -66,6 +67,10 @@ func dialogScreen(win fyne.Window) fyne.CanvasObject { dialog.ShowError(err, win) return } + if writer == nil { + log.Println("Cancelled") + return + } fileSaved(writer, win) }, win) @@ -77,6 +82,7 @@ func dialogScreen(win fyne.Window) fyne.CanvasObject { return } if list == nil { + log.Println("Cancelled") return } @@ -142,11 +148,6 @@ func imageOpened(f fyne.URIReadCloser) { } func fileSaved(f fyne.URIWriteCloser, w fyne.Window) { - if f == nil { - log.Println("Cancelled") - return - } - defer f.Close() _, err := f.Write([]byte("Written by Fyne demo\n")) if err != nil { From 120fac28a06388f0b6c3621609b7cc4615e1004d Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 9 Apr 2021 19:39:41 +0100 Subject: [PATCH 19/57] Fix typos --- theme/icons.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/theme/icons.go b/theme/icons.go index 927913845c..91db076f15 100644 --- a/theme/icons.go +++ b/theme/icons.go @@ -1014,12 +1014,12 @@ func ZoomOutIcon() fyne.Resource { return safeIconLookup(IconNameViewZoomOut) } -// VisibilityIcon returns a resource containing the standard visibity icon for the current theme +// VisibilityIcon returns a resource containing the standard visibility icon for the current theme func VisibilityIcon() fyne.Resource { return safeIconLookup(IconNameVisibility) } -// VisibilityOffIcon returns a resource containing the standard visibity off icon for the current theme +// VisibilityOffIcon returns a resource containing the standard visibility off icon for the current theme func VisibilityOffIcon() fyne.Resource { return safeIconLookup(IconNameVisibilityOff) } From 2b0e890e22a2c1452fb87e14c0b79c2363a04c5c Mon Sep 17 00:00:00 2001 From: FPabl0 Date: Fri, 9 Apr 2021 23:52:03 -0500 Subject: [PATCH 20/57] animationCurveItem boxes should be recolored when the theme changes --- cmd/fyne_demo/tutorials/animation.go | 45 +++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/cmd/fyne_demo/tutorials/animation.go b/cmd/fyne_demo/tutorials/animation.go index 29da0d43a2..465b36476c 100644 --- a/cmd/fyne_demo/tutorials/animation.go +++ b/cmd/fyne_demo/tutorials/animation.go @@ -84,7 +84,7 @@ func makeAnimationCurveItem(label string, curve fyne.AnimationCurve, yOff float3 text.Alignment = fyne.TextAlignCenter text.Resize(fyne.NewSize(380, 30)) text.Move(fyne.NewPos(0, yOff)) - box = canvas.NewRectangle(theme.ForegroundColor()) + box = newThemedBox() box.Resize(fyne.NewSize(30, 30)) box.Move(fyne.NewPos(0, yOff)) @@ -98,3 +98,46 @@ func makeAnimationCurveItem(label string, curve fyne.AnimationCurve, yOff float3 anim.RepeatCount = 1 return } + +// themedBox is a simple box that change its background color according +// to the selected theme +type themedBox struct { + widget.BaseWidget +} + +func newThemedBox() *themedBox { + b := &themedBox{} + b.ExtendBaseWidget(b) + return b +} + +func (b *themedBox) CreateRenderer() fyne.WidgetRenderer { + b.ExtendBaseWidget(b) + bg := canvas.NewRectangle(theme.ForegroundColor()) + return &themedBoxRenderer{bg: bg, objects: []fyne.CanvasObject{bg}} +} + +type themedBoxRenderer struct { + bg *canvas.Rectangle + objects []fyne.CanvasObject +} + +func (r *themedBoxRenderer) Destroy() { +} + +func (r *themedBoxRenderer) Layout(size fyne.Size) { + r.bg.Resize(size) +} + +func (r *themedBoxRenderer) MinSize() fyne.Size { + return r.bg.MinSize() +} + +func (r *themedBoxRenderer) Objects() []fyne.CanvasObject { + return r.objects +} + +func (r *themedBoxRenderer) Refresh() { + r.bg.FillColor = theme.ForegroundColor() + r.bg.Refresh() +} From eb9838844f5bf5b697eae56c87ab07e0ba46b88a Mon Sep 17 00:00:00 2001 From: Chris Howey Date: Wed, 14 Apr 2021 04:08:50 -0500 Subject: [PATCH 21/57] Save dialog with filename for Android (#2162) Use the initialFileName which gets set when calling SetFileName() on the fyne dialog. --- cmd/fyne/internal/mobile/dex.go | 375 +++++++++--------- dialog/file_mobile.go | 2 +- go.mod | 2 +- go.sum | 2 + internal/driver/gomobile/file.go | 6 +- .../fyne-io/mobile/app/GoNativeActivity.java | 7 +- .../github.com/fyne-io/mobile/app/android.c | 8 +- .../github.com/fyne-io/mobile/app/android.go | 8 +- vendor/github.com/fyne-io/mobile/app/app.go | 6 +- .../fyne-io/mobile/app/darwin_desktop.go | 2 +- .../fyne-io/mobile/app/darwin_ios.go | 2 +- vendor/github.com/fyne-io/mobile/app/shiny.go | 2 +- vendor/github.com/fyne-io/mobile/app/x11.go | 2 +- vendor/modules.txt | 2 +- 14 files changed, 217 insertions(+), 209 deletions(-) diff --git a/cmd/fyne/internal/mobile/dex.go b/cmd/fyne/internal/mobile/dex.go index c8a8df6669..03d6968f78 100644 --- a/cmd/fyne/internal/mobile/dex.go +++ b/cmd/fyne/internal/mobile/dex.go @@ -6,191 +6,192 @@ package mobile -var dexStr = `ZGV4CjAzNQDm5OAXFST/M4oWO8uK7rX3ZYH75wE14SUoJgAAcAAAAHhWNBIAAAAAAAAAAF` + - `glAAC+AAAAcAAAAC4AAABoAwAAOwAAACAEAAATAAAA5AYAAGUAAAB8BwAABgAAAKQKAADE` + - `GgAAZAsAAO4VAADwFQAA9RUAAPoVAAACFgAAFhYAAC0WAAA9FgAATRYAAFMWAABqFgAAbR` + - `YAAHIWAAB4FgAAfRYAAIMWAACGFgAAihYAAI8WAACTFgAAmBYAAJ0WAAC7FgAA3BYAAPcW` + - `AAARFwAANBcAAFkXAAByFwAAhRcAAKEXAAC2FwAAzBcAAOUXAAABGAAAFRgAAEoYAABqGA` + - `AAlhgAAKsYAADSGAAA6RgAAAYZAAA1GQAAUBkAAHsZAACgGQAAwBkAAN8ZAADvGQAACRoA` + - `ACAaAAA/GgAAUxoAAGkaAAB9GgAAkRoAAKgaAADNGgAA8hoAABcbAAA+GwAAYxsAAIYbAA` + - `CcGwAApxsAALAbAADKGwAA1RsAANgbAADcGwAA4RsAAOgbAADuGwAA8hsAAPcbAAD+GwAA` + - `ChwAAA8cAAASHAAAFhwAABscAAAwHAAANBwAAEAcAABMHAAAWBwAAGQcAABwHAAAfBwAAI` + - `kcAACWHAAAphwAALAcAADLHAAA4xwAAPUcAAALHQAAMh0AAFcdAACBHQAAox0AAMQdAADd` + - `HQAA8B0AAP4dAAAIHgAAFx4AACceAAA3HgAARx4AAFceAABaHgAAYh4AAIUeAACZHgAApx` + - `4AAKweAAC9HgAAzh4AANseAADpHgAA8h4AAAAfAAALHwAAFh8AACkfAAA2HwAASx8AAFQf` + - `AABfHwAAcR8AAI0fAACnHwAAwh8AANsfAADmHwAA8B8AAPsfAAALIAAAKSAAADsgAABDIA` + - `AAUSAAAGogAAB4IAAAhyAAAJcgAACmIAAArCAAALQgAAC6IAAAxyAAANsgAAAEIQAADyEA` + - `ABkhAAAfIQAAKSEAADshAABFIQAAVSEAAGQhAABuIQAAfCEAAIEhAACQIQAAnyEAAK0hAA` + - `C+IQAAxyEAANAhAADfIQAA6yEAAPkhAAAHIgAAFSIAACQiAAArIgAAQyIAAFAiAABYIgAA` + - `YCIAAGoiAABvIgAAkyIAAKEiAACzIgAAuiIAAMEiAAAKAAAAFQAAABYAAAAXAAAAGAAAAB` + - `kAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAjAAAAJAAAACUAAAAm` + - `AAAAJwAAACgAAAApAAAAKgAAACsAAAAsAAAALQAAAC4AAAAvAAAAMAAAADEAAAAyAAAAMw` + - `AAADQAAAA1AAAANgAAADcAAAA4AAAAOQAAADoAAAA7AAAAPAAAAD0AAAA+AAAARAAAAE4A` + - `AABRAAAACgAAAAAAAAAAAAAACwAAAAAAAADkFAAADAAAAAAAAADsFAAADQAAAAAAAAD4FA` + - `AADgAAAAAAAAAAFQAADwAAAAIAAAAAAAAADwAAAAQAAAAAAAAAEAAAAAQAAAAMFQAAFAAA` + - `AAQAAAAUFQAAEgAAAAQAAAAcFQAAFAAAAAQAAAAkFQAAEwAAAAUAAAAsFQAADwAAAAYAAA` + - `AAAAAADwAAAAgAAAAAAAAADwAAAAsAAAAAAAAAEAAAABAAAAAMFQAADwAAABIAAAAAAAAA` + - `EAAAABIAAAAMFQAADwAAABQAAAAAAAAADwAAABUAAAAAAAAAEgAAABcAAAA0FQAAFAAAAB` + - `cAAAA8FQAADwAAABwAAAAAAAAAEQAAAB0AAADkFAAAEgAAACAAAAAcFQAADwAAACIAAAAA` + - `AAAAEgAAACIAAAAcFQAAEgAAACIAAAA0FQAAFAAAACIAAABEFQAADwAAACoAAAAAAAAARA` + - `AAACsAAAAAAAAARQAAACsAAAAMFQAARgAAACsAAADkFAAARwAAACsAAABMFQAASAAAACsA` + - `AABYFQAASQAAACsAAABkFQAASgAAACsAAABsFQAASQAAACsAAAB0FQAASQAAACsAAAB8FQ` + - `AASQAAACsAAACEFQAASQAAACsAAADcFAAASQAAACsAAADUFAAATAAAACsAAACMFQAATQAA` + - `ACsAAACkFQAASQAAACsAAACsFQAASQAAACsAAAC0FQAASwAAACsAAAC8FQAASQAAACsAAA` + - `DMFAAASQAAACsAAAAcFQAASQAAACsAAADIFQAASQAAACsAAAA0FQAASgAAACsAAADQFQAA` + - `TQAAACsAAABEFQAATgAAACwAAAAAAAAAUAAAACwAAADYFQAAUAAAACwAAADgFQAATwAAAC` + - `wAAAC0FQAATwAAACwAAADoFQAAEgAAAC0AAAAcFQAABQAKAJsAAAAHAAAAlAAAAAcAAAC3` + - `AAAACQAAAEEAAAAlACoAtAAAACUAAAC6AAAAJgAqALQAAAAnACoAtAAAACgAKQC1AAAAKQ` + - `AqALQAAAAqAAAABAAAACoAAAAFAAAAKgAAAAYAAAAqAAAABwAAACoAAAA/AAAAKgAAAEIA` + - `AAAqACoAjAAAACoAFwCaAAAAKgAiAJ0AAAABAB4AAwAAAAEAJgCfAAAABAAwAAMAAAAEAA` + - `kAWgAAAAQABwBcAAAABAAIAGoAAAAEAAUAeAAAAAQADQB5AAAABAAKAKIAAAAEAAkAqgAA` + - `AAYACwB2AAAABwAeAAMAAAAHAAAAjQAAAAcAAAC8AAAACAAZALYAAAAKABoAgQAAAA4AAw` + - `BvAAAADgAEAG8AAAAQAAEAdAAAABAADwCWAAAAEgApAF0AAAASAAAAewAAABIAEAB+AAAA` + - `EgATAH8AAAASAAAAiAAAABIADgCKAAAAEgAlAIsAAAAUABAAegAAABUAAACDAAAAFQAAAI` + - `QAAAAVAAAAhQAAABUAAACGAAAAFgA2AI8AAAAWADcAsAAAABcAIwADAAAAFwAoAF4AAAAX` + - `AB4AaAAAABcANQCjAAAAFwAfAKYAAAAXAB8ApwAAABcALACoAAAAFwAtAKkAAAAXAB8Aqw` + - `AAABgAIAADAAAAHAAZAHUAAAAdAAAAlQAAAB0AFwCzAAAAHQAZALYAAAAgAB4AAwAAACIA` + - `OABpAAAAIgA5AHAAAAAiAAAAlQAAACIAOgCxAAAAIwAwAJcAAAAlADMAAwAAACUAHgCkAA` + - `AAJgAyAAMAAAAmAB4ApAAAACcAMgADAAAAJwAqAKAAAAAoADEAAwAAACgAJwBfAAAAKAAu` + - `AGcAAAAoAC4AoQAAACkAMgADAAAAKQAeAKQAAAAqAB4AAwAAACoAFABTAAAAKgAVAFQAAA` + - `AqABsAVQAAACoAHABWAAAAKgAdAFcAAAAqADQAWAAAACoAKwBbAAAAKgAeAGsAAAAqADAA` + - `bAAAACoAMABtAAAAKgAfAG4AAAAqADAAcgAAACoAEQBzAAAAKgAWAHcAAAAqAAYAfAAAAC` + - `oADAB9AAAAKgACAIAAAAAqABgAggAAACoAGQCHAAAAKgASAIkAAAAqAB4AjgAAACoAIQCR` + - `AAAAKgAeAJIAAAAqADAAkwAAACoAHgCWAAAAKgAiAJ4AAAAqACYAnwAAACoALwClAAAAKg` + - `AeAKwAAAAqADAArQAAACoAMACuAAAAKgAfAK8AAAAqACQAsgAAACoAHgC5AAAAJQAAAAAA` + - `AAAgAAAAzBQAAAkAAAB0FAAAcSQAAAAAAAAmAAAAAAAAACAAAADMFAAACQAAAIwUAACFJA` + - `AAAAAAACcAAAAAAAAAIAAAANQUAAAJAAAAnBQAAJYkAAAAAAAAKAAAAAAAAAAgAAAA3BQA` + - `AAkAAACsFAAApyQAAAAAAAApAAAAAAAAACAAAADMFAAACQAAALwUAADAJAAAAAAAACoAAA` + - `ABAAAAAQAAAAAAAAAJAAAAAAAAANEkAABiJAAAAgAAAC0kAAA0JAAAAQAAAD0kAAACAAAA` + - `RiQAADQkAAACAAAATSQAADQkAAACAAAAVCQAADQkAAACAAAAWyQAADQkAAADAAMAAQAAAM` + - `QiAAAIAAAAWwEEAFkCBQBwEDAAAAAOAAYAAQADAAAAyyIAAHgAAAAVAQBAEmISBBQAkAAI` + - `AFJTBQArA2UAAAAaAggAGgO4AHEgEAAyAFRSBABxEEMAAgAMAm4gJgASAFRRBABxEEMAAQ` + - `AMAW4gJwABAFRQBAAaAQAAcSBGABAAVFAEAHEQQwAAAAwAGgEAAG4gKQAQAFRQBABxEEMA` + - `AAAMAG4gKgBAAFRQBABxEEMAAAAMAG4QJAAAAFRQBABxEEMAAAAMAG4QJQAAAFRQBAAaAZ` + - `AAbiBUABAADAAfABYAVFEEAHEQQwABAAwBbjAhABAEDgABISisFACSAAgAASEopwAAAAED` + - `AAAAAAAKAAAAXQAAAF8AAAACAAIAAQAAAOYiAAAGAAAAWwEGAHAQMAAAAA4AAwABAAIAAA` + - `DsIgAADAAAAFQgBgBxEEMAAAAMABMBCABuICoAEAAOAAIAAgABAAAA8iIAAAYAAABbAQcA` + - `cBAwAAAADgALAAoAAQAAAPkiAAAGAAAAVBAHAG4QZAAAAA4AAgACAAEAAAAJIwAABgAAAF` + - `sBCABwEDAAAAAOAAIAAgAAAAAAECMAAAEAAAAOAAAABQAFAAAAAAAXIwAAAQAAAA4AAAAI` + - `AAUAAwAAACEjAABQAAAAchAtAAQACgBUMQgAVBEJAHEQRQABAAwBbhAzAAEACgE3EC0AVD` + - `AIAFQACQBUMQgAVBEJAHEQRQABAAwBbhAzAAEACgFyEC0ABAAKAnIwLgAUAgwBchAvAAEA` + - `DAFxIEgAEABUMAgAVAAJAHIQLwAEAAwBcSBGABAADgByEC0ABAAKAFQxCABUEQkAcRBFAA` + - `EADAFuEDMAAQAKATUQ5P8o4gIAAgABAAAANSMAAAYAAABbAQkAcBAwAAAADgAFAAEAAwAA` + - `ADwjAABOAAAAEuNUQAkAIgEXAHEARwAAAAwCcCAiACEAcSBEABAAVEAJAHEQQwAAAAwAEw` + - `EIAG4gKgAQAFRACQBxEEMAAAAMABQBkAAIAG4gJwAQACIAGABwMCsAMANUQQkAcRBDAAEA` + - `DAFuICgAAQBUQQkAVEIJAHEQQwACAAwCbjBJACEAVEAJAHEQQwAAAAwAIgEoAHAgPABBAG` + - `4gIwAQAA4AAgABAAEAAABLIwAACgAAAHAQAAABABoAAABbEBIAaQEQAA4AAgABAAAAAABT` + - `IwAAAwAAAFQQEQARAAAAAgACAAAAAABZIwAAAwAAAFsBEQARAQAAAgABAAAAAABgIwAAAw` + - `AAAFQQEgARAAAAAgACAAAAAABmIwAAAwAAAFsBEgARAQAAAQAAAAAAAABtIwAAAwAAAGIA` + - `EAARAAAAAgACAAIAAAByIwAABAAAAHAgWgAQAA4ABwADAAMAAQB5IwAAGQAAABLwcRATAA` + - `QADAFuMBIAUQYKATkBAwAPAAEQKP4NARoCCAAaA3EAcTARADIBKPUNASjzAAABAAAABwAB` + - `AAECDxceDgAAAQAAAAEAAACKIwAABgAAAGIAEABuEEoAAAAOAAQAAQADAAEAkCMAADMAAA` + - `BuEFIAAwAMAG4QUQADAAwBbhAGAAEADAETAoAAbjAKABACDABUAQAAOQEKABoACAAaAZkA` + - `cSAQABAADgBUAAAAGgFgAG4gDwAQAAwAcRA1AAAAKPQNABoBCAAaApgAcTARACEAKOsAAA` + - `AAAAApAAEAAQEeKgIAAQACAAAAoSMAAAkAAAAiACkAcCBAABAAbiBeAAEADgAAAAIAAQAC` + - `AAAAqiMAAAYAAABiABAAbiBLABAADgACAAEAAgAAALIjAAAGAAAAYgAQAG4gTAAQAA4AAg` + - `ABAAIAAAC6IwAABgAAAGIAEABuIE0AEAAOAAQAAQADAAAAwSMAACQAAAAaAJAAbiBUAAMA` + - `DAAfABYAFAECAAIBbiBPABMADAFuEBYAAQAMAW4QGQABAAwBEgJuMCAAEAIiACYAcCA4AD` + - `AAbiBeAAMADgAGAAIAAwAAAMojAABXAAAAEhMiAAQAGgFiAHAgAgAQABoBZgBuIDIAUQAK` + - `ATgBHABgAQMAEwIVADQhFgAiAAQAGgFjAHAgAgAQAG4gBAAwABoBQABxIAUAEAAMAG4wYw` + - `AEAw4AGgG9AG4gMQAVAAoBOAEeAGABAwATAhMANCEYABoBAgBuIAkAEAAaAWUAGgJSAG4g` + - `NAAlAAwCbjAIABACGgFkAG4gAwAQACjTbiAJAFAAGgFkAG4gAwAQACjKAAAFAAIAAwAAAN` + - `4jAAA5AAAAIgAEABoBYQBwIAIAEAAaAb0AbiAxABQACgE4ASgAYAEDABMCEwA0ISIAGgEC` + - `AG4gCQAQABoBZQAaAlIAbiA0ACQADAJuMAgAEAIaAWQAbiADABAAGgFDAHEgBQAQAAwAEi` + - `FuMGMAAwEOAG4gCQBAACjtAAADAAIAAwAAAOwjAAAJAAAAIgAlAHAwNgAQAm4gXgABAA4A` + - `AAACAAEAAQAAAPUjAAAJAAAAbhBQAAEADABuECwAAAAMABEAAAAFAAQAAgAAAPojAAAcAA` + - `AAEhAyAgYAEiAyAgMADgAS8DIDCAAaAAAAcCBOAAEAKPduEAcABAAMAG4QDgAAAAwAcCBO` + - `AAEAKOsEAAIAAgAAAAwkAAAdAAAAcBBbAAIAbyABADIAcBBfAAIAFAACAAIBbiBPAAIADA` + - `BuEBYAAAAMACIBJwBwIDoAIQBuIBQAEAAOAAAABwABAAUAAQAYJAAAYAAAAG4QVgAGAAwA` + - `bhAbAAAADABuEBcAAAAMADkAAwAOAG4QHwAAAAoBbhAcAAAACgJuEB0AAAAKA24QHgAAAA` + - `oAcFBYABYyKOwNACIABwBwEAsAAABuEFYABgAMAW4QGwABAAwBbiAaAAEAFAECAAIBbiBP` + - `ABYADAFuEBYAAQAMAVICAgBuEBUAAQAKA24QDAAAAAoEsUNSBAIAsUNSBAEAbhAYAAEACg` + - `FuEA0AAAAKBbFRUgABAJEAAQBwUFgAJkMorwAAAAAiAAEAAQEfI2QLAAAAAAAAAQAAAAAA` + - `AAA2AAAAcAsAAHgLAAAAAAAAAAAAAAAAAACECwAAAAAAAAAAAAAAAAAAkAsAAAAAAAAAAA` + - `AAAAAAAJwLAAAAAAAAAAAAAAAAAAABAAAAIQAAAAEAAAARAAAAAQAAAA0AAAACAAAAAAAA` + - `AAMAAAAAAAAAAAAAAAIAAAAiACIAAwAAACIAIgAkAAAAAQAAAAAAAAACAAAABAAdAAEAAA` + - `AiAAAAAgAAACIALQACAAAAAgAAAAEAAAAqAAAAAgAAACoAFwACAAAAKgAiAAQAAAAAAAAA` + - `AAAAAAMAAAAAAAAABAAAAAEAAAADAAAAAgAAAAQAAAABAAAABwAAAAEAAAAKAAAAAQAAAA` + - `wAAAAJAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAACAAAAEgATAAEAAAATAAAAAQAAAB0AAAAE` + - `AAAAHQAAAAAAAAABAAAAKQAAAAIAAAAqAAAAAgAAAAsAAAACAAAAEgAAAAEAAAAgAAAAAy` + - `gpVgADKi8qAAY8aW5pdD4AEkRFRkFVTFRfSU5QVVRfVFlQRQAVREVGQVVMVF9LRVlCT0FS` + - `RF9DT0RFAA5GSUxFX09QRU5fQ09ERQAORklMRV9TQVZFX0NPREUABEZ5bmUAFUdvTmF0aX` + - `ZlQWN0aXZpdHkuamF2YQABSQADSUlJAARJSUlJAANJTEwABElMTEwAAUwAAkxJAANMSUkA` + - `AkxMAANMTEkAA0xMTAAcTGFuZHJvaWQvYXBwL05hdGl2ZUFjdGl2aXR5OwAfTGFuZHJvaW` + - `QvY29udGVudC9Db21wb25lbnROYW1lOwAZTGFuZHJvaWQvY29udGVudC9Db250ZXh0OwAY` + - `TGFuZHJvaWQvY29udGVudC9JbnRlbnQ7ACFMYW5kcm9pZC9jb250ZW50L3BtL0FjdGl2aX` + - `R5SW5mbzsAI0xhbmRyb2lkL2NvbnRlbnQvcG0vUGFja2FnZU1hbmFnZXI7ABdMYW5kcm9p` + - `ZC9ncmFwaGljcy9SZWN0OwARTGFuZHJvaWQvbmV0L1VyaTsAGkxhbmRyb2lkL29zL0J1aW` + - `xkJFZFUlNJT047ABNMYW5kcm9pZC9vcy9CdW5kbGU7ABRMYW5kcm9pZC9vcy9JQmluZGVy` + - `OwAXTGFuZHJvaWQvdGV4dC9FZGl0YWJsZTsAGkxhbmRyb2lkL3RleHQvVGV4dFdhdGNoZX` + - `I7ABJMYW5kcm9pZC91dGlsL0xvZzsAM0xhbmRyb2lkL3ZpZXcvS2V5Q2hhcmFjdGVyTWFw` + - `JFVuYXZhaWxhYmxlRXhjZXB0aW9uOwAeTGFuZHJvaWQvdmlldy9LZXlDaGFyYWN0ZXJNYX` + - `A7ACpMYW5kcm9pZC92aWV3L1ZpZXckT25MYXlvdXRDaGFuZ2VMaXN0ZW5lcjsAE0xhbmRy` + - `b2lkL3ZpZXcvVmlldzsAJUxhbmRyb2lkL3ZpZXcvVmlld0dyb3VwJExheW91dFBhcmFtcz` + - `sAFUxhbmRyb2lkL3ZpZXcvV2luZG93OwAbTGFuZHJvaWQvdmlldy9XaW5kb3dJbnNldHM7` + - `AC1MYW5kcm9pZC92aWV3L2lucHV0bWV0aG9kL0lucHV0TWV0aG9kTWFuYWdlcjsAGUxhbm` + - `Ryb2lkL3dpZGdldC9FZGl0VGV4dDsAKUxhbmRyb2lkL3dpZGdldC9GcmFtZUxheW91dCRM` + - `YXlvdXRQYXJhbXM7ACNMZGFsdmlrL2Fubm90YXRpb24vRW5jbG9zaW5nTWV0aG9kOwAeTG` + - `RhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7AB1MZGFsdmlrL2Fubm90YXRpb24vU2ln` + - `bmF0dXJlOwAOTGphdmEvaW8vRmlsZTsAGExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOwAVTG` + - `phdmEvbGFuZy9FeGNlcHRpb247AB1MamF2YS9sYW5nL05vU3VjaE1ldGhvZEVycm9yOwAS` + - `TGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy` + - `9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwAj` + - `TG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHkkMTsAI0xvcmcvZ29sYW5nL2FwcC` + - `9Hb05hdGl2ZUFjdGl2aXR5JDI7ACNMb3JnL2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdml0` + - `eSQzOwAlTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHkkNCQxOwAjTG9yZy9nb2` + - `xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHkkNDsAIUxvcmcvZ29sYW5nL2FwcC9Hb05hdGl2` + - `ZUFjdGl2aXR5OwAUTlVNQkVSX0tFWUJPQVJEX0NPREUACU9wZW4gRmlsZQAHU0RLX0lOVA` + - `AYU0lOR0xFTElORV9LRVlCT0FSRF9DT0RFAAlTYXZlIEZpbGUAAVYAAlZJAANWSUkABVZJ` + - `SUlJAARWSUlMAAJWTAADVkxJAAVWTElJSQAKVkxJSUlJSUlJSQADVkxMAAFaAAJaTAADWk` + - `xJABNbTGphdmEvbGFuZy9TdHJpbmc7AAJcfAAKYWNjZXNzJDAwMAAKYWNjZXNzJDAwMgAK` + - `YWNjZXNzJDEwMAAKYWNjZXNzJDEwMgAKYWNjZXNzJDIwMAAKYWNjZXNzJDMwMAALYWNjZX` + - `NzRmxhZ3MAC2FkZENhdGVnb3J5AA5hZGRDb250ZW50VmlldwAIYWRkRmxhZ3MAGWFkZE9u` + - `TGF5b3V0Q2hhbmdlTGlzdGVuZXIAFmFkZFRleHRDaGFuZ2VkTGlzdGVuZXIAEGFmdGVyVG` + - `V4dENoYW5nZWQAFGFuZHJvaWQuYXBwLmxpYl9uYW1lACVhbmRyb2lkLmludGVudC5hY3Rp` + - `b24uQ1JFQVRFX0RPQ1VNRU5UACNhbmRyb2lkLmludGVudC5hY3Rpb24uT1BFTl9ET0NVTU` + - `VOVAAoYW5kcm9pZC5pbnRlbnQuYWN0aW9uLk9QRU5fRE9DVU1FTlRfVFJFRQAgYW5kcm9p` + - `ZC5pbnRlbnQuY2F0ZWdvcnkuT1BFTkFCTEUAH2FuZHJvaWQuaW50ZW50LmV4dHJhLk1JTU` + - `VfVFlQRVMAF2FwcGxpY2F0aW9uL3gtZGlyZWN0b3J5ABFiZWZvcmVUZXh0Q2hhbmdlZAAM` + - `YnJpbmdUb0Zyb250AAhjb250YWlucwANY3JlYXRlQ2hvb3NlcgAOZG9IaWRlS2V5Ym9hcm` + - `QADmRvU2hvd0ZpbGVPcGVuAA5kb1Nob3dGaWxlU2F2ZQAOZG9TaG93S2V5Ym9hcmQAAWUA` + - `BmVxdWFscwAhZXhjZXB0aW9uIHJlYWRpbmcgS2V5Q2hhcmFjdGVyTWFwABJmaWxlUGlja2` + - `VyUmV0dXJuZWQADGZpbmRWaWV3QnlJZAADZ2V0AA9nZXRBYnNvbHV0ZVBhdGgAD2dldEFj` + - `dGl2aXR5SW5mbwALZ2V0Q2FjaGVEaXIADGdldENvbXBvbmVudAAHZ2V0RGF0YQAMZ2V0RG` + - `Vjb3JWaWV3AAlnZXRIZWlnaHQACWdldEludGVudAARZ2V0UGFja2FnZU1hbmFnZXIAC2dl` + - `dFJvb3RWaWV3ABNnZXRSb290V2luZG93SW5zZXRzAAdnZXRSdW5lAAlnZXRTdHJpbmcAEG` + - `dldFN5c3RlbVNlcnZpY2UAGmdldFN5c3RlbVdpbmRvd0luc2V0Qm90dG9tABhnZXRTeXN0` + - `ZW1XaW5kb3dJbnNldExlZnQAGWdldFN5c3RlbVdpbmRvd0luc2V0UmlnaHQAF2dldFN5c3` + - `RlbVdpbmRvd0luc2V0VG9wAAlnZXRUbXBkaXIACGdldFdpZHRoAAlnZXRXaW5kb3cADmdl` + - `dFdpbmRvd1Rva2VuABxnZXRXaW5kb3dWaXNpYmxlRGlzcGxheUZyYW1lABBnb05hdGl2ZU` + - `FjdGl2aXR5AAZoZWlnaHQADGhpZGVLZXlib2FyZAAXaGlkZVNvZnRJbnB1dEZyb21XaW5k` + - `b3cADGlucHV0X21ldGhvZAANaW5zZXRzQ2hhbmdlZAAOa2V5Ym9hcmREZWxldGUADWtleW` + - `JvYXJkVHlwZWQABGxlZnQABmxlbmd0aAAEbG9hZAALbG9hZExpYnJhcnkAEmxvYWRMaWJy` + - `YXJ5IGZhaWxlZAAnbG9hZExpYnJhcnk6IG5vIG1hbmlmZXN0IG1ldGFkYXRhIGZvdW5kAA` + - `ltVGV4dEVkaXQACG1ldGFEYXRhAARuYW1lAAhvbGRTdGF0ZQAQb25BY3Rpdml0eVJlc3Vs` + - `dAAIb25DcmVhdGUADm9uTGF5b3V0Q2hhbmdlAA1vblRleHRDaGFuZ2VkAAhwdXRFeHRyYQ` + - `AMcmVxdWVzdEZvY3VzAANydW4ADXJ1bk9uVWlUaHJlYWQADXNldEltZU9wdGlvbnMADHNl` + - `dElucHV0VHlwZQAPc2V0TGF5b3V0UGFyYW1zAAdzZXRUZXh0AAdzZXRUeXBlAA1zZXRWaX` + - `NpYmlsaXR5AApzZXR1cEVudHJ5AAxzaG93RmlsZU9wZW4ADHNob3dGaWxlU2F2ZQAMc2hv` + - `d0tleWJvYXJkAA1zaG93U29mdElucHV0AAVzcGxpdAAWc3RhcnRBY3Rpdml0eUZvclJlc3` + - `VsdAALc3ViU2VxdWVuY2UABnRoaXMkMAAGdGhpcyQxAAh0b1N0cmluZwADdG9wACJ1bmtu` + - `b3duIGtleWJvYXJkIHR5cGUsIHVzZSBkZWZhdWx0AAx1cGRhdGVMYXlvdXQAEHZhbCRrZX` + - `lib2FyZFR5cGUABXZhbHVlAAV3aWR0aAABfABPAgAABw4AUgAHdxACDll5lpd4tJaWl6WW` + - `Am0sIEsCdB0AfAEABw4AfwAHDrQA1AEBAAcOANcBCQAAAAAAAAAAAAcOWgDpAQEABw4A+w` + - `EBAAcOAPcBBAAAAAAHDgDsAQQAAAAABw4BEg8BHxO0AnsdAN0BAQAHDgDgAQAHHeG0xFuW` + - `tQIU4AAvAAcOOE4tABoBAAcOABoCAAAHDgAaAQAHDgAaAgAABw4AGgAHDgAaAgAABw4AqQ` + - `EDAAAABx2HNAJ7LCAegwB0AAcOWgDAAQAHDkujTEt/Ansdh0seAN0BAAcOAiKGAIUBAQAH` + - `DloAmQEBAAcOWgBLAQAHDloAeAAHDoe0iIwAiQEBAAcdeOF4RJYCdx3hWrRqPACdAQEABw` + - `544Vq3WqUZAE8BAAcOAiKGADQABw4AhAIDAAAABw4CDGgCeR08bEsAzwEBAAcOPDw9tIwA` + - `OQAHDsMCDiwCdh2HhUweWrW0/9AAAhkBuwEaTQIaAlkEAJwBHgIbAbsBHAEXAQIZAbsBGk` + - `oCGQG7ARpdAhkBuwEaQQIZAbsBGl8GRJAACAQABAEEAgQCBAEAAgEBBJAgAZAgNoCABKgX` + - `NwHIFwABAQEGkCA4gIAEyBk5AeQZAAEBAQeQIDqAgASMGjsBqBoAAQEDCJAgPICABMQaPQ` + - `HgGgEB9BoBAYgbAAEBAQmQIECAgAS4HEEB1BwHAhIIChoBGgEaARoBGgEaAQoRAgECQoGA` + - `BIAeAYggpB4BiCC8HgGIINQeAYgg7B4BiCCEHwGIIJwfBoICAAUItB8ECIggAYICAAGCAg` + - `ABggIAAQKkIAQCqCEBCMwhAQjoIQEIhCJKAKAiAQD4IgEAuCQBALwlCADgJQcEhCYBAcwm` + - `BwCYJwARAAAAAAAAAAEAAAAAAAAAAQAAAL4AAABwAAAAAgAAAC4AAABoAwAAAwAAADsAAA` + - `AgBAAABAAAABMAAADkBgAABQAAAGUAAAB8BwAABgAAAAYAAACkCgAAAxAAAAYAAABkCwAA` + - `ASAAACIAAACoCwAABiAAAAUAAAB0FAAAARAAACAAAADMFAAAAiAAAL4AAADuFQAAAyAAAC` + - `IAAADEIgAABCAAAAcAAAAtJAAABSAAAAEAAABiJAAAACAAAAYAAABxJAAAABAAAAEAAABY` + - `JQAA` + +var dexStr = `ZGV4CjAzNQAM8J6VgTepd211FPPtsu4FeyftNcBZIIp0JgAAcAAAAHhWNBIAAAAAAAAAAK` + + `QlAAC/AAAAcAAAAC4AAABsAwAAPQAAACQEAAATAAAAAAcAAGYAAACYBwAABgAAAMgKAADs` + + `GgAAiAsAABoWAAAcFgAAIRYAACYWAAAuFgAAQhYAAFkWAABpFgAAeRYAAH8WAACWFgAAmR` + + `YAAJ4WAACkFgAAqRYAAK8WAACyFgAAthYAALsWAAC/FgAAxBYAAMkWAADnFgAACBcAACMX` + + `AAA9FwAAYBcAAIUXAACeFwAAsRcAAM0XAADiFwAA+BcAABEYAAAtGAAAQRgAAHYYAACWGA` + + `AAwhgAANcYAAD+GAAAFRkAADIZAABhGQAAfBkAAKcZAADMGQAA7BkAAAsaAAAbGgAANRoA` + + `AEwaAABrGgAAfxoAAJUaAACpGgAAvRoAANQaAAD5GgAAHhsAAEMbAABqGwAAjxsAALIbAA` + + `DIGwAA0xsAANwbAAD2GwAAARwAAAQcAAAIHAAADRwAABQcAAAaHAAAHhwAACMcAAAqHAAA` + + `NhwAADscAAA+HAAAQhwAAEccAABcHAAAYBwAAGwcAAB4HAAAhBwAAJAcAACcHAAAqBwAAL` + + `UcAADCHAAA0hwAANwcAAD3HAAADx0AACEdAAA3HQAAXh0AAIMdAACtHQAAzx0AAPAdAAAM` + + `HgAAJR4AADgeAABGHgAAUB4AAF8eAABvHgAAfx4AAI8eAACfHgAAoh4AAKoeAADNHgAA4R` + + `4AAO8eAAD0HgAABR8AABYfAAAjHwAAMR8AADofAABIHwAAUx8AAF4fAABxHwAAfh8AAJMf` + + `AACcHwAApx8AALkfAADVHwAA7x8AAAogAAAjIAAALiAAADggAABDIAAAUyAAAHEgAACDIA` + + `AAiyAAAJkgAACyIAAAwCAAAM8gAADfIAAA7iAAAPQgAAD8IAAAAiEAAA8hAAAjIQAATCEA` + + `AFchAABhIQAAZyEAAHEhAACDIQAAjSEAAJ0hAACsIQAAtiEAAMQhAADJIQAA2CEAAOchAA` + + `D1IQAABiIAAA8iAAAYIgAAJyIAADMiAABBIgAATyIAAF0iAABsIgAAcyIAAIsiAACYIgAA` + + `oCIAAKgiAACyIgAAtyIAANsiAADpIgAA+yIAAAIjAAAJIwAACgAAABUAAAAWAAAAFwAAAB` + + `gAAAAZAAAAGgAAABsAAAAcAAAAHQAAAB4AAAAfAAAAIAAAACEAAAAiAAAAIwAAACQAAAAl` + + `AAAAJgAAACcAAAAoAAAAKQAAACoAAAArAAAALAAAAC0AAAAuAAAALwAAADAAAAAxAAAAMg` + + `AAADMAAAA0AAAANQAAADYAAAA3AAAAOAAAADkAAAA6AAAAOwAAADwAAAA9AAAAPgAAAEQA` + + `AABOAAAAUQAAAAoAAAAAAAAAAAAAAAsAAAAAAAAAEBUAAAwAAAAAAAAAGBUAAA0AAAAAAA` + + `AAJBUAAA4AAAAAAAAALBUAAA8AAAACAAAAAAAAAA8AAAAEAAAAAAAAABAAAAAEAAAAOBUA` + + `ABQAAAAEAAAAQBUAABIAAAAEAAAASBUAABQAAAAEAAAAJBUAABQAAAAEAAAAUBUAABMAAA` + + `AFAAAAWBUAAA8AAAAGAAAAAAAAAA8AAAAIAAAAAAAAAA8AAAALAAAAAAAAABAAAAAQAAAA` + + `OBUAAA8AAAASAAAAAAAAABAAAAASAAAAOBUAAA8AAAAUAAAAAAAAAA8AAAAVAAAAAAAAAB` + + `IAAAAXAAAAYBUAABQAAAAXAAAAaBUAAA8AAAAcAAAAAAAAABEAAAAdAAAAEBUAABIAAAAg` + + `AAAASBUAAA8AAAAiAAAAAAAAABIAAAAiAAAASBUAABIAAAAiAAAAYBUAABQAAAAiAAAAcB` + + `UAAA8AAAAqAAAAAAAAAEQAAAArAAAAAAAAAEUAAAArAAAAOBUAAEYAAAArAAAAEBUAAEcA` + + `AAArAAAAeBUAAEgAAAArAAAAhBUAAEkAAAArAAAAkBUAAEoAAAArAAAAmBUAAEkAAAArAA` + + `AAoBUAAEkAAAArAAAAqBUAAEkAAAArAAAAsBUAAEkAAAArAAAACBUAAEkAAAArAAAAABUA` + + `AEwAAAArAAAAuBUAAE0AAAArAAAA0BUAAEkAAAArAAAA2BUAAEkAAAArAAAA4BUAAEsAAA` + + `ArAAAA6BUAAEkAAAArAAAA+BQAAEkAAAArAAAASBUAAE0AAAArAAAAJBUAAEkAAAArAAAA` + + `9BUAAEkAAAArAAAAYBUAAEoAAAArAAAA/BUAAE0AAAArAAAAcBUAAE4AAAAsAAAAAAAAAF` + + `AAAAAsAAAABBYAAFAAAAAsAAAADBYAAE8AAAAsAAAA4BUAAE8AAAAsAAAAFBYAABIAAAAt` + + `AAAASBUAAAUACgCcAAAABwAAAJUAAAAHAAAAuAAAAAkAAABBAAAAJQAqALUAAAAlAAAAuw` + + `AAACYAKgC1AAAAJwAqALUAAAAoACkAtgAAACkAKgC1AAAAKgAAAAQAAAAqAAAABQAAACoA` + + `AAAGAAAAKgAAAAcAAAAqAAAAPwAAACoAAABCAAAAKgAqAI0AAAAqABcAmwAAACoAIgCeAA` + + `AAAQAfAAMAAAABACcAoAAAAAQAMQADAAAABAAJAFoAAAAEAAcAXAAAAAQACABrAAAABAAF` + + `AHkAAAAEAA4AegAAAAQACgCjAAAABAALAKMAAAAEAAkAqwAAAAYADAB3AAAABwAfAAMAAA` + + `AHAAAAjgAAAAcAAAC9AAAACAAaALcAAAAKABsAggAAAA4AAwBwAAAADgAEAHAAAAAQAAEA` + + `dQAAABAAEACXAAAAEgAqAF0AAAASAAAAfAAAABIAEQB/AAAAEgAUAIAAAAASAAAAiQAAAB` + + `IADwCLAAAAEgAmAIwAAAAUABEAewAAABUAAACEAAAAFQAAAIUAAAAVAAAAhgAAABUAAACH` + + `AAAAFgA4AJAAAAAWADkAsQAAABcAJAADAAAAFwApAF4AAAAXAB8AaQAAABcANwCkAAAAFw` + + `AgAKcAAAAXACAAqAAAABcALQCpAAAAFwAuAKoAAAAXACAArAAAABgAIQADAAAAHAAaAHYA` + + `AAAdAAAAlgAAAB0AGAC0AAAAHQAaALcAAAAgAB8AAwAAACIAOgBqAAAAIgA7AHEAAAAiAA` + + `AAlgAAACIAPACyAAAAIwAxAJgAAAAlADUAAwAAACUAHwClAAAAJgA0AAMAAAAmAB8ApQAA` + + `ACcANAADAAAAJwArAKEAAAAoADMAAwAAACgAKABfAAAAKAAvAGgAAAAoAC8AogAAACkANA` + + `ADAAAAKQAfAKUAAAAqAB8AAwAAACoAFQBTAAAAKgAWAFQAAAAqABwAVQAAACoAHQBWAAAA` + + `KgAeAFcAAAAqADYAWAAAACoALABbAAAAKgAfAGwAAAAqADEAbQAAACoAMgBuAAAAKgAgAG` + + `8AAAAqADEAcwAAACoAEgB0AAAAKgAXAHgAAAAqAAYAfQAAACoADQB+AAAAKgACAIEAAAAq` + + `ABkAgwAAACoAGgCIAAAAKgATAIoAAAAqAB8AjwAAACoAIgCSAAAAKgAfAJMAAAAqADEAlA` + + `AAACoAHwCXAAAAKgAjAJ8AAAAqACcAoAAAACoAMACmAAAAKgAfAK0AAAAqADEArgAAACoA` + + `MgCvAAAAKgAgALAAAAAqACUAswAAACoAHwC6AAAAJQAAAAAAAAAgAAAA+BQAAAkAAACgFA` + + `AAviQAAAAAAAAmAAAAAAAAACAAAAD4FAAACQAAALgUAADSJAAAAAAAACcAAAAAAAAAIAAA` + + `AAAVAAAJAAAAyBQAAOMkAAAAAAAAKAAAAAAAAAAgAAAACBUAAAkAAADYFAAA9CQAAAAAAA` + + `ApAAAAAAAAACAAAAD4FAAACQAAAOgUAAANJQAAAAAAACoAAAABAAAAAQAAAAAAAAAJAAAA` + + `AAAAAB4lAACvJAAAAgAAAHokAACBJAAAAQAAAIokAAACAAAAkyQAAIEkAAACAAAAmiQAAI` + + `EkAAACAAAAoSQAAIEkAAACAAAAqCQAAIEkAAADAAMAAQAAAAwjAAAIAAAAWwEEAFkCBQBw` + + `EDEAAAAOAAYAAQADAAAAEyMAAHgAAAAVAQBAEmISBBQAkAAIAFJTBQArA2UAAAAaAggAGg` + + `O5AHEgEQAyAFRSBABxEEQAAgAMAm4gJwASAFRRBABxEEQAAQAMAW4gKAABAFRQBAAaAQAA` + + `cSBHABAAVFAEAHEQRAAAAAwAGgEAAG4gKgAQAFRQBABxEEQAAAAMAG4gKwBAAFRQBABxEE` + + `QAAAAMAG4QJQAAAFRQBABxEEQAAAAMAG4QJgAAAFRQBAAaAZEAbiBVABAADAAfABYAVFEE` + + `AHEQRAABAAwBbjAiABAEDgABISisFACSAAgAASEopwAAAAEDAAAAAAAKAAAAXQAAAF8AAA` + + `ACAAIAAQAAAC4jAAAGAAAAWwEGAHAQMQAAAA4AAwABAAIAAAA0IwAADAAAAFQgBgBxEEQA` + + `AAAMABMBCABuICsAEAAOAAIAAgABAAAAOiMAAAYAAABbAQcAcBAxAAAADgALAAoAAQAAAE` + + `EjAAAGAAAAVBAHAG4QZQAAAA4AAgACAAEAAABRIwAABgAAAFsBCABwEDEAAAAOAAIAAgAA` + + `AAAAWCMAAAEAAAAOAAAABQAFAAAAAABfIwAAAQAAAA4AAAAIAAUAAwAAAGkjAABQAAAAch` + + `AuAAQACgBUMQgAVBEJAHEQRgABAAwBbhA0AAEACgE3EC0AVDAIAFQACQBUMQgAVBEJAHEQ` + + `RgABAAwBbhA0AAEACgFyEC4ABAAKAnIwLwAUAgwBchAwAAEADAFxIEkAEABUMAgAVAAJAH` + + `IQMAAEAAwBcSBHABAADgByEC4ABAAKAFQxCABUEQkAcRBGAAEADAFuEDQAAQAKATUQ5P8o` + + `4gIAAgABAAAAfSMAAAYAAABbAQkAcBAxAAAADgAFAAEAAwAAAIQjAABOAAAAEuNUQAkAIg` + + `EXAHEASAAAAAwCcCAjACEAcSBFABAAVEAJAHEQRAAAAAwAEwEIAG4gKwAQAFRACQBxEEQA` + + `AAAMABQBkAAIAG4gKAAQACIAGABwMCwAMANUQQkAcRBEAAEADAFuICkAAQBUQQkAVEIJAH` + + `EQRAACAAwCbjBKACEAVEAJAHEQRAAAAAwAIgEoAHAgPQBBAG4gJAAQAA4AAgABAAEAAACT` + + `IwAACgAAAHAQAAABABoAAABbEBIAaQEQAA4AAgABAAAAAACbIwAAAwAAAFQQEQARAAAAAg` + + `ACAAAAAAChIwAAAwAAAFsBEQARAQAAAgABAAAAAACoIwAAAwAAAFQQEgARAAAAAgACAAAA` + + `AACuIwAAAwAAAFsBEgARAQAAAQAAAAAAAAC1IwAAAwAAAGIAEAARAAAAAgACAAIAAAC6Iw` + + `AABAAAAHAgWwAQAA4ABwADAAMAAQDBIwAAGQAAABLwcRAUAAQADAFuMBMAUQYKATkBAwAP` + + `AAEQKP4NARoCCAAaA3IAcTASADIBKPUNASjzAAABAAAABwABAAECDxceDgAAAQAAAAEAAA` + + `DSIwAABgAAAGIAEABuEEsAAAAOAAQAAQADAAEA2CMAADMAAABuEFMAAwAMAG4QUgADAAwB` + + `bhAGAAEADAETAoAAbjALABACDABUAQAAOQEKABoACAAaAZoAcSARABAADgBUAAAAGgFgAG` + + `4gEAAQAAwAcRA2AAAAKPQNABoBCAAaApkAcTASACEAKOsAAAAAAAApAAEAAQEeKgIAAQAC` + + `AAAA6SMAAAkAAAAiACkAcCBBABAAbiBfAAEADgAAAAIAAQACAAAA8iMAAAYAAABiABAAbi` + + `BMABAADgADAAIAAwAAAPojAAAGAAAAYgAQAG4wTQAQAg4AAgABAAIAAAADJAAABgAAAGIA` + + `EABuIE4AEAAOAAQAAQADAAAACiQAACQAAAAaAJEAbiBVAAMADAAfABYAFAECAAIBbiBQAB` + + `MADAFuEBcAAQAMAW4QGgABAAwBEgJuMCEAEAIiACYAcCA5ADAAbiBfAAMADgAGAAIAAwAA` + + `ABMkAABXAAAAEhMiAAQAGgFiAHAgAgAQABoBZwBuIDMAUQAKATgBHABgAQMAEwIVADQhFg` + + `AiAAQAGgFjAHAgAgAQAG4gBAAwABoBQABxIAUAEAAMAG4wZAAEAw4AGgG+AG4gMgAVAAoB` + + `OAEeAGABAwATAhMANCEYABoBAgBuIAoAEAAaAWUAGgJSAG4gNQAlAAwCbjAJABACGgFkAG` + + `4gAwAQACjTbiAKAFAAGgFkAG4gAwAQACjKAAAGAAMAAwAAACckAAA+AAAAIgAEABoBYQBw` + + `IAIAEAAaAb4AbiAyABQACgE4AS0AYAEDABMCEwA0IScAGgECAG4gCgAQABoBZQAaAlIAbi` + + `A1ACQADAJuMAkAEAIaAWYAbjAIABAFGgFkAG4gAwAQABoBQwBxIAUAEAAMABIhbjBkAAMB` + + `DgBuIAoAQAAo6AMAAgADAAAAOSQAAAkAAAAiACUAcDA3ABACbiBfAAEADgAAAAIAAQABAA` + + `AAQiQAAAkAAABuEFEAAQAMAG4QLQAAAAwAEQAAAAUABAACAAAARyQAABwAAAASEDICBgAS` + + `IDICAwAOABLwMgMIABoAAABwIE8AAQAo924QBwAEAAwAbhAPAAAADABwIE8AAQAo6wQAAg` + + `ACAAAAWSQAAB0AAABwEFwAAgBvIAEAMgBwEGAAAgAUAAIAAgFuIFAAAgAMAG4QFwAAAAwA` + + `IgEnAHAgOwAhAG4gFQAQAA4AAAAHAAEABQABAGUkAABgAAAAbhBXAAYADABuEBwAAAAMAG` + + `4QGAAAAAwAOQADAA4AbhAgAAAACgFuEB0AAAAKAm4QHgAAAAoDbhAfAAAACgBwUFkAFjIo` + + `7A0AIgAHAHAQDAAAAG4QVwAGAAwBbhAcAAEADAFuIBsAAQAUAQIAAgFuIFAAFgAMAW4QFw` + + `ABAAwBUgICAG4QFgABAAoDbhANAAAACgSxQ1IEAgCxQ1IEAQBuEBkAAQAKAW4QDgAAAAoF` + + `sVFSAAEAkQABAHBQWQAmQyivAAAAACIAAQABAR8jiAsAAAAAAAABAAAAAAAAADcAAACUCw` + + `AAnAsAAAAAAAAAAAAAAAAAAKgLAAAAAAAAAAAAAAAAAAC0CwAAAAAAAAAAAAAAAAAAwAsA` + + `AAAAAAAAAAAAAAAAAAEAAAAhAAAAAQAAABEAAAABAAAADQAAAAIAAAAAAAAAAwAAAAAAAA` + + `AAAAAAAgAAACIAIgADAAAAIgAiACQAAAABAAAAAAAAAAIAAAAEAB0AAQAAACIAAAACAAAA` + + `IgAtAAIAAAACAAAAAQAAACoAAAACAAAAKgAXAAIAAAAqACIABAAAAAAAAAAAAAAAAwAAAA` + + `AAAAAEAAAAAQAAAAMAAAACAAAABAAAAAEAAAAHAAAAAQAAAAoAAAABAAAADAAAAAkAAAAS` + + `AAAAAAAAAAAAAAAAAAAAAAAAAAIAAAASABMAAQAAABMAAAABAAAAHQAAAAQAAAAdAAAAAA` + + `AAAAEAAAApAAAAAgAAACoAAAACAAAACwAAAAIAAAASAAAAAQAAACAAAAADKClWAAMqLyoA` + + `Bjxpbml0PgASREVGQVVMVF9JTlBVVF9UWVBFABVERUZBVUxUX0tFWUJPQVJEX0NPREUADk` + + `ZJTEVfT1BFTl9DT0RFAA5GSUxFX1NBVkVfQ09ERQAERnluZQAVR29OYXRpdmVBY3Rpdml0` + + `eS5qYXZhAAFJAANJSUkABElJSUkAA0lMTAAESUxMTAABTAACTEkAA0xJSQACTEwAA0xMSQ` + + `ADTExMABxMYW5kcm9pZC9hcHAvTmF0aXZlQWN0aXZpdHk7AB9MYW5kcm9pZC9jb250ZW50` + + `L0NvbXBvbmVudE5hbWU7ABlMYW5kcm9pZC9jb250ZW50L0NvbnRleHQ7ABhMYW5kcm9pZC` + + `9jb250ZW50L0ludGVudDsAIUxhbmRyb2lkL2NvbnRlbnQvcG0vQWN0aXZpdHlJbmZvOwAj` + + `TGFuZHJvaWQvY29udGVudC9wbS9QYWNrYWdlTWFuYWdlcjsAF0xhbmRyb2lkL2dyYXBoaW` + + `NzL1JlY3Q7ABFMYW5kcm9pZC9uZXQvVXJpOwAaTGFuZHJvaWQvb3MvQnVpbGQkVkVSU0lP` + + `TjsAE0xhbmRyb2lkL29zL0J1bmRsZTsAFExhbmRyb2lkL29zL0lCaW5kZXI7ABdMYW5kcm` + + `9pZC90ZXh0L0VkaXRhYmxlOwAaTGFuZHJvaWQvdGV4dC9UZXh0V2F0Y2hlcjsAEkxhbmRy` + + `b2lkL3V0aWwvTG9nOwAzTGFuZHJvaWQvdmlldy9LZXlDaGFyYWN0ZXJNYXAkVW5hdmFpbG` + + `FibGVFeGNlcHRpb247AB5MYW5kcm9pZC92aWV3L0tleUNoYXJhY3Rlck1hcDsAKkxhbmRy` + + `b2lkL3ZpZXcvVmlldyRPbkxheW91dENoYW5nZUxpc3RlbmVyOwATTGFuZHJvaWQvdmlldy` + + `9WaWV3OwAlTGFuZHJvaWQvdmlldy9WaWV3R3JvdXAkTGF5b3V0UGFyYW1zOwAVTGFuZHJv` + + `aWQvdmlldy9XaW5kb3c7ABtMYW5kcm9pZC92aWV3L1dpbmRvd0luc2V0czsALUxhbmRyb2` + + `lkL3ZpZXcvaW5wdXRtZXRob2QvSW5wdXRNZXRob2RNYW5hZ2VyOwAZTGFuZHJvaWQvd2lk` + + `Z2V0L0VkaXRUZXh0OwApTGFuZHJvaWQvd2lkZ2V0L0ZyYW1lTGF5b3V0JExheW91dFBhcm` + + `FtczsAI0xkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdNZXRob2Q7AB5MZGFsdmlrL2Fu` + + `bm90YXRpb24vSW5uZXJDbGFzczsAHUxkYWx2aWsvYW5ub3RhdGlvbi9TaWduYXR1cmU7AA` + + `5MamF2YS9pby9GaWxlOwAYTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7ABVMamF2YS9sYW5n` + + `L0V4Y2VwdGlvbjsAHUxqYXZhL2xhbmcvTm9TdWNoTWV0aG9kRXJyb3I7ABJMamF2YS9sYW` + + `5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsA` + + `EkxqYXZhL2xhbmcvU3lzdGVtOwAVTGphdmEvbGFuZy9UaHJvd2FibGU7ACNMb3JnL2dvbG` + + `FuZy9hcHAvR29OYXRpdmVBY3Rpdml0eSQxOwAjTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZl` + + `QWN0aXZpdHkkMjsAI0xvcmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2aXR5JDM7ACVMb3` + + `JnL2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdml0eSQ0JDE7ACNMb3JnL2dvbGFuZy9hcHAv` + + `R29OYXRpdmVBY3Rpdml0eSQ0OwAhTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdH` + + `k7ABROVU1CRVJfS0VZQk9BUkRfQ09ERQAJT3BlbiBGaWxlAAdTREtfSU5UABhTSU5HTEVM` + + `SU5FX0tFWUJPQVJEX0NPREUACVNhdmUgRmlsZQABVgACVkkAA1ZJSQAFVklJSUkABFZJSU` + + `wAAlZMAANWTEkABVZMSUlJAApWTElJSUlJSUlJAANWTEwAAVoAAlpMAANaTEkAE1tMamF2` + + `YS9sYW5nL1N0cmluZzsAAlx8AAphY2Nlc3MkMDAwAAphY2Nlc3MkMDAyAAphY2Nlc3MkMT` + + `AwAAphY2Nlc3MkMTAyAAphY2Nlc3MkMjAwAAphY2Nlc3MkMzAwAAthY2Nlc3NGbGFncwAL` + + `YWRkQ2F0ZWdvcnkADmFkZENvbnRlbnRWaWV3AAhhZGRGbGFncwAZYWRkT25MYXlvdXRDaG` + + `FuZ2VMaXN0ZW5lcgAWYWRkVGV4dENoYW5nZWRMaXN0ZW5lcgAQYWZ0ZXJUZXh0Q2hhbmdl` + + `ZAAUYW5kcm9pZC5hcHAubGliX25hbWUAJWFuZHJvaWQuaW50ZW50LmFjdGlvbi5DUkVBVE` + + `VfRE9DVU1FTlQAI2FuZHJvaWQuaW50ZW50LmFjdGlvbi5PUEVOX0RPQ1VNRU5UAChhbmRy` + + `b2lkLmludGVudC5hY3Rpb24uT1BFTl9ET0NVTUVOVF9UUkVFACBhbmRyb2lkLmludGVudC` + + `5jYXRlZ29yeS5PUEVOQUJMRQAfYW5kcm9pZC5pbnRlbnQuZXh0cmEuTUlNRV9UWVBFUwAa` + + `YW5kcm9pZC5pbnRlbnQuZXh0cmEuVElUTEUAF2FwcGxpY2F0aW9uL3gtZGlyZWN0b3J5AB` + + `FiZWZvcmVUZXh0Q2hhbmdlZAAMYnJpbmdUb0Zyb250AAhjb250YWlucwANY3JlYXRlQ2hv` + + `b3NlcgAOZG9IaWRlS2V5Ym9hcmQADmRvU2hvd0ZpbGVPcGVuAA5kb1Nob3dGaWxlU2F2ZQ` + + `AOZG9TaG93S2V5Ym9hcmQAAWUABmVxdWFscwAhZXhjZXB0aW9uIHJlYWRpbmcgS2V5Q2hh` + + `cmFjdGVyTWFwABJmaWxlUGlja2VyUmV0dXJuZWQADGZpbmRWaWV3QnlJZAADZ2V0AA9nZX` + + `RBYnNvbHV0ZVBhdGgAD2dldEFjdGl2aXR5SW5mbwALZ2V0Q2FjaGVEaXIADGdldENvbXBv` + + `bmVudAAHZ2V0RGF0YQAMZ2V0RGVjb3JWaWV3AAlnZXRIZWlnaHQACWdldEludGVudAARZ2` + + `V0UGFja2FnZU1hbmFnZXIAC2dldFJvb3RWaWV3ABNnZXRSb290V2luZG93SW5zZXRzAAdn` + + `ZXRSdW5lAAlnZXRTdHJpbmcAEGdldFN5c3RlbVNlcnZpY2UAGmdldFN5c3RlbVdpbmRvd0` + + `luc2V0Qm90dG9tABhnZXRTeXN0ZW1XaW5kb3dJbnNldExlZnQAGWdldFN5c3RlbVdpbmRv` + + `d0luc2V0UmlnaHQAF2dldFN5c3RlbVdpbmRvd0luc2V0VG9wAAlnZXRUbXBkaXIACGdldF` + + `dpZHRoAAlnZXRXaW5kb3cADmdldFdpbmRvd1Rva2VuABxnZXRXaW5kb3dWaXNpYmxlRGlz` + + `cGxheUZyYW1lABBnb05hdGl2ZUFjdGl2aXR5AAZoZWlnaHQADGhpZGVLZXlib2FyZAAXaG` + + `lkZVNvZnRJbnB1dEZyb21XaW5kb3cADGlucHV0X21ldGhvZAANaW5zZXRzQ2hhbmdlZAAO` + + `a2V5Ym9hcmREZWxldGUADWtleWJvYXJkVHlwZWQABGxlZnQABmxlbmd0aAAEbG9hZAALbG` + + `9hZExpYnJhcnkAEmxvYWRMaWJyYXJ5IGZhaWxlZAAnbG9hZExpYnJhcnk6IG5vIG1hbmlm` + + `ZXN0IG1ldGFkYXRhIGZvdW5kAAltVGV4dEVkaXQACG1ldGFEYXRhAARuYW1lAAhvbGRTdG` + + `F0ZQAQb25BY3Rpdml0eVJlc3VsdAAIb25DcmVhdGUADm9uTGF5b3V0Q2hhbmdlAA1vblRl` + + `eHRDaGFuZ2VkAAhwdXRFeHRyYQAMcmVxdWVzdEZvY3VzAANydW4ADXJ1bk9uVWlUaHJlYW` + + `QADXNldEltZU9wdGlvbnMADHNldElucHV0VHlwZQAPc2V0TGF5b3V0UGFyYW1zAAdzZXRU` + + `ZXh0AAdzZXRUeXBlAA1zZXRWaXNpYmlsaXR5AApzZXR1cEVudHJ5AAxzaG93RmlsZU9wZW` + + `4ADHNob3dGaWxlU2F2ZQAMc2hvd0tleWJvYXJkAA1zaG93U29mdElucHV0AAVzcGxpdAAW` + + `c3RhcnRBY3Rpdml0eUZvclJlc3VsdAALc3ViU2VxdWVuY2UABnRoaXMkMAAGdGhpcyQxAA` + + `h0b1N0cmluZwADdG9wACJ1bmtub3duIGtleWJvYXJkIHR5cGUsIHVzZSBkZWZhdWx0AAx1` + + `cGRhdGVMYXlvdXQAEHZhbCRrZXlib2FyZFR5cGUABXZhbHVlAAV3aWR0aAABfABPAgAABw` + + `4AUgAHdxACDll5lpd4tJaWl6WWAm0sIEsCdB0AfAEABw4AfwAHDrQA1QEBAAcOANgBCQAA` + + `AAAAAAAAAAcOWgDqAQEABw4A/AEBAAcOAPgBBAAAAAAHDgDtAQQAAAAABw4BEg8BHxO0An` + + `sdAN4BAQAHDgDhAQAHHeG0xFuWtQIU4AAvAAcOOE4tABoBAAcOABoCAAAHDgAaAQAHDgAa` + + `AgAABw4AGgAHDgAaAgAABw4AqgEDAAAABx2HNAJ7LCAegwB0AAcOWgDBAQAHDkujTEt/An` + + `sdh0seAN4BAAcOAiKGAIUBAQAHDloAmQECAAAHDloASwEABw5aAHgABw6HtIiMAIkBAQAH` + + `HXjheESWAncd4Vq0ajwAnQECAAAHDnjhWrdaWqUCex0ATwEABw4CIoYANAAHDgCFAgMAAA` + + `AHDgIMaAJ5HTxsSwDQAQEABw48PD20jAA5AAcOwwIOLAJ2HYeFTB5atbT/0AACGQG8ARpO` + + `AhoCWQQAnQEeAhsBvAEcARcBAhkBvAEaSwIZAbwBGl4CGQG8ARpCAhkBvAEaYAZEkAAIBA` + + `AEAQQCBAIEAQACAQEEkCABkCA3gIAEzBc4AewXAAEBAQaQIDmAgATsGToBiBoAAQEBB5Ag` + + `O4CABLAaPAHMGgABAQMIkCA9gIAE6Bo+AYQbAQGYGwEBrBsAAQEBCZAgQYCABNwcQgH4HA` + + `cCEggKGgEaARoBGgEaARoBChECAQJDgYAEpB4BiCDIHgGIIOAeAYgg+B4BiCCQHwGIIKgf` + + `AYggwB8GggIABQjYHwQIrCABggIAAYICAAGCAgABAsggBALMIQEI8CEBCIwiAQioIksAxC` + + `IBAJwjAQDcJAEA6CUIAIwmBwSwJgEB+CYHAMQnEQAAAAAAAAABAAAAAAAAAAEAAAC/AAAA` + + `cAAAAAIAAAAuAAAAbAMAAAMAAAA9AAAAJAQAAAQAAAATAAAAAAcAAAUAAABmAAAAmAcAAA` + + `YAAAAGAAAAyAoAAAMQAAAGAAAAiAsAAAEgAAAiAAAAzAsAAAYgAAAFAAAAoBQAAAEQAAAg` + + `AAAA+BQAAAIgAAC/AAAAGhYAAAMgAAAiAAAADCMAAAQgAAAHAAAAeiQAAAUgAAABAAAAry` + + `QAAAAgAAAGAAAAviQAAAAQAAABAAAApCUAAA==` + `` diff --git a/dialog/file_mobile.go b/dialog/file_mobile.go index 6e31497cb2..e453bdf377 100644 --- a/dialog/file_mobile.go +++ b/dialog/file_mobile.go @@ -36,7 +36,7 @@ func fileOpenOSOverride(f *FileDialog) bool { } func fileSaveOSOverride(f *FileDialog) bool { - gomobile.ShowFileSavePicker(f.callback.(func(fyne.URIWriteCloser, error)), f.filter) + gomobile.ShowFileSavePicker(f.callback.(func(fyne.URIWriteCloser, error)), f.filter, f.initialFileName) return true } diff --git a/go.mod b/go.mod index e54cf3e053..ab4380bd56 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/akavel/rsrc v0.8.0 // indirect github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 github.com/fsnotify/fsnotify v1.4.9 - github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f + github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 github.com/godbus/dbus/v5 v5.0.4 diff --git a/go.sum b/go.sum index 61900d05ce..4a21b1cb61 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,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/mobile v0.1.3-0.20210318200029-09e9c4e13a8f h1:rguJ/t99j/6zRSFzsBKlsmmyl+vOvCeTJ+2uTBvuXFI= github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= +github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a h1:3TAJhl8vXyli0tooKB0vd6gLCyBdWL4QEYbDoJpHEZk= +github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a/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/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI= diff --git a/internal/driver/gomobile/file.go b/internal/driver/gomobile/file.go index ea12251caa..7ea6af7b6d 100644 --- a/internal/driver/gomobile/file.go +++ b/internal/driver/gomobile/file.go @@ -102,11 +102,11 @@ func fileWriterForURI(u fyne.URI) (fyne.URIWriteCloser, error) { } type hasSavePicker interface { - ShowFileSavePicker(func(string, func()), *app.FileFilter) + ShowFileSavePicker(func(string, func()), *app.FileFilter, string) } // ShowFileSavePicker loads the native file save dialog and returns the chosen file path via the callback func. -func ShowFileSavePicker(callback func(fyne.URIWriteCloser, error), filter storage.FileFilter) { +func ShowFileSavePicker(callback func(fyne.URIWriteCloser, error), filter storage.FileFilter, filename string) { drv := fyne.CurrentApp().Driver().(*mobileDriver) if a, ok := drv.app.(hasSavePicker); ok { a.ShowFileSavePicker(func(uri string, closer func()) { @@ -119,6 +119,6 @@ func ShowFileSavePicker(callback func(fyne.URIWriteCloser, error), filter storag f.(*fileSave).done = closer } callback(f, err) - }, mobileFilter(filter)) + }, mobileFilter(filter), filename) } } diff --git a/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java b/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java index 16df340d56..afe446940d 100644 --- a/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java +++ b/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java @@ -149,11 +149,11 @@ void doShowFileOpen(String mimes) { startActivityForResult(Intent.createChooser(intent, "Open File"), FILE_OPEN_CODE); } - static void showFileSave(String mimes) { - goNativeActivity.doShowFileSave(mimes); + static void showFileSave(String mimes, String filename) { + goNativeActivity.doShowFileSave(mimes, filename); } - void doShowFileSave(String mimes) { + void doShowFileSave(String mimes, String filename) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); if (mimes.contains("|") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { intent.setType("*/*"); @@ -161,6 +161,7 @@ void doShowFileSave(String mimes) { } else { intent.setType(mimes); } + intent.putExtra(Intent.EXTRA_TITLE, filename); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(Intent.createChooser(intent, "Save File"), FILE_SAVE_CODE); } diff --git a/vendor/github.com/fyne-io/mobile/app/android.c b/vendor/github.com/fyne-io/mobile/app/android.c index 0c1ed9e6e6..3ba916cfef 100644 --- a/vendor/github.com/fyne-io/mobile/app/android.c +++ b/vendor/github.com/fyne-io/mobile/app/android.c @@ -83,7 +83,7 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_ show_keyboard_method = find_static_method(env, current_class, "showKeyboard", "(I)V"); hide_keyboard_method = find_static_method(env, current_class, "hideKeyboard", "()V"); show_file_open_method = find_static_method(env, current_class, "showFileOpen", "(Ljava/lang/String;)V"); - show_file_save_method = find_static_method(env, current_class, "showFileSave", "(Ljava/lang/String;)V"); + show_file_save_method = find_static_method(env, current_class, "showFileSave", "(Ljava/lang/String;Ljava/lang/String;)V"); setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz)); @@ -241,13 +241,15 @@ void showFileOpen(JNIEnv* env, char* mimes) { ); } -void showFileSave(JNIEnv* env, char* mimes) { +void showFileSave(JNIEnv* env, char* mimes, char* filename) { jstring mimesJString = (*env)->NewStringUTF(env, mimes); + jstring filenameJString = (*env)->NewStringUTF(env, filename); (*env)->CallStaticVoidMethod( env, current_class, show_file_save_method, - mimesJString + mimesJString, + filenameJString ); } diff --git a/vendor/github.com/fyne-io/mobile/app/android.go b/vendor/github.com/fyne-io/mobile/app/android.go index 7fc65902c2..7143b1fee1 100644 --- a/vendor/github.com/fyne-io/mobile/app/android.go +++ b/vendor/github.com/fyne-io/mobile/app/android.go @@ -45,7 +45,7 @@ int32_t getKeyRune(JNIEnv* env, AInputEvent* e); void showKeyboard(JNIEnv* env, int keyboardType); void hideKeyboard(JNIEnv* env); void showFileOpen(JNIEnv* env, char* mimes); -void showFileSave(JNIEnv* env, char* mimes); +void showFileSave(JNIEnv* env, char* mimes, char* filename); void Java_org_golang_app_GoNativeActivity_filePickerReturned(JNIEnv *env, jclass clazz, jstring str); */ @@ -383,16 +383,18 @@ func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter) } } -func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter) { +func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter, filename string) { fileCallback = callback mimes := mimeStringFromFilter(filter) mimeStr := C.CString(mimes) defer C.free(unsafe.Pointer(mimeStr)) + filenameStr := C.CString(filename) + defer C.free(unsafe.Pointer(filenameStr)) save := func(vm, jniEnv, ctx uintptr) error { env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer - C.showFileSave(env, mimeStr) + C.showFileSave(env, mimeStr, filenameStr) return nil } diff --git a/vendor/github.com/fyne-io/mobile/app/app.go b/vendor/github.com/fyne-io/mobile/app/app.go index b1ca18b278..b020c544a8 100644 --- a/vendor/github.com/fyne-io/mobile/app/app.go +++ b/vendor/github.com/fyne-io/mobile/app/app.go @@ -55,7 +55,7 @@ type App interface { ShowVirtualKeyboard(KeyboardType) HideVirtualKeyboard() ShowFileOpenPicker(func(string, func()), *FileFilter) - ShowFileSavePicker(func(string, func()), *FileFilter) + ShowFileSavePicker(func(string, func()), *FileFilter, string) } type FileFilter struct { @@ -150,8 +150,8 @@ func (a *app) HideVirtualKeyboard() { func (a *app) ShowFileOpenPicker(callback func(string, func()), filter *FileFilter) { driverShowFileOpenPicker(callback, filter) } -func (a *app) ShowFileSavePicker(callback func(string, func()), filter *FileFilter) { - driverShowFileSavePicker(callback, filter) +func (a *app) ShowFileSavePicker(callback func(string, func()), filter *FileFilter, filename string) { + driverShowFileSavePicker(callback, filter, filename) } type stopPumping struct{} diff --git a/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go b/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go index 0e1a240ef2..99368918c0 100644 --- a/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go +++ b/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go @@ -240,7 +240,7 @@ func driverShowFileOpenPicker(func(string, func()), *FileFilter) { } // driverShowFileSavePicker does nothing on desktop -func driverShowFileSavePicker(func(string, func()), *FileFilter) { +func driverShowFileSavePicker(func(string, func()), *FileFilter, string) { } // convRune marks the Carbon/Cocoa private-range unicode rune representing 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 dc30a97438..7477f6f2b4 100644 --- a/vendor/github.com/fyne-io/mobile/app/darwin_ios.go +++ b/vendor/github.com/fyne-io/mobile/app/darwin_ios.go @@ -306,7 +306,7 @@ func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter) C.showFileOpenPicker(mimeStr, extStr) } -func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter) { +func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter, filename string) { fileCallback = callback mimeStr, extStr := cStringsForFilter(filter) diff --git a/vendor/github.com/fyne-io/mobile/app/shiny.go b/vendor/github.com/fyne-io/mobile/app/shiny.go index b7c1926a43..d29da5d62b 100644 --- a/vendor/github.com/fyne-io/mobile/app/shiny.go +++ b/vendor/github.com/fyne-io/mobile/app/shiny.go @@ -27,5 +27,5 @@ func driverShowFileOpenPicker(func(string, func()), *FileFilter) { } // driverShowFileSavePicker does nothing on desktop -func driverShowFileSavePicker(func(string, func()), *FileFilter) { +func driverShowFileSavePicker(func(string, func()), *FileFilter, string) { } diff --git a/vendor/github.com/fyne-io/mobile/app/x11.go b/vendor/github.com/fyne-io/mobile/app/x11.go index edc3977af1..7b112f0969 100644 --- a/vendor/github.com/fyne-io/mobile/app/x11.go +++ b/vendor/github.com/fyne-io/mobile/app/x11.go @@ -137,5 +137,5 @@ func driverShowFileOpenPicker(func(string, func()), *FileFilter) { } // driverShowFileSavePicker does nothing on desktop -func driverShowFileSavePicker(func(string, func()), *FileFilter) { +func driverShowFileSavePicker(func(string, func()), *FileFilter, string) { } diff --git a/vendor/modules.txt b/vendor/modules.txt index f9a242d171..4d72bc40c9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/davecgh/go-spew/spew github.com/fredbi/uri # github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify -# github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f +# github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a github.com/fyne-io/mobile/app github.com/fyne-io/mobile/app/internal/callfn github.com/fyne-io/mobile/event/key From 534093a2d4837c9b30c882652791ec567c1aaa8d Mon Sep 17 00:00:00 2001 From: Jacalz Date: Wed, 14 Apr 2021 17:40:10 +0200 Subject: [PATCH 22/57] Don't create a new size on each iteration --- widget/select_entry.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/widget/select_entry.go b/widget/select_entry.go index 92c878f93f..dedfdb2a42 100644 --- a/widget/select_entry.go +++ b/widget/select_entry.go @@ -59,8 +59,9 @@ func (e *SelectEntry) MinSize() fyne.Size { min := e.Entry.MinSize() if e.dropDown != nil { + padding := fyne.NewSize(4*theme.Padding(), 0) for _, item := range e.dropDown.Items { - itemMin := fyne.MeasureText(item.Label, theme.TextSize(), fyne.TextStyle{}).Add(fyne.NewSize(4*theme.Padding(), 0)) + itemMin := fyne.MeasureText(item.Label, theme.TextSize(), fyne.TextStyle{}).Add(padding) min = min.Max(itemMin) } } From cf1374ef7448a470f577101f0179d4b5e5c7bed9 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Wed, 14 Apr 2021 17:48:05 +0200 Subject: [PATCH 23/57] Preallocate slice of items Tests in fyne_demo indicates that it speeds things up by about 20-40%. Speedups should be more notisable with more items. --- widget/select_entry.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/widget/select_entry.go b/widget/select_entry.go index dedfdb2a42..ce30860491 100644 --- a/widget/select_entry.go +++ b/widget/select_entry.go @@ -81,10 +81,10 @@ func (e *SelectEntry) Resize(size fyne.Size) { // SetOptions sets the options the user might select from. func (e *SelectEntry) SetOptions(options []string) { e.options = options - var items []*fyne.MenuItem - for _, option := range options { + items := make([]*fyne.MenuItem, len(options)) + for i, option := range options { option := option // capture - items = append(items, fyne.NewMenuItem(option, func() { e.SetText(option) })) + items[i] = fyne.NewMenuItem(option, func() { e.SetText(option) }) } e.dropDown = fyne.NewMenu("", items...) From 37e379e4d638cf267d22e23faa7c1301a7de3334 Mon Sep 17 00:00:00 2001 From: Ankush Jadhav Date: Sat, 17 Apr 2021 11:55:12 +0530 Subject: [PATCH 24/57] Fix Form does not render HintTexts for more than one item --- widget/form.go | 2 +- widget/form_test.go | 23 +++++++++++++++++++++++ widget/testdata/form/hints_rendered.png | Bin 0 -> 3155 bytes 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 widget/testdata/form/hints_rendered.png diff --git a/widget/form.go b/widget/form.go index c6b69660ac..489daf7ed7 100644 --- a/widget/form.go +++ b/widget/form.go @@ -67,7 +67,7 @@ func (f *Form) AppendItem(item *FormItem) { f.Items = append(f.Items, item) if f.itemGrid != nil { f.itemGrid.Add(f.createLabel(item.Text)) - f.itemGrid.Add(item.Widget) + f.itemGrid.Add(f.createInput(item)) f.setUpValidation(item.Widget, len(f.Items)-1) } diff --git a/widget/form_test.go b/widget/form_test.go index 5e037296ce..564ba65aff 100644 --- a/widget/form_test.go +++ b/widget/form_test.go @@ -232,3 +232,26 @@ func TestForm_EntryValidation_FirstTypeValid(t *testing.T) { test.AssertImageMatches(t, "form/validation_entry_first_type_invalid.png", w.Canvas().Capture()) } + +func TestForm_HintsRendered(t *testing.T) { + f := NewForm() + + fi1 := NewFormItem("Form Item 1", NewEntry()) + fi1.HintText = "HT1" + f.AppendItem(fi1) + + fi2 := NewFormItem("Form Item 2", NewEntry()) + fi2.HintText = "HT2" + + f.AppendItem(fi2) + + fi3 := NewFormItem("Form Item 3", NewEntry()) + fi3.HintText = "HT3" + + f.AppendItem(fi3) + + w := test.NewWindow(f) + defer w.Close() + + test.AssertImageMatches(t, "form/hints_rendered.png", w.Canvas().Capture()) +} diff --git a/widget/testdata/form/hints_rendered.png b/widget/testdata/form/hints_rendered.png new file mode 100644 index 0000000000000000000000000000000000000000..a62a2137d251dd2250c1a937a5f3bbf7a63303e6 GIT binary patch literal 3155 zcmZ`+2{=@H8LYsCp{n_XY?aJfY?f0x%}poaXi<=Q ztnDuvLoX^U^CZ$hf?=rm#7B#`)K@pcX|r9NiE`G3wOsmE*qpE5Jom|QNl|fOvv>Ht z(ut~$@BXnv8*@VbsN$7lwIKtJc(snp!3uQT5A7ZwF*ZyKPnFj{MJ}kST8fuJe=fKY zxUsceRi*yrg|(7i#JZ|N^XTGwM8nwFn0>&0X&vJS^!VM+ zjqXh%1Oh=e!V`_oZF8HNp0=;^=bld0RZKHWOiT<94>wY@jEagfHh!C3P*_l)2}(wn z-=c;r_VpdvJk2;4%#(<3di?ltHyBY!YG^xUNrRe$qcVFMx zEWZX*v9|X1D%R{3{M^Nh7hm1H7`?S3!ye1<%+jk;)e_ywU`WGOXIX_zp4Ij1_}QTsJzj>2J?DrKOPvsoYL){~n$p@~tMf@l+rNMA4+jfX^$UUTU+a{ ze~!ERfXj8RcYq?1>KYpRBqWAdEF;C98ei6=8R;Xu{Ed{9lzN8G=IZRN$d$BCkIm6k z{Zk>IcL_Ac$H&X5OA-prPO90^NV^wSJP?;><{L7xVgEx zyZ4Oq`M5%+wzjsZm}QYQZhWf0m}j}4{qp5YCH`M#Vh0I^xWAQctL~OPqgWu3g_jJj z*^fOv91t2BIw|O$c1IxgJDQlAyXr9bV{eC&ZD)D`V6bZa+mz*r~Rx=H}+??34LDAz#-Qo0^)O zR0r8?3r>S^dE#<4V#jr3D=O0+g-ZYJyyPBXiTLLg6%V$bGI@_p&eZ1Vt4(?ki+jB^ z0VLguSd4FT3#fPCR|!4^b{Q3g?;MsCm6eqRIK*>|r_49l=l1{(J-!1OP>LdJvM;L! zn69Crp)TlVNkc;emFl3Bmf7i%)wv3Y&$U|p#SxEr!}RoYEeNE;OV6*X5UilE@S947RYnVVh2jbm?{lsMO&yVIFK^r?8|RyvnN3YjdU<4vVQrH*d8CPtl!R4SFu6^KnQ@_V*#O)=Z+1@6T8kmYFnpOEp5Wx zaJ@zYaY(9SnG^Ye^S>E)#s{06k}^DUY!Tf*D?G1Zkl-q#4*$-+QaiFpo?QU@)S1~O zsA4wu#`zGhw_<$&PT{QX4@}bn#w9d<_3-iWadCNGS6A2AsJ}$x z4kjPK4~Kdh8HuoLxtD`C__MVf^%y|*0ns+X?>to@YGFUs;doYLT~*cBjDUN`;7^c% zKHwxoayCw_Y>fu}H;b>L(&{HE|DOFnCE-GT|J2mzb+;~-2TS;ofpzJd0B`jLlld_* zyBayfoSbjXV`5?=aLDDAlT?>#!`G#b0d#mW>fO@j7O-Ke$@%%GRg!LhbAok=AtQv- z*HWx)Z1gt99${-K&!0EBRN{hzh2d*jCESe6OtW%fzWyuNvx=J+9T<}%!XFkSuhTX9 zIMF+$1JmLP@Ml%W6M@edSoo_9O(O?RAZl5TDAei#{QyY^8}SB|d$4~2SMLBwMO<85 zeSLjrXD6f^f(Bl{Fx%^eBtmnD#2s#;{9DTm_Y&@j1lDX>aq%>d_lvSJyD_@aNqZoWUy&goPx;S%a%*D@67mHSQ=hrQ4VgcJcuO0|U`G=ZqzaBDZqj#1Fof~=V)c+ zLi)%L(|e8cql0q!kmvh~NIqJurST41IYMdb;ua zlTqmp3-@0grLa|US}+DBT=5go#iSn-mqejR@`~QVz7lrC0kJNxI*6FPfbNySi}!wfqNXGl`pv;`}@I~nqwxG7_~>lAPyWNL&kEQ2}4{s*X%Xx#t+ literal 0 HcmV?d00001 From 0d16a269547292368b8bcd4aa8ce12526532f28b Mon Sep 17 00:00:00 2001 From: Ankush Jadhav Date: Sat, 17 Apr 2021 12:07:34 +0530 Subject: [PATCH 25/57] fixed breaking test --- widget/form_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/widget/form_test.go b/widget/form_test.go index 564ba65aff..9537fa4f53 100644 --- a/widget/form_test.go +++ b/widget/form_test.go @@ -234,6 +234,10 @@ func TestForm_EntryValidation_FirstTypeValid(t *testing.T) { } func TestForm_HintsRendered(t *testing.T) { + app := test.NewApp() + defer test.NewApp() + app.Settings().SetTheme(theme.LightTheme()) + f := NewForm() fi1 := NewFormItem("Form Item 1", NewEntry()) From 8c4c42c941f60b78f877b3a0409fb96c2527832a Mon Sep 17 00:00:00 2001 From: FPabl0 Date: Sun, 18 Apr 2021 01:38:07 -0500 Subject: [PATCH 26/57] tap animation should be created in widget's CreateRenderer method --- widget/button.go | 22 +++++------- widget/button_internal_test.go | 31 +++++++++++++++++ widget/select.go | 18 ++++------ widget/select_internal_test.go | 42 +++++++++++++++++++++++ widget/testdata/button/tap_animation.png | Bin 0 -> 501 bytes widget/testdata/select/tap_animation.png | Bin 0 -> 1745 bytes 6 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 widget/select_internal_test.go create mode 100644 widget/testdata/button/tap_animation.png create mode 100644 widget/testdata/select/tap_animation.png diff --git a/widget/button.go b/widget/button.go index 07e390287e..a41169bd01 100644 --- a/widget/button.go +++ b/widget/button.go @@ -66,7 +66,6 @@ type Button struct { hovered bool tapAnim *fyne.Animation - tapBG *canvas.Rectangle } // NewButton creates a new button widget with the set label and tap handler @@ -99,10 +98,12 @@ func (b *Button) CreateRenderer() fyne.WidgetRenderer { text.TextStyle.Bold = true background := canvas.NewRectangle(theme.ButtonColor()) - b.tapBG = canvas.NewRectangle(color.Transparent) + tapBG := canvas.NewRectangle(color.Transparent) + b.tapAnim = newButtonTapAnimation(tapBG, b) + b.tapAnim.Curve = fyne.AnimationEaseOut objects := []fyne.CanvasObject{ background, - b.tapBG, + tapBG, text, } shadowLevel := widget.ButtonLevel @@ -112,6 +113,7 @@ func (b *Button) CreateRenderer() fyne.WidgetRenderer { r := &buttonRenderer{ ShadowingRenderer: widget.NewShadowingRenderer(objects, shadowLevel), background: background, + tapBG: tapBG, button: b, label: text, layout: layout.NewHBoxLayout(), @@ -177,17 +179,10 @@ func (b *Button) Tapped(*fyne.PointEvent) { } func (b *Button) tapAnimation() { - if b.tapBG == nil { // not rendered yet? (tests) - return - } - if b.tapAnim == nil { - b.tapAnim = newButtonTapAnimation(b.tapBG, b) - b.tapAnim.Curve = fyne.AnimationEaseOut - } else { - b.tapAnim.Stop() + return } - + b.tapAnim.Stop() b.tapAnim.Start() } @@ -197,6 +192,7 @@ type buttonRenderer struct { icon *canvas.Image label *canvas.Text background *canvas.Rectangle + tapBG *canvas.Rectangle button *Button layout fyne.Layout } @@ -351,7 +347,7 @@ func (r *buttonRenderer) updateIconAndText() { if r.icon == nil { r.icon = canvas.NewImageFromResource(r.button.Icon) r.icon.FillMode = canvas.ImageFillContain - r.SetObjects([]fyne.CanvasObject{r.background, r.button.tapBG, r.label, r.icon}) + r.SetObjects([]fyne.CanvasObject{r.background, r.tapBG, r.label, r.icon}) } if r.button.Disabled() { r.icon.Resource = theme.NewDisabledResource(r.button.Icon) diff --git a/widget/button_internal_test.go b/widget/button_internal_test.go index e37caf4e2a..6573462bb1 100644 --- a/widget/button_internal_test.go +++ b/widget/button_internal_test.go @@ -5,6 +5,7 @@ import ( "testing" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/internal/cache" "fyne.io/fyne/v2/internal/widget" "fyne.io/fyne/v2/test" "fyne.io/fyne/v2/theme" @@ -157,3 +158,33 @@ func TestButtonRenderer_ApplyTheme(t *testing.T) { assert.NotEqual(t, textSize, customTextSize) } + +func TestButtonRenderer_TapAnimation(t *testing.T) { + test.NewApp() + defer test.NewApp() + + button := NewButton("Hi", func() {}) + w := test.NewWindow(button) + defer w.Close() + w.Resize(fyne.NewSize(50, 50).Add(fyne.NewSize(10, 10))) + button.Resize(fyne.NewSize(50, 50)) + + test.ApplyTheme(t, test.NewTheme()) + button.Refresh() + + render1 := test.WidgetRenderer(button).(*buttonRenderer) + test.Tap(button) + button.tapAnim.Tick(0.5) + test.AssertImageMatches(t, "button/tap_animation.png", w.Canvas().Capture()) + + cache.DestroyRenderer(button) + button.Refresh() + + render2 := test.WidgetRenderer(button).(*buttonRenderer) + + assert.NotEqual(t, render1, render2) + + test.Tap(button) + button.tapAnim.Tick(0.5) + test.AssertImageMatches(t, "button/tap_animation.png", w.Canvas().Capture()) +} diff --git a/widget/select.go b/widget/select.go index 37d0ebbd3c..ca2dbbfe00 100644 --- a/widget/select.go +++ b/widget/select.go @@ -24,7 +24,6 @@ type Select struct { hovered bool popUp *PopUpMenu tapAnim *fyne.Animation - tapBG *canvas.Rectangle } var _ fyne.Widget = (*Select)(nil) @@ -66,8 +65,10 @@ func (s *Select) CreateRenderer() fyne.WidgetRenderer { background := &canvas.Rectangle{} line := canvas.NewRectangle(theme.ShadowColor()) - s.tapBG = canvas.NewRectangle(color.Transparent) - objects := []fyne.CanvasObject{background, line, s.tapBG, txtProv, icon} + tapBG := canvas.NewRectangle(color.Transparent) + s.tapAnim = newButtonTapAnimation(tapBG, s) + s.tapAnim.Curve = fyne.AnimationEaseOut + objects := []fyne.CanvasObject{background, line, tapBG, txtProv, icon} r := &selectRenderer{icon, txtProv, background, line, objects, s} background.FillColor, line.FillColor = r.bgLineColor() r.updateIcon() @@ -251,17 +252,10 @@ func (s *Select) showPopUp() { } func (s *Select) tapAnimation() { - if s.tapBG == nil { // not rendered yet? (tests) - return - } - if s.tapAnim == nil { - s.tapAnim = newButtonTapAnimation(s.tapBG, s) - s.tapAnim.Curve = fyne.AnimationEaseOut - } else { - s.tapAnim.Stop() + return } - + s.tapAnim.Stop() s.tapAnim.Start() } diff --git a/widget/select_internal_test.go b/widget/select_internal_test.go new file mode 100644 index 0000000000..90c86835c7 --- /dev/null +++ b/widget/select_internal_test.go @@ -0,0 +1,42 @@ +package widget + +import ( + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/internal/cache" + "fyne.io/fyne/v2/test" + "github.com/stretchr/testify/assert" +) + +func TestSelectRenderer_TapAnimation(t *testing.T) { + test.NewApp() + defer test.NewApp() + + sel := NewSelect([]string{"one"}, func(s string) {}) + w := test.NewWindow(sel) + defer w.Close() + w.Resize(sel.MinSize().Add(fyne.NewSize(10, 10))) + sel.Resize(sel.MinSize()) + + test.ApplyTheme(t, test.NewTheme()) + sel.Refresh() + + render1 := test.WidgetRenderer(sel).(*selectRenderer) + test.Tap(sel) + sel.popUp.Hide() + sel.tapAnim.Tick(0.5) + test.AssertImageMatches(t, "select/tap_animation.png", w.Canvas().Capture()) + + cache.DestroyRenderer(sel) + sel.Refresh() + + render2 := test.WidgetRenderer(sel).(*selectRenderer) + + assert.NotEqual(t, render1, render2) + + test.Tap(sel) + sel.popUp.Hide() + sel.tapAnim.Tick(0.5) + test.AssertImageMatches(t, "select/tap_animation.png", w.Canvas().Capture()) +} diff --git a/widget/testdata/button/tap_animation.png b/widget/testdata/button/tap_animation.png new file mode 100644 index 0000000000000000000000000000000000000000..8f04fc5c904d53f55cd8ff21fbf40b7038ff4725 GIT binary patch literal 501 zcmVC?@W}A?m!3k%1M?lOzL)D9CL4S<1%39HEafvz z(pZo*79@=YNn=6MSdcW6WHQ<7^*WtSEEeO8;*!p0Pd%^ddDT|yW~F7@*1Q#s9?vV4 zN+p-e<@0&VvUrB_N(TZ5x*pW^U^uLe1iHG-{svy z{TyD`Es%1jNg4~1#)71=AZaW}8VmoEiwYA1hAHTa8?-@`0Yeogj0OIGn0EY{d9Cy= rBjqzq(pZo*79@>@l+s@T00960z@=d#EJ&%r00000NkvXXu0mjf2p8TA literal 0 HcmV?d00001 diff --git a/widget/testdata/select/tap_animation.png b/widget/testdata/select/tap_animation.png new file mode 100644 index 0000000000000000000000000000000000000000..ac78da2709485626c3cdde811df8b97e12dee7b1 GIT binary patch literal 1745 zcmV;?1}^!DP)-4-5=cl0gq=}>f=J+rf+!%e=RM{rf9cPzL^04*=t(!8Ai3m}ck$(+quJnxPL&GxULJhCVRO(1(L%p?M~Ix&*j0+5F6s z{^|xFImUvmGxULJhCVRO&xKy<=m~Mn!35G5`Q8FK^MVU9Q2wc_U9n0(s-rw&Oc*sAL*jTJvXlkerYoB&tF9 zbp&N)&CkjCfx|Hgwy>aJSy`E^qocXBv`M^_?nzTx~JyvOn2H_y?#L=(*VJeUYV;8H7!4~;quemgQ?LO^8o9Nl{H+Ty12#{8P2_fPJ*HJxcL z;c}z)?;rP3V{vfsNTwG`Y0ixy?&Y7D^OB)Xya0?4dzj5P>*6pj_iE$(o z%}=RrWHu+_{-viT0RRA9t-@!sXV7*q? z`LG|UIhttTavO__$J*^97?SIZ?BxXjP(FWdX@p_8Vc$Of@#712?3kOoZEHo^e|Inc zDErK#JI0_Cx}1q8;0F6;5$w66TJvXlJh<6D%4pSpe#|T!9>+V+M`}_NVbaEOwb@ND zBsC+^>BjwqR66#NtgEYT=T2^sdgb<|n?6V>zWo0;Lyiq}54rhJ0RU-RBGa~vzHo4M z8z=j=>hJ1pbP)B(kebvaSjuKk!I0EUuTmMCqfjPt(5iH24&O5vz~&?DWoud074`a& z@O5&}?ORnXqnwq)<7C`c{UM$4FU^T!rySg@lRX7PQZuzsXpWGR(9zOo-2ROmV)eEU z0suVxT}3^^wsc|DU)X6}<^G{o#(*GpR?7rpdk~dKq>)J6!otiJ0+Be@Ep`nH0Px2n zgg~{@a%M8meORKGXXn+g=fQ+}J2@d!R<)+gnEB>$nmI{^eS)s9OSOy4Y z__-`ho|C;Vsr~{V(i@aR>J{6T21WV|_RC3RlDjWWY}@)aW_94s^xnuzoVkH3J%9qVg z?h6o{zTLoC9+yt@8y*KTIj6T*+SYb9I@)X`kVwvceh$vgmj*XWdMEr{Q|M0azErIm z5($RK&8A=0-_?ssbEn9}N~!RLBN#j$?;FU_>mXsL)b?Y~*XkP{Bx9pNuZQ*e5Dw=# zk2n16naK!4kVYz14Ggr%VkfDq)98mqN`+Jn8*F-yR{hlq?XXCpR*b$tfMHlBF;=vB zTHtkyjR@a)wv?6i-MyP|>==PWLbhcLGYF?LFbrR0vxzP)Fl0TxVN4C=(@#jNg+wC6 zo5e7LeZzx3Uf4Iryn@?f3@g;*Mq99ThCVRO&>i4$ Date: Tue, 20 Apr 2021 01:03:41 +0530 Subject: [PATCH 27/57] filter out shortcut triggers where key-modifier is same --- internal/driver/glfw/window.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index b3d41b20b3..d4b6eacfcb 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -1120,7 +1120,7 @@ func (w *window) keyPressed(_ *glfw.Window, key glfw.Key, scancode int, action g } } - if shortcut == nil && keyDesktopModifier != 0 && keyDesktopModifier != desktop.ShiftModifier { + if shortcut == nil && keyDesktopModifier != 0 && !isKeyModifierPair(keyName, keyDesktopModifier) && keyDesktopModifier != desktop.ShiftModifier { shortcut = &desktop.CustomShortcut{ KeyName: keyName, Modifier: keyDesktopModifier, @@ -1417,3 +1417,17 @@ func (d *gLDriver) CreateSplashWindow() fyne.Window { func (d *gLDriver) AllWindows() []fyne.Window { return d.windows } + +func isKeyModifierPair(keyName fyne.KeyName, modifier desktop.Modifier) bool { + switch modifier { + case desktop.ShiftModifier: + return (keyName == desktop.KeyShiftLeft || keyName == desktop.KeyShiftRight) + case desktop.ControlModifier: + return (keyName == desktop.KeyControlLeft || keyName == desktop.KeyControlRight) + case desktop.AltModifier: + return (keyName == desktop.KeyAltLeft || keyName == desktop.KeyAltRight) + case desktop.SuperModifier: + return (keyName == desktop.KeySuperLeft || keyName == desktop.KeySuperRight) + } + return false +} From f610ddfa6cf0277afc5aa0a38ffef13411a6e816 Mon Sep 17 00:00:00 2001 From: Ankush Jadhav Date: Tue, 20 Apr 2021 18:04:02 +0530 Subject: [PATCH 28/57] function position refactor + test case --- internal/driver/glfw/window_test.go | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index bcabd91ac9..98f435c830 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -933,6 +933,27 @@ func TestWindow_Focus(t *testing.T) { assert.Equal(t, "ef", e2.Text) } +func TestWindow_CaptureTypedShortcut(t *testing.T) { + w := createWindow("Test").(*window) + content := &typedShortcutable{} + content.SetMinSize(fyne.NewSize(10, 10)) + w.SetContent(content) + repaintWindow(w) + + w.mouseMoved(w.viewport, 5, 5) + w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + + w.keyPressed(nil, glfw.KeyLeftControl, 0, glfw.Action(glfw.Press), glfw.ModControl) + w.keyPressed(nil, glfw.KeyF, 0, glfw.Action(glfw.Press), glfw.ModControl) + w.keyPressed(nil, glfw.KeyLeftControl, 0, glfw.Action(glfw.Release), glfw.ModControl) + w.keyPressed(nil, glfw.KeyF, 0, glfw.Action(glfw.Release), glfw.ModControl) + + w.waitForEvents() + + assert.Equal(t, 1, len(content.capturedShortcuts)) + assert.Equal(t, "CustomDesktop:Control+F", content.capturedShortcuts[0].ShortcutName()) +} + func TestWindow_ManualFocus(t *testing.T) { w := createWindow("Test").(*window) content := &focusable{} @@ -1273,6 +1294,15 @@ func (f *focusable) Disabled() bool { return f.disabled } +type typedShortcutable struct { + focusable + capturedShortcuts []fyne.Shortcut +} + +func (ts *typedShortcutable) TypedShortcut(s fyne.Shortcut) { + ts.capturedShortcuts = append(ts.capturedShortcuts, s) +} + // // Test helper // From f35415910031ee31839b048dee584b36f51a3a57 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 21 Apr 2021 16:55:59 +0100 Subject: [PATCH 29/57] Starting prep for 2.0.3 release --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ad3e8977..2b4b1b2343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,20 @@ 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). +## 2.0.3 - TBC + +### Fixed + +* Optimisations for TextGrid rendering +* Data binding with widget.List sometimes crash while scrolling (#2125) +* Fix compilation on FreeBSD 13 +* DataLists should notify only once when change. +* Keyboard will appear on Android in disabled Entry Widget (#2139) +* Save dialog with filename for Android +* form widget can't draw hinttext of appended item. (#2028) +* Don't create empty shortcuts (#2148) + + ## 2.0.2 - 1 April 2021 ### Changed From a1292392b16171f19e4008db015592dbe36bd77f Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 21 Apr 2021 17:26:40 +0100 Subject: [PATCH 30/57] Attempt getting fixed staticcheck --- .github/workflows/static_analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index 18e2dd32c0..c511d94aee 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -16,7 +16,7 @@ jobs: GO111MODULE=off go get golang.org/x/tools/cmd/goimports GO111MODULE=off go get github.com/fzipp/gocyclo/cmd/gocyclo GO111MODULE=off go get golang.org/x/lint/golint - GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck + GO111MODULE=off go get -u honnef.co/go/tools/cmd/staticcheck - name: Cleanup repository run: rm -rf vendor/ From 5cc853474d2fbd685ae53c101f911bb5e0d210d6 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 21 Apr 2021 17:48:50 +0100 Subject: [PATCH 31/57] Fix static check error --- .github/workflows/static_analysis.yml | 2 +- internal/driver/util_test.go | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/static_analysis.yml b/.github/workflows/static_analysis.yml index c511d94aee..18e2dd32c0 100644 --- a/.github/workflows/static_analysis.yml +++ b/.github/workflows/static_analysis.yml @@ -16,7 +16,7 @@ jobs: GO111MODULE=off go get golang.org/x/tools/cmd/goimports GO111MODULE=off go get github.com/fzipp/gocyclo/cmd/gocyclo GO111MODULE=off go get golang.org/x/lint/golint - GO111MODULE=off go get -u honnef.co/go/tools/cmd/staticcheck + GO111MODULE=off go get honnef.co/go/tools/cmd/staticcheck - name: Cleanup repository run: rm -rf vendor/ diff --git a/internal/driver/util_test.go b/internal/driver/util_test.go index b9a006e539..9407de0763 100644 --- a/internal/driver/util_test.go +++ b/internal/driver/util_test.go @@ -439,23 +439,23 @@ type objectTree struct { size fyne.Size } -func (o objectTree) Size() fyne.Size { +func (o *objectTree) Size() fyne.Size { return o.size } -func (o objectTree) Resize(size fyne.Size) { +func (o *objectTree) Resize(size fyne.Size) { o.size = size } -func (o objectTree) Position() fyne.Position { +func (o *objectTree) Position() fyne.Position { return o.pos } -func (o objectTree) Move(position fyne.Position) { +func (o *objectTree) Move(position fyne.Position) { o.pos = position } -func (o objectTree) MinSize() fyne.Size { +func (o *objectTree) MinSize() fyne.Size { return o.size } @@ -463,18 +463,18 @@ func (o objectTree) Visible() bool { return !o.hidden } -func (o objectTree) Show() { +func (o *objectTree) Show() { o.hidden = false } -func (o objectTree) Hide() { +func (o *objectTree) Hide() { o.hidden = true } -func (o objectTree) Refresh() { +func (o *objectTree) Refresh() { } -func (o objectTree) CreateRenderer() fyne.WidgetRenderer { +func (o *objectTree) CreateRenderer() fyne.WidgetRenderer { r := &objectTreeRenderer{} r.SetObjects(o.children) return r From b5bbc40830a1fd9890d72dc775eba92c4a59326d Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 14 Apr 2021 20:19:35 +0100 Subject: [PATCH 32/57] =?UTF-8?q?=C2=96Use=20the=20correct=20install=20loc?= =?UTF-8?q?ation=20for=20Windows=20and=20cleanup=20.syso?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/fyne/commands/package-windows.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/fyne/commands/package-windows.go b/cmd/fyne/commands/package-windows.go index 82aba0ca7f..f309e9f41b 100644 --- a/cmd/fyne/commands/package-windows.go +++ b/cmd/fyne/commands/package-windows.go @@ -89,6 +89,7 @@ func (p *packager) packageWindows() error { if err != nil { return errors.Wrap(err, "Failed to write .syso file") } + defer os.Remove(outPath) err = os.Remove(icoPath) if err != nil { @@ -106,7 +107,7 @@ func (p *packager) packageWindows() error { } if p.install { - err := runAsAdminWindows("copy", "\"\""+p.exe+"\"\"", "\"\""+filepath.Join(os.Getenv("ProgramFiles"), p.name)+"\"\"") + err := runAsAdminWindows("copy", "\"\""+p.exe+"\"\"", "\"\""+filepath.Join(p.dir, p.name)+"\"\"") if err != nil { return errors.Wrap(err, "Failed to run as administrator") } From 111e7a5f407585f631aeac4cae98de36e7fc33e6 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 21 Apr 2021 17:52:24 +0100 Subject: [PATCH 33/57] More fix --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b4b1b2343..5e0094d9d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ More detailed release notes can be found on the [releases page](https://github.c * Save dialog with filename for Android * form widget can't draw hinttext of appended item. (#2028) * Don't create empty shortcuts (#2148) +* Install directory for windows install command contains ".exe" ## 2.0.2 - 1 April 2021 From 1b80214f83c863e530a1f1e78785792407db7482 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Thu, 22 Apr 2021 09:43:48 +0100 Subject: [PATCH 34/57] Fix wayland build Relates to #1851 --- go.mod | 2 +- go.sum | 6 ++---- ...-protocol.h => wayland-xdg-decoration-client-protocol.h} | 0 vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go | 2 +- vendor/modules.txt | 2 +- 5 files changed, 5 insertions(+), 7 deletions(-) rename vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/{wayland-xdg-decoration-unstable-v1-client-protocol.h => wayland-xdg-decoration-client-protocol.h} (100%) diff --git a/go.mod b/go.mod index ab4380bd56..4e500510a3 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/fsnotify/fsnotify v1.4.9 github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb github.com/godbus/dbus/v5 v5.0.4 github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526 diff --git a/go.sum b/go.sum index 4a21b1cb61..a074a968ef 100644 --- a/go.sum +++ b/go.sum @@ -10,14 +10,12 @@ github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8 github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f h1:rguJ/t99j/6zRSFzsBKlsmmyl+vOvCeTJ+2uTBvuXFI= -github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a h1:3TAJhl8vXyli0tooKB0vd6gLCyBdWL4QEYbDoJpHEZk= github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a/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/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-client-protocol.h similarity index 100% rename from vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.h rename to vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-client-protocol.h diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go index 22ff0280b2..db5def5f11 100644 --- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go +++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go @@ -7,4 +7,4 @@ package glfw // generate` on this package. This exists to invalidate the build cache (see // https://github.com/go-gl/glfw/issues/269), which is unaffected by C source // inputs. -const upstreamTreeSHA = "4490c2c270a92046291b021c15e33340289b33db" +const upstreamTreeSHA = "35599f5c137b48f8395629e686000729426bf966" diff --git a/vendor/modules.txt b/vendor/modules.txt index 4d72bc40c9..f0cea6fed1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -25,7 +25,7 @@ github.com/fyne-io/mobile/internal/mobileinit # github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 github.com/go-gl/gl/v3.1/gles2 github.com/go-gl/gl/v3.2-core/gl -# github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 +# github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb github.com/go-gl/glfw/v3.3/glfw github.com/go-gl/glfw/v3.3/glfw/glfw/deps github.com/go-gl/glfw/v3.3/glfw/glfw/deps/glad From 39eb32e004e0ee4b613a15a61948839c08d14e21 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Thu, 15 Apr 2021 22:54:22 +0100 Subject: [PATCH 35/57] Fix tab button layout on mobile --- container/apptabs.go | 45 +++++++++++++++---- container/apptabs_mobile_test.go | 10 ++--- .../apptabs/mobile/layout_bottom_ico.xml | 14 ------ 3 files changed, 42 insertions(+), 27 deletions(-) delete mode 100644 container/testdata/apptabs/mobile/layout_bottom_ico.xml diff --git a/container/apptabs.go b/container/apptabs.go index ccebc08dd4..af53b6ec0e 100644 --- a/container/apptabs.go +++ b/container/apptabs.go @@ -211,13 +211,25 @@ func (r *appTabsRenderer) Destroy() { func (r *appTabsRenderer) Layout(size fyne.Size) { tabBarMinSize := r.tabBar.MinSize() + if fyne.CurrentDevice().IsMobile() { + cells := len(r.tabBar.Objects) + if cells == 0 { + cells = 1 + } + + if fyne.IsVertical(fyne.CurrentDevice().Orientation()) { + r.tabBar.Layout = layout.NewGridLayoutWithColumns(cells) + } else { + r.tabBar.Layout = layout.NewGridLayoutWithRows(cells) + } + } var tabBarPos fyne.Position var tabBarSize fyne.Size var linePos fyne.Position var lineSize fyne.Size var childPos fyne.Position var childSize fyne.Size - switch r.adaptedLocation() { + switch r.adaptedLocation(r.container.tabLocation) { case TabLocationTop: buttonHeight := tabBarMinSize.Height tabBarPos = fyne.NewPos(0, 0) @@ -327,13 +339,25 @@ func (r *appTabsRenderer) Refresh() { canvas.Refresh(r.container) } -func (r *appTabsRenderer) adaptedLocation() TabLocation { - tabLocation := r.container.tabLocation - if fyne.CurrentDevice().IsMobile() && (tabLocation == TabLocationLeading || tabLocation == TabLocationTrailing) { - return TabLocationBottom +func (r *appTabsRenderer) adaptedLocation(l TabLocation) TabLocation { + // Mobile has limited screen space, so don't put app tab bar on long edges + if d := fyne.CurrentDevice(); d.IsMobile() { + if o := d.Orientation(); fyne.IsVertical(o) { + if l == TabLocationLeading { + return TabLocationTop + } else if l == TabLocationTrailing { + return TabLocationBottom + } + } else { + if l == TabLocationTop { + return TabLocationLeading + } else if l == TabLocationBottom { + return TabLocationTrailing + } + } } - return r.container.tabLocation + return l } func (r *appTabsRenderer) buildButton(item *TabItem, iconPos buttonIconPosition) *tabButton { @@ -352,7 +376,12 @@ func (r *appTabsRenderer) buildTabBar(buttons []fyne.CanvasObject) *fyne.Contain if cells == 0 { cells = 1 } - lay = layout.NewGridLayout(cells) + + if fyne.IsVertical(fyne.CurrentDevice().Orientation()) { + lay = layout.NewGridLayoutWithColumns(cells) + } else { + lay = layout.NewGridLayoutWithRows(cells) + } } else if r.container.tabLocation == TabLocationLeading || r.container.tabLocation == TabLocationTrailing { lay = layout.NewVBoxLayout() } else { @@ -371,7 +400,7 @@ func (r *appTabsRenderer) moveSelection() { var underlinePos fyne.Position var underlineSize fyne.Size - switch r.adaptedLocation() { + switch r.adaptedLocation(r.container.tabLocation) { case TabLocationTop: underlinePos = fyne.NewPos(selected.Position().X, r.tabBar.MinSize().Height) underlineSize = fyne.NewSize(selected.Size().Width, theme.Padding()) diff --git a/container/apptabs_mobile_test.go b/container/apptabs_mobile_test.go index 8a7533d5cb..cb3e6534b9 100644 --- a/container/apptabs_mobile_test.go +++ b/container/apptabs_mobile_test.go @@ -232,25 +232,25 @@ func TestTabContainer_Layout(t *testing.T) { name: "bottom: tab with icon only", item: container.NewTabItemWithIcon("", theme.InfoIcon(), canvas.NewCircle(theme.BackgroundColor())), location: container.TabLocationBottom, - want: "apptabs/mobile/layout_bottom_ico.xml", + want: "apptabs/mobile/layout_bottom_icon.xml", }, { name: "leading: tab with icon and text", item: container.NewTabItemWithIcon("Text1", theme.CancelIcon(), canvas.NewCircle(theme.BackgroundColor())), location: container.TabLocationLeading, - want: "apptabs/mobile/layout_bottom_icon_and_text.xml", + want: "apptabs/mobile/layout_top_icon_and_text.xml", }, { name: "leading: tab with text only", item: container.NewTabItem("Text2", canvas.NewCircle(theme.BackgroundColor())), location: container.TabLocationLeading, - want: "apptabs/mobile/layout_bottom_text.xml", + want: "apptabs/mobile/layout_top_text.xml", }, { name: "leading: tab with icon only", item: container.NewTabItemWithIcon("", theme.InfoIcon(), canvas.NewCircle(theme.BackgroundColor())), location: container.TabLocationLeading, - want: "apptabs/mobile/layout_bottom_icon.xml", + want: "apptabs/mobile/layout_top_icon.xml", }, { name: "trailing: tab with icon and text", @@ -301,7 +301,7 @@ func TestTabContainer_SetTabLocation(t *testing.T) { tabs.SetTabLocation(container.TabLocationLeading) w.Resize(tabs.MinSize()) - test.AssertRendersToMarkup(t, "apptabs/mobile/tab_location_bottom.xml", c, "leading is the same as bottom on mobile") + test.AssertRendersToMarkup(t, "apptabs/mobile/tab_location_top.xml", c, "leading is the same as top on mobile") tabs.SetTabLocation(container.TabLocationBottom) w.Resize(tabs.MinSize()) diff --git a/container/testdata/apptabs/mobile/layout_bottom_ico.xml b/container/testdata/apptabs/mobile/layout_bottom_ico.xml deleted file mode 100644 index 9bfe21d353..0000000000 --- a/container/testdata/apptabs/mobile/layout_bottom_ico.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - From 2830285e72146f421ba11b6bf188c0cf4e060a56 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Thu, 22 Apr 2021 10:04:03 +0100 Subject: [PATCH 36/57] Latest Changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5e0094d9d1..18dec5435a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ More detailed release notes can be found on the [releases page](https://github.c * form widget can't draw hinttext of appended item. (#2028) * Don't create empty shortcuts (#2148) * Install directory for windows install command contains ".exe" +* Fix compilation for Linux Wayland apps +* Fix tab button layout on mobile (#2117) ## 2.0.2 - 1 April 2021 From 7d758fad18934b9533e95e6f8bd2ad9ae612620d Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 23 Apr 2021 13:54:02 +0100 Subject: [PATCH 37/57] Docs typo --- container/layouts.go | 2 +- layout/gridlayout.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/container/layouts.go b/container/layouts.go index 6bd7034172..72be1ed4bf 100644 --- a/container/layouts.go +++ b/container/layouts.go @@ -52,7 +52,7 @@ func NewGridWithColumns(cols int, objects ...fyne.CanvasObject) *fyne.Container } // NewGridWithRows creates a new container with the specified objects and using the grid layout with -// a specified number of columns. The number of columns will depend on how many children are in the container. +// a specified number of rows. The number of columns will depend on how many children are in the container. // // Since: 1.4 func NewGridWithRows(rows int, objects ...fyne.CanvasObject) *fyne.Container { diff --git a/layout/gridlayout.go b/layout/gridlayout.go index 32e4e69443..1a68779ca9 100644 --- a/layout/gridlayout.go +++ b/layout/gridlayout.go @@ -31,7 +31,7 @@ func NewGridLayoutWithColumns(cols int) fyne.Layout { return &gridLayout{Cols: cols} } -// NewGridLayoutWithRows returns a new grid layout that specifies a row count that creates new columns as required. +// NewGridLayoutWithRows returns a new grid layout that specifies a row count that creates new rows as required. func NewGridLayoutWithRows(rows int) fyne.Layout { return &gridLayout{Cols: rows, vertical: true} } From b0ebeaafc83e8a28aa984c2b6c26a30e2f95b74b Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 23 Apr 2021 16:09:23 +0100 Subject: [PATCH 38/57] Move to unverified label on new bugs --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 390029dd94..d8863c1a31 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: 'bug' +labels: 'unverified' assignees: '' --- From 36ebe7dcfec580aeb861ddfd141ec85c0e47ddef Mon Sep 17 00:00:00 2001 From: Jacalz Date: Sun, 18 Apr 2021 18:14:23 +0200 Subject: [PATCH 39/57] Fix movement of select entry popup The popup was not being moved when open. --- widget/select_entry.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/widget/select_entry.go b/widget/select_entry.go index ce30860491..7df4a43393 100644 --- a/widget/select_entry.go +++ b/widget/select_entry.go @@ -68,6 +68,16 @@ func (e *SelectEntry) MinSize() fyne.Size { return min } +// Move changes the relative position of the select entry. +// +// Implements: fyne.Widget +func (e *SelectEntry) Move(pos fyne.Position) { + e.Entry.Move(pos) + if e.popUp != nil { + e.popUp.Move(e.popUpPos()) + } +} + // Resize changes the size of the select entry. // // Implements: fyne.Widget @@ -96,15 +106,17 @@ func (e *SelectEntry) SetOptions(options []string) { } } +func (e *SelectEntry) popUpPos() fyne.Position { + entryPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(e.super()) + return entryPos.Add(fyne.NewPos(0, e.Size().Height-theme.InputBorderSize())) +} + func (e *SelectEntry) setupDropDown() *Button { dropDownButton := NewButton("", func() { c := fyne.CurrentApp().Driver().CanvasForObject(e.super()) - entryPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(e.super()) - popUpPos := entryPos.Add(fyne.NewPos(0, e.Size().Height-theme.InputBorderSize())) - e.popUp = NewPopUpMenu(e.dropDown, c) - e.popUp.ShowAtPosition(popUpPos) + e.popUp.ShowAtPosition(e.popUpPos()) e.popUp.Resize(fyne.NewSize(e.Size().Width, e.popUp.MinSize().Height)) }) dropDownButton.Importance = LowImportance From e4f24b3a2f71a629d2af133a5788ca03280bc5f8 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Wed, 21 Apr 2021 18:28:37 +0200 Subject: [PATCH 40/57] Add test, many thanks to @fpabl0 --- widget/select_entry_test.go | 54 ++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/widget/select_entry_test.go b/widget/select_entry_test.go index 9d84f44adf..627ab5e971 100644 --- a/widget/select_entry_test.go +++ b/widget/select_entry_test.go @@ -1,4 +1,4 @@ -package widget_test +package widget import ( "testing" @@ -6,7 +6,6 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/test" "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" "github.com/stretchr/testify/assert" ) @@ -16,7 +15,7 @@ func TestSelectEntry_Disableable(t *testing.T) { defer test.NewApp() options := []string{"A", "B", "C"} - e := widget.NewSelectEntry(options) + e := NewSelectEntry(options) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -54,7 +53,7 @@ func TestSelectEntry_DropDown(t *testing.T) { defer test.NewApp() options := []string{"A", "B", "C"} - e := widget.NewSelectEntry(options) + e := NewSelectEntry(options) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -81,12 +80,43 @@ func TestSelectEntry_DropDown(t *testing.T) { assert.Equal(t, "C", e.Text) } +func TestSelectEntry_DropDownMove(t *testing.T) { + test.NewApp() + defer test.NewApp() + + e := NewSelectEntry([]string{"one"}) + w := test.NewWindow(e) + defer w.Close() + entrySize := e.MinSize() + w.Resize(entrySize.Add(fyne.NewSize(100, 100))) + e.Resize(entrySize) + + // open the popup + test.Tap(e.ActionItem.(fyne.Tappable)) + + // first movement + e.Move(fyne.NewPos(10, 10)) + assert.Equal(t, fyne.NewPos(10, 10), e.Entry.Position()) + assert.Equal(t, + fyne.NewPos(10, 10+entrySize.Height-theme.InputBorderSize()), + e.popUp.Position(), + ) + + // second movement + e.Move(fyne.NewPos(30, 27)) + assert.Equal(t, fyne.NewPos(30, 27), e.Entry.Position()) + assert.Equal(t, + fyne.NewPos(30, 27+entrySize.Height-theme.InputBorderSize()), + e.popUp.Position(), + ) +} + func TestSelectEntry_DropDownResize(t *testing.T) { test.NewApp() defer test.NewApp() options := []string{"A", "B", "C"} - e := widget.NewSelectEntry(options) + e := NewSelectEntry(options) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -114,7 +144,7 @@ func TestSelectEntry_MinSize(t *testing.T) { largeOptions := []string{"Large Option A", "Larger Option B", "Very Large Option C"} largeOptionsMinWidth := optionsMinSize(largeOptions).Width - labelHeight := widget.NewLabel("W").MinSize().Height + labelHeight := NewLabel("W").MinSize().Height tests := map[string]struct { placeholder string @@ -150,7 +180,7 @@ func TestSelectEntry_MinSize(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - e := widget.NewSelectEntry(tt.options) + e := NewSelectEntry(tt.options) e.PlaceHolder = tt.placeholder e.Text = tt.value assert.Equal(t, tt.want, e.MinSize()) @@ -162,7 +192,7 @@ func TestSelectEntry_SetOptions(t *testing.T) { test.NewApp() defer test.NewApp() - e := widget.NewSelectEntry([]string{"A", "B", "C"}) + e := NewSelectEntry([]string{"A", "B", "C"}) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -184,7 +214,7 @@ func TestSelectEntry_SetOptions_Empty(t *testing.T) { test.NewApp() defer test.NewApp() - e := widget.NewSelectEntry([]string{}) + e := NewSelectEntry([]string{}) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -203,13 +233,13 @@ func dropDownIconWidth() float32 { } func emptyTextWidth() float32 { - return widget.NewLabel("M").MinSize().Width + return NewLabel("M").MinSize().Width } func optionsMinSize(options []string) fyne.Size { - var labels []*widget.Label + var labels []*Label for _, option := range options { - labels = append(labels, widget.NewLabel(option)) + labels = append(labels, NewLabel(option)) } minWidth := float32(0) minHeight := float32(0) From 4b5599e7a61165e8a1def946e204ef0c9a8136dc Mon Sep 17 00:00:00 2001 From: Jacalz Date: Wed, 21 Apr 2021 20:14:25 +0200 Subject: [PATCH 41/57] Rename the test file to be internal --- widget/{select_entry_test.go => select_entry_internal_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename widget/{select_entry_test.go => select_entry_internal_test.go} (100%) diff --git a/widget/select_entry_test.go b/widget/select_entry_internal_test.go similarity index 100% rename from widget/select_entry_test.go rename to widget/select_entry_internal_test.go From 0519ff12fd8f1209549281186ee602954e390022 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 23 Apr 2021 20:43:08 +0100 Subject: [PATCH 42/57] Merged change --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18dec5435a..16adfbb1f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ More detailed release notes can be found on the [releases page](https://github.c * Install directory for windows install command contains ".exe" * Fix compilation for Linux Wayland apps * Fix tab button layout on mobile (#2117) +* List does not move if a select widget moves with popup open ## 2.0.2 - 1 April 2021 From 4535fb2c61b482f6adbe75cf79448e58884efc83 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Sat, 17 Apr 2021 09:47:50 +0200 Subject: [PATCH 43/57] Pre-allocate menuItem slice for widget.Select Seems to be about 30% faster when we have 500 objects. Updates #2164 --- widget/select.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/widget/select.go b/widget/select.go index ca2dbbfe00..1bc8ea5b6c 100644 --- a/widget/select.go +++ b/widget/select.go @@ -236,13 +236,12 @@ func (s *Select) popUpPos() fyne.Position { } func (s *Select) showPopUp() { - var items []*fyne.MenuItem - for _, option := range s.Options { + items := make([]*fyne.MenuItem, len(s.Options)) + for i, option := range s.Options { text := option // capture - item := fyne.NewMenuItem(option, func() { + items[i] = fyne.NewMenuItem(option, func() { s.optionTapped(text) }) - items = append(items, item) } c := fyne.CurrentApp().Driver().CanvasForObject(s.super()) From ed9accc47ce494d05393f2b6c0d1d1bd4044e6d8 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Sat, 17 Apr 2021 20:58:47 +0200 Subject: [PATCH 44/57] Apply suggestion from Pablo --- widget/select.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/widget/select.go b/widget/select.go index 1bc8ea5b6c..8fe5d848c0 100644 --- a/widget/select.go +++ b/widget/select.go @@ -237,9 +237,9 @@ func (s *Select) popUpPos() fyne.Position { func (s *Select) showPopUp() { items := make([]*fyne.MenuItem, len(s.Options)) - for i, option := range s.Options { - text := option // capture - items[i] = fyne.NewMenuItem(option, func() { + for i := range s.Options { + text := s.Options[i] // capture + items[i] = fyne.NewMenuItem(text, func() { s.optionTapped(text) }) } From 1f71ff94d69f74ee294434a8584a9cf6f237be64 Mon Sep 17 00:00:00 2001 From: Jacalz Date: Sat, 17 Apr 2021 20:59:41 +0200 Subject: [PATCH 45/57] Optimize selection as well Selection used the slow path fore selection, SetSelected(), which loops through all options. We know that our item is in the list and can thus use the fast path. --- widget/select.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/widget/select.go b/widget/select.go index 8fe5d848c0..bc5e0e6973 100644 --- a/widget/select.go +++ b/widget/select.go @@ -225,11 +225,6 @@ func (s *Select) object() fyne.Widget { return nil } -func (s *Select) optionTapped(text string) { - s.SetSelected(text) - s.popUp = nil -} - func (s *Select) popUpPos() fyne.Position { buttonPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(s.super()) return buttonPos.Add(fyne.NewPos(0, s.Size().Height-theme.InputBorderSize())) @@ -240,7 +235,8 @@ func (s *Select) showPopUp() { for i := range s.Options { text := s.Options[i] // capture items[i] = fyne.NewMenuItem(text, func() { - s.optionTapped(text) + s.updateSelected(text) + s.popUp = nil }) } From 8af449b36f3e62dd146bc8efe52a5d9c6ffd937b Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 23 Apr 2021 20:54:53 +0100 Subject: [PATCH 46/57] Merged more fixes --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16adfbb1f7..ab8b8199f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ More detailed release notes can be found on the [releases page](https://github.c * Fix compilation for Linux Wayland apps * Fix tab button layout on mobile (#2117) * List does not move if a select widget moves with popup open +* Speed improvements to Select drop down (#2164) ## 2.0.2 - 1 April 2021 From e75dd0a50ac3c31c4f9daf0f18ca6acaff60bdcf Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 24 Apr 2021 20:09:22 +0100 Subject: [PATCH 47/57] Don't trigger blank shortcut for multiple modifiers either --- internal/driver/glfw/window.go | 19 ++++++------------- internal/driver/glfw/window_test.go | 2 ++ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index d4b6eacfcb..d1039f1938 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -1120,7 +1120,7 @@ func (w *window) keyPressed(_ *glfw.Window, key glfw.Key, scancode int, action g } } - if shortcut == nil && keyDesktopModifier != 0 && !isKeyModifierPair(keyName, keyDesktopModifier) && keyDesktopModifier != desktop.ShiftModifier { + if shortcut == nil && keyDesktopModifier != 0 && !isKeyModifier(keyName) && keyDesktopModifier != desktop.ShiftModifier { shortcut = &desktop.CustomShortcut{ KeyName: keyName, Modifier: keyDesktopModifier, @@ -1418,16 +1418,9 @@ func (d *gLDriver) AllWindows() []fyne.Window { return d.windows } -func isKeyModifierPair(keyName fyne.KeyName, modifier desktop.Modifier) bool { - switch modifier { - case desktop.ShiftModifier: - return (keyName == desktop.KeyShiftLeft || keyName == desktop.KeyShiftRight) - case desktop.ControlModifier: - return (keyName == desktop.KeyControlLeft || keyName == desktop.KeyControlRight) - case desktop.AltModifier: - return (keyName == desktop.KeyAltLeft || keyName == desktop.KeyAltRight) - case desktop.SuperModifier: - return (keyName == desktop.KeySuperLeft || keyName == desktop.KeySuperRight) - } - return false +func isKeyModifier(keyName fyne.KeyName) bool { + return keyName == desktop.KeyShiftLeft || keyName == desktop.KeyShiftRight || + keyName == desktop.KeyControlLeft || keyName == desktop.KeyControlRight || + keyName == desktop.KeyAltLeft || keyName == desktop.KeyAltRight || + keyName == desktop.KeySuperLeft || keyName == desktop.KeySuperRight } diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index 98f435c830..2f216cec2c 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -944,7 +944,9 @@ func TestWindow_CaptureTypedShortcut(t *testing.T) { w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) w.keyPressed(nil, glfw.KeyLeftControl, 0, glfw.Action(glfw.Press), glfw.ModControl) + w.keyPressed(nil, glfw.KeyLeftShift, 0, glfw.Action(glfw.Press), glfw.ModControl) w.keyPressed(nil, glfw.KeyF, 0, glfw.Action(glfw.Press), glfw.ModControl) + w.keyPressed(nil, glfw.KeyLeftShift, 0, glfw.Action(glfw.Press), glfw.ModControl) w.keyPressed(nil, glfw.KeyLeftControl, 0, glfw.Action(glfw.Release), glfw.ModControl) w.keyPressed(nil, glfw.KeyF, 0, glfw.Action(glfw.Release), glfw.ModControl) From 6b53bbc9438998a05ac5e8947511258707a0f975 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 24 Apr 2021 21:00:45 +0100 Subject: [PATCH 48/57] Remove mysterious TextGrid glitching It was cells not being refreshed it seems --- widget/textgrid.go | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/widget/textgrid.go b/widget/textgrid.go index 40392ea878..c5f5becb8f 100644 --- a/widget/textgrid.go +++ b/widget/textgrid.go @@ -358,11 +358,9 @@ func (t *textGridRenderer) setCellRune(str rune, pos int, style, rowStyle TextGr } else if rowStyle != nil && rowStyle.TextColor() != nil { fg = rowStyle.TextColor() } - if (text.Text == "" || str != []rune(text.Text)[0]) || fg != text.Color { - text.Text = string(str) - text.Color = fg - canvas.Refresh(text) - } + text.Text = string(str) + text.Color = fg + canvas.Refresh(text) rect := t.objects[pos*2].(*canvas.Rectangle) bg := color.Color(color.Transparent) @@ -371,10 +369,8 @@ func (t *textGridRenderer) setCellRune(str rune, pos int, style, rowStyle TextGr } else if rowStyle != nil && rowStyle.BackgroundColor() != nil { bg = rowStyle.BackgroundColor() } - if bg != rect.FillColor { - rect.FillColor = bg - canvas.Refresh(rect) - } + rect.FillColor = bg + canvas.Refresh(rect) } func (t *textGridRenderer) addCellsIfRequired() { From ba981f06f6681164a7d54acd7a2caa826ebe65b1 Mon Sep 17 00:00:00 2001 From: Ankush Jadhav Date: Sun, 25 Apr 2021 01:39:13 +0530 Subject: [PATCH 49/57] fix: allow apptabs to store state while transitioning tabs (#2192) --- container/apptabs.go | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/container/apptabs.go b/container/apptabs.go index af53b6ec0e..49bfa84671 100644 --- a/container/apptabs.go +++ b/container/apptabs.go @@ -18,10 +18,11 @@ import ( type AppTabs struct { widget.BaseWidget - Items []*TabItem - OnChanged func(tab *TabItem) - current int - tabLocation TabLocation + Items []*TabItem + OnChanged func(tab *TabItem) + current int + tabLocation TabLocation + isTransitioning bool } // TabItem represents a single view in a AppTabs. @@ -89,7 +90,7 @@ func (c *AppTabs) CreateRenderer() fyne.WidgetRenderer { c.BaseWidget.ExtendBaseWidget(c) r := &appTabsRenderer{line: canvas.NewRectangle(theme.ShadowColor()), underline: canvas.NewRectangle(theme.PrimaryColor()), container: c} - r.updateTabs() + r.updateTabs(false) return r } @@ -158,8 +159,10 @@ func (c *AppTabs) SelectTabIndex(index int) { if index < 0 || index >= len(c.Items) || c.current == index { return } + c.isTransitioning = true c.current = index c.Refresh() + c.isTransitioning = false if c.OnChanged != nil { c.OnChanged(c.Items[c.current]) @@ -277,7 +280,7 @@ func (r *appTabsRenderer) Layout(size fyne.Size) { child.Move(childPos) child.Resize(childSize) } - r.moveSelection() + r.moveSelection(r.container.isTransitioning) } func (r *appTabsRenderer) MinSize() fyne.Size { @@ -311,7 +314,7 @@ func (r *appTabsRenderer) Refresh() { r.line.Refresh() r.underline.FillColor = theme.PrimaryColor() - if r.updateTabs() { + if r.updateTabs(r.container.isTransitioning) { r.Layout(r.container.Size()) } else { current := r.container.current @@ -335,7 +338,7 @@ func (r *appTabsRenderer) Refresh() { button.Refresh() } } - r.moveSelection() + r.moveSelection(r.container.isTransitioning) canvas.Refresh(r.container) } @@ -391,7 +394,7 @@ func (r *appTabsRenderer) buildTabBar(buttons []fyne.CanvasObject) *fyne.Contain return fyne.NewContainerWithLayout(lay, buttons...) } -func (r *appTabsRenderer) moveSelection() { +func (r *appTabsRenderer) moveSelection(withAnimation bool) { if r.container.current < 0 { r.underline.Hide() return @@ -422,6 +425,13 @@ func (r *appTabsRenderer) moveSelection() { return } + if !withAnimation && r.animation == nil { + r.underline.Move(underlinePos) + r.underline.Resize(underlineSize) + canvas.Refresh(r.underline) + return + } + if r.animation != nil { r.animation.Stop() } @@ -468,7 +478,7 @@ func (r *appTabsRenderer) tabsInSync() bool { return true } -func (r *appTabsRenderer) updateTabs() bool { +func (r *appTabsRenderer) updateTabs(withAnimation bool) bool { if r.tabsInSync() { return false } @@ -497,7 +507,7 @@ func (r *appTabsRenderer) updateTabs() bool { } r.tabBar = r.buildTabBar(buttons) r.objects = objects - r.moveSelection() + r.moveSelection(withAnimation) return true } From 79572d20a4bdb6a24b78b6b0a46d0a7fb2776690 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 27 Apr 2021 11:07:01 +0100 Subject: [PATCH 50/57] Add missing position info in ScrollEvent Fixes #2199 --- internal/driver/glfw/window.go | 4 +++- internal/driver/glfw/window_test.go | 32 +++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index d1039f1938..b37c5872e3 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -825,7 +825,7 @@ func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent) { } func (w *window) mouseScrolled(viewport *glfw.Window, xoff float64, yoff float64) { - co, _, _ := w.findObjectAtPositionMatching(w.canvas, w.mousePos, func(object fyne.CanvasObject) bool { + co, pos, _ := w.findObjectAtPositionMatching(w.canvas, w.mousePos, func(object fyne.CanvasObject) bool { _, ok := object.(fyne.Scrollable) return ok }) @@ -838,6 +838,8 @@ func (w *window) mouseScrolled(viewport *glfw.Window, xoff float64, yoff float64 } ev := &fyne.ScrollEvent{} ev.Scrolled = fyne.NewDelta(float32(xoff)*scrollSpeed, float32(yoff)*scrollSpeed) + ev.Position = pos + ev.AbsolutePosition = w.mousePos wid.Scrolled(ev) } } diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index 2f216cec2c..5ca949c8d3 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -534,6 +534,22 @@ func TestWindow_HoverableOnDragging(t *testing.T) { assert.NotNil(t, dh.popMouseOutEvent()) } +func TestWindow_Scrolled(t *testing.T) { + w := createWindow("Test").(*window) + o := &scrollable{Rectangle: canvas.NewRectangle(color.White)} + o.SetMinSize(fyne.NewSize(100, 100)) + w.SetContent(o) + + w.mousePos = fyne.NewPos(50, 60) + w.mouseScrolled(w.viewport, 10, 10) + w.waitForEvents() + + if e, _ := o.popScrollEvent().(*fyne.PointEvent); assert.NotNil(t, e, "scroll event") { + assert.Equal(t, fyne.NewPos(50, 60), e.AbsolutePosition) + assert.Equal(t, fyne.NewPos(46, 56), e.Position) + } +} + func TestWindow_Tapped(t *testing.T) { w := createWindow("Test").(*window) rect := canvas.NewRectangle(color.White) @@ -1305,6 +1321,22 @@ func (ts *typedShortcutable) TypedShortcut(s fyne.Shortcut) { ts.capturedShortcuts = append(ts.capturedShortcuts, s) } +var _ fyne.Draggable = (*draggable)(nil) + +type scrollable struct { + *canvas.Rectangle + events []interface{} +} + +func (s *scrollable) Scrolled(e *fyne.ScrollEvent) { + s.events = append(s.events, e) +} + +func (s *scrollable) popScrollEvent() (e interface{}) { + e, s.events = pop(s.events) + return +} + // // Test helper // From 71f23257a23927ad6d0b96a542be60b2e91835cf Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 27 Apr 2021 11:32:17 +0100 Subject: [PATCH 51/57] We had included the wrong license for NOTO. Fixes #2193 --- theme/font/LICENSE.txt | 210 +++++------------------------------------ 1 file changed, 23 insertions(+), 187 deletions(-) diff --git a/theme/font/LICENSE.txt b/theme/font/LICENSE.txt index 75b52484ea..72fcf5fd06 100644 --- a/theme/font/LICENSE.txt +++ b/theme/font/LICENSE.txt @@ -1,202 +1,38 @@ +—————————————————————————————- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +—————————————————————————————- - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. - 1. Definitions. +DEFINITIONS +“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +“Reserved Font Name” refers to any names specified as such after the copyright statement(s). - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. +“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. +“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. +“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. +TERMINATION +This license becomes null and void if any of the above conditions are not met. - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file From 28abc8db0e2e3d7a0ca6c07a76d82b255806b65b Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 27 Apr 2021 10:23:19 +0100 Subject: [PATCH 52/57] Remove stale builds If no executable was specified, but one was in place, assume it is stale and remove --- cmd/fyne/commands/package.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmd/fyne/commands/package.go b/cmd/fyne/commands/package.go index eb09ced601..e00fa5bec2 100644 --- a/cmd/fyne/commands/package.go +++ b/cmd/fyne/commands/package.go @@ -157,6 +157,10 @@ func (p *packager) validate() error { if p.exe == "" { p.exe = filepath.Join(p.srcDir, exeName) + + if util.Exists(p.exe) { // the exe was not specified, assume stale + p.removeBuild() + } } else if p.os == "ios" || p.os == "android" { _, _ = fmt.Fprint(os.Stderr, "Parameter -executable is ignored for mobile builds.\n") } From b9b25ed78456f5813d7c49f02cbba050b30eafaf Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 28 Apr 2021 14:55:31 +0100 Subject: [PATCH 53/57] Fix paste issue on event type (though it did work ;) ) --- internal/driver/glfw/window_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index 5ca949c8d3..934b03a557 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -544,7 +544,7 @@ func TestWindow_Scrolled(t *testing.T) { w.mouseScrolled(w.viewport, 10, 10) w.waitForEvents() - if e, _ := o.popScrollEvent().(*fyne.PointEvent); assert.NotNil(t, e, "scroll event") { + if e, _ := o.popScrollEvent().(*fyne.ScrollEvent); assert.NotNil(t, e, "scroll event") { assert.Equal(t, fyne.NewPos(50, 60), e.AbsolutePosition) assert.Equal(t, fyne.NewPos(46, 56), e.Position) } From 7176620cc588c0359c3db0051f74feb4cd534b7f Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 27 Apr 2021 13:54:56 +0100 Subject: [PATCH 54/57] For release the profile is required --- cmd/fyne/commands/release.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/fyne/commands/release.go b/cmd/fyne/commands/release.go index 39865edb25..077dd22cea 100644 --- a/cmd/fyne/commands/release.go +++ b/cmd/fyne/commands/release.go @@ -311,6 +311,9 @@ func (r *releaser) validate() error { if r.certificate == "" { r.certificate = "3rd Party Mac Developer Application" } + if r.profile == "" { + return errors.New("missing required -profile parameter for macOS release") + } if r.category == "" { return errors.New("missing required -category parameter for macOS release") } else if !isValidMacOSCategory(r.category) { From 40915e0f2870ecae21765656ca81fafe4a09c8cc Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 28 Apr 2021 14:38:06 +0100 Subject: [PATCH 55/57] Better doc for exe param --- cmd/fyne/commands/package.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/fyne/commands/package.go b/cmd/fyne/commands/package.go index e00fa5bec2..ce005583fb 100644 --- a/cmd/fyne/commands/package.go +++ b/cmd/fyne/commands/package.go @@ -43,7 +43,7 @@ func NewPackager() Command { func (p *packager) AddFlags() { flag.StringVar(&p.os, "os", "", "The operating system to target (android, android/arm, android/arm64, android/amd64, android/386, darwin, freebsd, ios, linux, netbsd, openbsd, windows)") - flag.StringVar(&p.exe, "executable", "", "The path to the executable, default is the current dir main binary") + flag.StringVar(&p.exe, "executable", "", "Specify an existing binary instead of building before package") flag.StringVar(&p.srcDir, "sourceDir", "", "The directory to package, if executable is not set") flag.StringVar(&p.name, "name", "", "The name of the application, default is the executable file name") flag.StringVar(&p.icon, "icon", "Icon.png", "The name of the application icon file") From edd6d43bba5744e624159f6ae748c021d1bbe07d Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 28 Apr 2021 19:08:11 +0100 Subject: [PATCH 56/57] retry release --- CHANGELOG.md | 5 ++++- README.md | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab8b8199f7..c10fa9b0c6 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). -## 2.0.3 - TBC +## 2.0.3 - 30 April 2021 ### Fixed @@ -20,6 +20,9 @@ More detailed release notes can be found on the [releases page](https://github.c * Fix tab button layout on mobile (#2117) * List does not move if a select widget moves with popup open * Speed improvements to Select drop down (#2164) +* theme/fonts has an apache LICENSE file but it should have SIL OFL (#2193) +* Fix build requirements for target macOS platforms (#2154) +* ScrollEvent.Position and ScrollEvent.AbsolutePosition is 0,0 (#2199) ## 2.0.2 - 1 April 2021 diff --git a/README.md b/README.md index 27908ea92e..e23bafe647 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Go API Reference - 2.0.2 release + 2.0.3 release Join us on Slack
Code Status From dddf878f5bbb6181fc961a20446a1be1e2335fc4 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 30 Apr 2021 19:17:48 +0100 Subject: [PATCH 57/57] Fix errors in Changelog --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c10fa9b0c6..46fccdce9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,8 +18,8 @@ More detailed release notes can be found on the [releases page](https://github.c * Install directory for windows install command contains ".exe" * Fix compilation for Linux Wayland apps * Fix tab button layout on mobile (#2117) -* List does not move if a select widget moves with popup open -* Speed improvements to Select drop down (#2164) +* Options popup does not move if a SelectEntry widget moves with popup open +* Speed improvements to Select and SelectEntry drop down * theme/fonts has an apache LICENSE file but it should have SIL OFL (#2193) * Fix build requirements for target macOS platforms (#2154) * ScrollEvent.Position and ScrollEvent.AbsolutePosition is 0,0 (#2199)