From 947b785297b0256a680244e807bae57315ace541 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sun, 10 Sep 2023 21:44:00 +0100 Subject: [PATCH 01/32] Move to js GOOS flag from gopherjs to be consistent with tags --- cmd/fyne/internal/commands/build.go | 10 +++++----- cmd/fyne/internal/commands/build_test.go | 2 +- cmd/fyne/internal/commands/package-web.go | 2 +- cmd/fyne/internal/commands/package.go | 6 +++--- cmd/fyne/internal/commands/package_test.go | 22 +++++++++++----------- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/fyne/internal/commands/build.go b/cmd/fyne/internal/commands/build.go index 5cc55c59ec..1fe1eca901 100644 --- a/cmd/fyne/internal/commands/build.go +++ b/cmd/fyne/internal/commands/build.go @@ -137,7 +137,7 @@ func checkVersion(output string, versionConstraint *version.ConstraintGroup) err } func isWeb(goos string) bool { - return goos == "gopherjs" || goos == "wasm" + return goos == "js" || goos == "wasm" } func checkGoVersion(runner runner, versionConstraint *version.ConstraintGroup) error { @@ -197,7 +197,7 @@ func (b *Builder) build() error { goos = targetOS() } - if goos == "gopherjs" && runtime.GOOS == "windows" { + if goos == "js" && runtime.GOOS == "windows" { return errors.New("gopherjs doesn't support Windows. Only wasm target is supported for the web output. You can also use fyne-cross to solve this") } @@ -262,7 +262,7 @@ func (b *Builder) build() error { tags = append(tags, "release") } if len(tags) > 0 { - if goos == "gopherjs" { + if goos == "js" { args = append(args, "--tags") } else { args = append(args, "-tags") @@ -280,7 +280,7 @@ func (b *Builder) build() error { versionConstraint = version.NewConstrainGroupFromString(">=1.17") env = append(env, "GOARCH=wasm") env = append(env, "GOOS=js") - } else if goos == "gopherjs" { + } else if goos == "js" { _, err := b.runner.runOutput("version") if err != nil { fmt.Fprintf(os.Stderr, "Can not execute `gopherjs version`. Please do `go install github.com/gopherjs/gopherjs@latest`.\n") @@ -359,7 +359,7 @@ func (b *Builder) updateAndGetGoExecutable(goos string) runner { fyneGoModRunner = newCommand(goBin) b.runner = fyneGoModRunner } else { - if goos != "gopherjs" { + if goos != "js" { b.runner = newCommand("go") } else { b.runner = newCommand("gopherjs") diff --git a/cmd/fyne/internal/commands/build_test.go b/cmd/fyne/internal/commands/build_test.go index 42043f35a5..55646cf4e0 100644 --- a/cmd/fyne/internal/commands/build_test.go +++ b/cmd/fyne/internal/commands/build_test.go @@ -201,7 +201,7 @@ func Test_BuildGopherJSReleaseVersion(t *testing.T) { } gopherJSBuildTest := &testCommandRuns{runs: expected, t: t} - b := &Builder{appData: &appData{}, os: "gopherjs", srcdir: "myTest", release: true, runner: gopherJSBuildTest} + b := &Builder{appData: &appData{}, os: "js", srcdir: "myTest", release: true, runner: gopherJSBuildTest} err := b.build() if runtime.GOOS == "windows" { assert.NotNil(t, err) diff --git a/cmd/fyne/internal/commands/package-web.go b/cmd/fyne/internal/commands/package-web.go index 4735da6dde..55ed8312ad 100644 --- a/cmd/fyne/internal/commands/package-web.go +++ b/cmd/fyne/internal/commands/package-web.go @@ -39,7 +39,7 @@ func (p *Packager) packageWasm() error { } func (p *Packager) packageGopherJS() error { - appDir := util.EnsureSubDir(p.dir, "gopherjs") + appDir := util.EnsureSubDir(p.dir, "js") tpl := webData{ AppName: p.Name, diff --git a/cmd/fyne/internal/commands/package.go b/cmd/fyne/internal/commands/package.go index cac8d91ecc..c7ce92113a 100644 --- a/cmd/fyne/internal/commands/package.go +++ b/cmd/fyne/internal/commands/package.go @@ -41,7 +41,7 @@ func Package() *cli.Command { &cli.StringFlag{ Name: "target", Aliases: []string{"os"}, - Usage: "The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios, iossimulator, wasm, gopherjs, web).", + Usage: "The mobile platform to target (android, android/arm, android/arm64, android/amd64, android/386, ios, iossimulator, wasm, js, web).", Destination: &p.os, }, &cli.StringFlag{ @@ -243,7 +243,7 @@ func (p *Packager) buildPackage(runner runner, tags []string) ([]string, error) } bGopherJS := &Builder{ - os: "gopherjs", + os: "js", srcdir: p.srcDir, target: p.exe + ".js", release: p.release, @@ -322,7 +322,7 @@ func (p *Packager) doPackage(runner runner) error { return p.packageIOS(p.os, tags) case "wasm": return p.packageWasm() - case "gopherjs": + case "js": return p.packageGopherJS() case "web": return p.packageWeb() diff --git a/cmd/fyne/internal/commands/package_test.go b/cmd/fyne/internal/commands/package_test.go index f9cd6464d6..a34348dc42 100644 --- a/cmd/fyne/internal/commands/package_test.go +++ b/cmd/fyne/internal/commands/package_test.go @@ -343,7 +343,7 @@ func Test_buildPackageGopherJS(t *testing.T) { p := &Packager{ appData: &appData{}, - os: "gopherjs", + os: "js", srcDir: "myTest", exe: "myTest.js", release: true, @@ -394,7 +394,7 @@ func Test_PackageGopherJS(t *testing.T) { Name: "myTest", icon: "myTest.png", }, - os: "gopherjs", + os: "js", srcDir: "myTest", dir: "myTestTarget", exe: "myTest.js", @@ -409,7 +409,7 @@ func Test_PackageGopherJS(t *testing.T) { expectedEnsureSubDirRuns := mockEnsureSubDirRuns{ expected: []mockEnsureSubDir{ - {"myTestTarget", "gopherjs", "myTestTarget/gopherjs"}, + {"myTestTarget", "js", "myTestTarget/js"}, }, } utilEnsureSubDirMock = func(parent, name string) string { @@ -428,12 +428,12 @@ func Test_PackageGopherJS(t *testing.T) { expectedWriteFileRuns := mockWriteFileRuns{ expected: []mockWriteFile{ - {filepath.Join("myTestTarget", "gopherjs", "index.html"), nil}, - {filepath.Join("myTestTarget", "gopherjs", "spinner_light.gif"), nil}, - {filepath.Join("myTestTarget", "gopherjs", "spinner_dark.gif"), nil}, - {filepath.Join("myTestTarget", "gopherjs", "light.css"), nil}, - {filepath.Join("myTestTarget", "gopherjs", "dark.css"), nil}, - {filepath.Join("myTestTarget", "gopherjs", "webgl-debug.js"), nil}, + {filepath.Join("myTestTarget", "js", "index.html"), nil}, + {filepath.Join("myTestTarget", "js", "spinner_light.gif"), nil}, + {filepath.Join("myTestTarget", "js", "spinner_dark.gif"), nil}, + {filepath.Join("myTestTarget", "js", "light.css"), nil}, + {filepath.Join("myTestTarget", "js", "dark.css"), nil}, + {filepath.Join("myTestTarget", "js", "webgl-debug.js"), nil}, }, } utilWriteFileMock = func(target string, _ []byte) error { @@ -442,8 +442,8 @@ func Test_PackageGopherJS(t *testing.T) { expectedCopyFileRuns := mockCopyFileRuns{ expected: []mockCopyFile{ - {source: "myTest.png", target: filepath.Join("myTestTarget", "gopherjs", "icon.png")}, - {source: "myTest.js", target: filepath.Join("myTestTarget", "gopherjs", "myTest.js")}, + {source: "myTest.png", target: filepath.Join("myTestTarget", "js", "icon.png")}, + {source: "myTest.js", target: filepath.Join("myTestTarget", "js", "myTest.js")}, }, } utilCopyFileMock = func(source, target string) error { From a1aaa3c51ec1509c0534eeb5ff6cce08524828bf Mon Sep 17 00:00:00 2001 From: Roemer Date: Thu, 31 Aug 2023 12:36:05 +0200 Subject: [PATCH 02/32] Close an open branch with the left-key on a tree --- widget/tree.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/widget/tree.go b/widget/tree.go index 4660f588e8..99a476f02c 100644 --- a/widget/tree.go +++ b/widget/tree.go @@ -348,11 +348,17 @@ func (t *Tree) TypedKey(event *fyne.KeyEvent) { t.ScrollTo(t.currentFocus) t.RefreshItem(t.currentFocus) case fyne.KeyLeft: - t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) { - if id == t.currentFocus && p != "" { - t.currentFocus = p - } - }) + // If the current focus is on a branch which is open, just close it + if t.IsBranch(t.currentFocus) && t.IsBranchOpen(t.currentFocus) { + t.CloseBranch(t.currentFocus) + } else { + // Every other case should move the focus to the current parent node + t.walk(t.Root, "", 0, func(id, p TreeNodeID, _ bool, _ int) { + if id == t.currentFocus && p != "" { + t.currentFocus = p + } + }) + } t.RefreshItem(t.currentFocus) t.ScrollTo(t.currentFocus) From ecde2a22dd5c4eeacffd6c2bcbdab93bafac145c Mon Sep 17 00:00:00 2001 From: Roemer Date: Thu, 31 Aug 2023 14:03:39 +0200 Subject: [PATCH 03/32] Added a test for tree keyboard navigation --- widget/tree_internal_test.go | 121 +++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/widget/tree_internal_test.go b/widget/tree_internal_test.go index 60555f9a53..2107b4c982 100644 --- a/widget/tree_internal_test.go +++ b/widget/tree_internal_test.go @@ -194,6 +194,127 @@ func TestTree_Focus(t *testing.T) { assert.Equal(t, "foo", tree.selected[0]) } +func TestTree_Keyboard(t *testing.T) { + // Prepare data for a tree like this: + // item_1 + // |- item_1_1 + // |- item_1_1_1 + // |- item_1_1_2 + // |- item_1_2 + // |- item_1_2_1 + // |- item_1_2_2 + // item_2 + // |- item_2_1 + // |- item_2_2 + var treeData = map[string][]string{ + "": {"item_1", "item_2"}, + "item_1": {"item_1_1", "item_1_2"}, + "item_2": {"item_2_1", "item_2_2"}, + "item_1_1": {"item_1_1_1", "item_1_1_2"}, + "item_1_2": {"item_1_2_1", "item_1_2_2"}, + } + tree := NewTreeWithStrings(treeData) + window := test.NewWindow(tree) + defer window.Close() + window.Resize(tree.MinSize().Max(fyne.NewSize(250, 400))) + + canvas := window.Canvas().(test.WindowlessCanvas) + assert.Nil(t, canvas.Focused()) + + // Start with a fully collapsed tree + tree.CloseAllBranches() + + // Select the first node + canvas.FocusNext() + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), false) + + // Open the node "item_1" + tree.TypedKey(&fyne.KeyEvent{Name: fyne.KeyRight}) + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1_1", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), true) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), false) + + // Go to next node "item1_2" + tree.TypedKey(&fyne.KeyEvent{Name: fyne.KeyDown}) + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1_2", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), true) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), false) + + // Open the node "item_1_2" + tree.TypedKey(&fyne.KeyEvent{Name: fyne.KeyRight}) + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1_2_1", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), true) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), true) + + // Go to next node "item_1_2_2" + tree.TypedKey(&fyne.KeyEvent{Name: fyne.KeyDown}) + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1_2_2", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), true) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), true) + + // Press left on the non-branch node "item_1_2_2" + tree.TypedKey(&fyne.KeyEvent{Name: fyne.KeyLeft}) + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1_2", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), true) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), true) + + // Press left on the open branch node "item_1_2" + tree.TypedKey(&fyne.KeyEvent{Name: fyne.KeyLeft}) + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1_2", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), true) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), false) + + // Press left on the closed branch node "item_1_2" + tree.TypedKey(&fyne.KeyEvent{Name: fyne.KeyLeft}) + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), true) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), false) + + // Press left on the open branch node "item_1" + tree.TypedKey(&fyne.KeyEvent{Name: fyne.KeyLeft}) + // Validate the state + assert.NotNil(t, canvas.Focused()) + assert.Equal(t, "item_1", canvas.Focused().(*Tree).currentFocus) + assert.Equal(t, tree.IsBranchOpen("item_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_2"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_1"), false) + assert.Equal(t, tree.IsBranchOpen("item_1_2"), false) +} + func TestTree_Indentation(t *testing.T) { data := make(map[string][]string) tree := NewTreeWithStrings(data) From 7d020b70c0084c801ca69a58b4561f949d6ceb06 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 3 Oct 2023 22:58:36 +0100 Subject: [PATCH 04/32] Avoid memory leak in Android driver code Avoid leaking context when switching and move Go callbacks to C code in cases where they are only used in C code. --- internal/driver/mobile/app/android.c | 10 ++++++---- internal/driver/mobile/app/android.go | 16 ---------------- internal/driver/mobile/mobileinit/ctx_android.go | 11 +++++++++++ 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/internal/driver/mobile/app/android.c b/internal/driver/mobile/app/android.c index 1eaaed344c..efe35195ae 100644 --- a/internal/driver/mobile/app/android.c +++ b/internal/driver/mobile/app/android.c @@ -67,13 +67,15 @@ jint JNI_OnLoad(JavaVM* vm, void* reserved) { static int main_running = 0; // ensure we refresh context on resume in case something has changed... -void processOnResume(ANativeActivity *activity) { +void onResume(ANativeActivity *activity) { JNIEnv* env = activity->env; setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz)); - - onResume(activity); } +void onStart(ANativeActivity *activity) {} +void onPause(ANativeActivity *activity) {} +void onStop(ANativeActivity *activity) {} + // Entry point from our subclassed NativeActivity. // // By here, the Go runtime has been initialized (as we are running in @@ -127,7 +129,7 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_ // Note that onNativeWindowResized is not called on resize. Avoid it. // https://code.google.com/p/android/issues/detail?id=180645 activity->callbacks->onStart = onStart; - activity->callbacks->onResume = processOnResume; + activity->callbacks->onResume = onResume; activity->callbacks->onSaveInstanceState = onSaveInstanceState; activity->callbacks->onPause = onPause; activity->callbacks->onStop = onStop; diff --git a/internal/driver/mobile/app/android.go b/internal/driver/mobile/app/android.go index fdc09b8597..24eb000330 100644 --- a/internal/driver/mobile/app/android.go +++ b/internal/driver/mobile/app/android.go @@ -131,27 +131,11 @@ func callMain(mainPC uintptr) { go callfn.CallFn(mainPC) } -//export onStart -func onStart(activity *C.ANativeActivity) { -} - -//export onResume -func onResume(activity *C.ANativeActivity) { -} - //export onSaveInstanceState func onSaveInstanceState(activity *C.ANativeActivity, outSize *C.size_t) unsafe.Pointer { return nil } -//export onPause -func onPause(activity *C.ANativeActivity) { -} - -//export onStop -func onStop(activity *C.ANativeActivity) { -} - //export onBackPressed func onBackPressed() { k := key.Event{ diff --git a/internal/driver/mobile/mobileinit/ctx_android.go b/internal/driver/mobile/mobileinit/ctx_android.go index b58881a26d..562cc381b9 100644 --- a/internal/driver/mobile/mobileinit/ctx_android.go +++ b/internal/driver/mobile/mobileinit/ctx_android.go @@ -55,6 +55,11 @@ static char* checkException(uintptr_t jnienv) { static void unlockJNI(JavaVM *vm) { (*vm)->DetachCurrentThread(vm); } + +static void deletePrevCtx(JNIEnv* env,jobject ctx){ + if (ctx == NULL) { return; } + (*env)->DeleteGlobalRef(env, ctx); +} */ import "C" @@ -80,7 +85,13 @@ var currentCtx C.jobject // The android.context.Context object must be a global reference. func SetCurrentContext(vm unsafe.Pointer, ctx uintptr) { currentVM = (*C.JavaVM)(vm) + currentCtxPrev := currentCtx currentCtx = (C.jobject)(ctx) + RunOnJVM(func(vm, jniEnv, ctx uintptr) error { + env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) + C.deletePrevCtx(env, C.jobject(currentCtxPrev)) + return nil + }) } // RunOnJVM runs fn on a new goroutine locked to an OS thread with a JNIEnv. From 5762df2daac10017ea00de0c26061d5d93baea67 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 5 Sep 2023 18:30:45 +0100 Subject: [PATCH 05/32] Usage of deprecated commands would crash due to nil AppData --- cmd/fyne/commands/command.go | 12 ++++++------ cmd/fyne/internal/commands/build.go | 7 ++++++- cmd/fyne/internal/commands/bundle.go | 5 +++++ cmd/fyne/internal/commands/get.go | 2 +- cmd/fyne/internal/commands/install.go | 7 ++++++- cmd/fyne/internal/commands/package.go | 7 ++++++- cmd/fyne/internal/commands/release.go | 10 ++++++++-- 7 files changed, 38 insertions(+), 12 deletions(-) diff --git a/cmd/fyne/commands/command.go b/cmd/fyne/commands/command.go index fc1f3e2832..43a34197af 100644 --- a/cmd/fyne/commands/command.go +++ b/cmd/fyne/commands/command.go @@ -17,35 +17,35 @@ type Command interface { type Getter = commands.Getter // NewGetter returns a command that can handle the download and install of GUI apps built using Fyne. -// It depends on a Go and C compiler installed at this stage and takes a single, package, parameter to identify the app. +// It depends on a Go and C compiler installed. func NewGetter() *Getter { - return &Getter{} + return commands.NewGetter() } // NewBundler returns a command that can bundle resources into Go code. // // Deprecated: A better version will be exposed in the future. func NewBundler() Command { - return &commands.Bundler{} + return commands.NewBundler() } // NewInstaller returns an install command that can install locally built Fyne apps. // // Deprecated: A better version will be exposed in the future. func NewInstaller() Command { - return &commands.Installer{} + return commands.NewInstaller() } // NewPackager returns a packager command that can wrap executables into full GUI app packages. // // Deprecated: A better version will be exposed in the future. func NewPackager() Command { - return &commands.Packager{} + return commands.NewPackager() } // NewReleaser returns a command that can adapt app packages for distribution. // // Deprecated: A better version will be exposed in the future. func NewReleaser() Command { - return &commands.Releaser{} + return commands.NewReleaser() } diff --git a/cmd/fyne/internal/commands/build.go b/cmd/fyne/internal/commands/build.go index 1fe1eca901..83881cf6ad 100644 --- a/cmd/fyne/internal/commands/build.go +++ b/cmd/fyne/internal/commands/build.go @@ -33,9 +33,14 @@ type Builder struct { runner runner } +// NewBuilder returns a command that can handle the build of GUI apps built using Fyne. +func NewBuilder() *Builder { + return &Builder{appData: &appData{}} +} + // Build returns the cli command for building fyne applications func Build() *cli.Command { - b := &Builder{appData: &appData{}} + b := NewBuilder() return &cli.Command{ Name: "build", diff --git a/cmd/fyne/internal/commands/bundle.go b/cmd/fyne/internal/commands/bundle.go index e06399aa66..0c2feddabe 100644 --- a/cmd/fyne/internal/commands/bundle.go +++ b/cmd/fyne/internal/commands/bundle.go @@ -69,6 +69,11 @@ type Bundler struct { noheader bool } +// NewBundler returns a command that can handle the bundling assets into a GUI app binary. +func NewBundler() *Bundler { + return &Bundler{} +} + // AddFlags adds all the command line flags for passing to the Bundler. // // Deprecated: Access to the individual cli commands are being removed. diff --git a/cmd/fyne/internal/commands/get.go b/cmd/fyne/internal/commands/get.go index daaf52f40e..e7266adda8 100644 --- a/cmd/fyne/internal/commands/get.go +++ b/cmd/fyne/internal/commands/get.go @@ -50,7 +50,7 @@ type Getter struct { } // NewGetter returns a command that can handle the download and install of GUI apps built using Fyne. -// It depends on a Go and C compiler installed at this stage and takes a single, package, parameter to identify the app. +// It depends on a Go and C compiler installed at this stage. func NewGetter() *Getter { return &Getter{appData: &appData{}} } diff --git a/cmd/fyne/internal/commands/install.go b/cmd/fyne/internal/commands/install.go index 4dbe04d25c..d89e0c18a8 100644 --- a/cmd/fyne/internal/commands/install.go +++ b/cmd/fyne/internal/commands/install.go @@ -17,7 +17,7 @@ import ( // Install returns the cli command for installing fyne applications func Install() *cli.Command { - i := &Installer{appData: &appData{}} + i := NewInstaller() return &cli.Command{ Name: "install", @@ -73,6 +73,11 @@ type Installer struct { release bool } +// NewInstaller returns a command that can install a GUI apps built using Fyne from local source code. +func NewInstaller() *Installer { + return &Installer{appData: &appData{}} +} + // AddFlags adds the flags for interacting with the Installer. // // Deprecated: Access to the individual cli commands are being removed. diff --git a/cmd/fyne/internal/commands/package.go b/cmd/fyne/internal/commands/package.go index c7ce92113a..7bc5e209bd 100644 --- a/cmd/fyne/internal/commands/package.go +++ b/cmd/fyne/internal/commands/package.go @@ -31,7 +31,7 @@ const ( // Package returns the cli command for packaging fyne applications func Package() *cli.Command { - p := &Packager{appData: &appData{}} + p := NewPackager() return &cli.Command{ Name: "package", @@ -140,6 +140,11 @@ type Packager struct { linuxAndBSDMetadata *metadata.LinuxAndBSD } +// NewPackager returns a command that can handle the packaging a GUI apps built using Fyne from local source code. +func NewPackager() *Packager { + return &Packager{appData: &appData{}} +} + // AddFlags adds the flags for interacting with the package command. // // Deprecated: Access to the individual cli commands are being removed. diff --git a/cmd/fyne/internal/commands/release.go b/cmd/fyne/internal/commands/release.go index 58c35f9add..43fe05cf67 100644 --- a/cmd/fyne/internal/commands/release.go +++ b/cmd/fyne/internal/commands/release.go @@ -28,8 +28,7 @@ var macAppStoreCategories = []string{ // Release returns the cli command for bundling release builds of fyne applications func Release() *cli.Command { - r := &Releaser{} - r.appData = &appData{} + r := NewReleaser() return &cli.Command{ Name: "release", @@ -149,6 +148,13 @@ type Releaser struct { password string } +// NewReleaser returns a command that can handle the packaging a GUI apps for release from local Fyne source code. +func NewReleaser() *Releaser { + r := &Releaser{} + r.appData = &appData{} + return r +} + // AddFlags adds the flags for interacting with the release command. // // Deprecated: Access to the individual cli commands are being removed. From fafefedb2f63bfab47967e3210135186c231d3cf Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 5 Sep 2023 18:33:28 +0100 Subject: [PATCH 06/32] Add test --- cmd/fyne/commands/command_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 cmd/fyne/commands/command_test.go diff --git a/cmd/fyne/commands/command_test.go b/cmd/fyne/commands/command_test.go new file mode 100644 index 0000000000..f01b566ea7 --- /dev/null +++ b/cmd/fyne/commands/command_test.go @@ -0,0 +1,8 @@ +package commands + +import "testing" + +func TestNewGetter(t *testing.T) { + g := NewGetter() + g.SetAppID("io.fyne.text") // would crash if not set up internally correctly +} From 0285095bbe8e6ed2dc5393d912ae0df18832cc65 Mon Sep 17 00:00:00 2001 From: Ju-B Date: Fri, 8 Sep 2023 21:51:18 +0200 Subject: [PATCH 07/32] Update table.go for ease of dev usage/self documentation (retry) When using the widget, i always mix rows an columns in the lenght function. This change allows IDE/LSPs to display a usefull signature for the function. --- widget/table.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/widget/table.go b/widget/table.go index d0b5c141e7..a8f2e47ba9 100644 --- a/widget/table.go +++ b/widget/table.go @@ -40,7 +40,7 @@ type TableCellID struct { type Table struct { BaseWidget - Length func() (int, int) `json:"-"` + Length func() (rows int, cols int) `json:"-"` CreateCell func() fyne.CanvasObject `json:"-"` UpdateCell func(id TableCellID, template fyne.CanvasObject) `json:"-"` OnSelected func(id TableCellID) `json:"-"` @@ -104,7 +104,7 @@ type Table struct { // passed template CanvasObject. // // Since: 1.4 -func NewTable(length func() (int, int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table { +func NewTable(length func() (rows int, cols int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table { t := &Table{Length: length, CreateCell: create, UpdateCell: update} t.ExtendBaseWidget(t) return t @@ -117,7 +117,7 @@ func NewTable(length func() (int, int), create func() fyne.CanvasObject, update // The row and column headers will stick to the leading and top edges of the table and contain "1-10" and "A-Z" formatted labels. // // Since: 2.4 -func NewTableWithHeaders(length func() (int, int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table { +func NewTableWithHeaders(length func() (rows int, cols int), create func() fyne.CanvasObject, update func(TableCellID, fyne.CanvasObject)) *Table { t := NewTable(length, create, update) t.ShowHeaderRow = true t.ShowHeaderColumn = true From 99c60be7a4b5827dacaf0184ba13630fea4f7bb2 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 30 Aug 2023 15:58:55 +0100 Subject: [PATCH 08/32] Fix issue where fullscreen input would cover app in landscape keyboard mode --- cmd/fyne/internal/mobile/dex.go | 396 +++++++++--------- .../driver/mobile/app/GoNativeActivity.java | 2 +- 2 files changed, 199 insertions(+), 199 deletions(-) diff --git a/cmd/fyne/internal/mobile/dex.go b/cmd/fyne/internal/mobile/dex.go index c6921ed5e0..22f52a756f 100644 --- a/cmd/fyne/internal/mobile/dex.go +++ b/cmd/fyne/internal/mobile/dex.go @@ -6,45 +6,45 @@ package mobile -var dexStr = `ZGV4CjAzNQDTYRTF/qrASEUxTObFaqwTSgBKmxVbTMLALQAAcAAAAHhWNBIAAAAAAAAAAP` + - `AsAADbAAAAcAAAADUAAADcAwAARQAAALAEAAAXAAAA7AcAAHgAAACkCAAACAAAAGQMAABc` + - `IAAAZA0AAKQaAACmGgAAqRoAAK4aAACzGgAAthoAAL4aAADSGgAA6RoAAPkaAAAJGwAADx` + - `sAACYbAAApGwAALhsAADQbAAA5GwAAPxsAAEIbAABGGwAASxsAAE8bAABUGwAAWRsAAHcb` + - `AACYGwAAsxsAAM0bAADwGwAAFRwAADocAABbHAAAdBwAAIccAACjHAAAuBwAAM4cAADnHA` + - `AAAx0AABcdAABMHQAAbB0AAIUdAACxHQAAxh0AAO0dAAAEHgAAIR4AAFAeAABrHgAAlh4A` + - `AMgeAADjHgAACB8AACgfAABHHwAAVx8AAHEfAACIHwAApx8AALsfAADRHwAA5R8AAPkfAA` + - `AQIAAANyAAAFwgAACBIAAApiAAAM0gAADyIAAAFyEAADohAABQIQAAWyEAAHMhAAB8IQAA` + - `liEAAKEhAACkIQAAqCEAAK0hAAC0IQAAuiEAAL4hAADDIQAAyiEAANYhAADbIQAA3yEAAO` + - `IhAADmIQAA6yEAAPEhAAD2IQAACyIAAA8iAAAbIgAAJyIAADMiAAA/IgAASyIAAFciAABj` + - `IgAAbyIAAHwiAACJIgAAmSIAAKMiAAC+IgAA1iIAAOgiAAD+IgAAJSMAAEojAAB0IwAAli` + - `MAALcjAADTIwAA7CMAAPkjAAAMJAAAGiQAACQkAAAzJAAAQyQAAFMkAABjJAAAcyQAAHYk` + - `AAB+JAAAoSQAALUkAADDJAAA0yQAANgkAADpJAAA+iQAAAclAAAVJQAAJyUAADAlAAA+JQ` + - `AASSUAAFQlAABnJQAAdSUAAIIlAACXJQAAoCUAAKslAAC9JQAA2SUAAPMlAAAOJgAAJyYA` + - `ADAmAAA7JgAARSYAAFAmAABgJgAAfiYAAJAmAACYJgAApiYAAL8mAADKJgAA2CYAAOcmAA` + - `D3JgAABicAAAwnAAAUJwAAGicAACcnAAA7JwAAZCcAAG8nAAB5JwAAfycAAJEnAACgJwAA` + - `uCcAAMInAADSJwAA4icAAPEnAAD7JwAACSgAAA4oAAAdKAAAKigAADkoAABHKAAAWCgAAH` + - `MoAACBKAAAiigAAJMoAACiKAAArigAALwoAADKKAAA2CgAAOcoAADuKAAABikAABMpAAAb` + - `KQAAIykAAC0pAAAyKQAAOikAAF4pAABsKQAAeSkAAIspAACSKQAAmSkAAAwAAAAXAAAAGA` + +var dexStr = `ZGV4CjAzNQDjZX63XYczplWvLvfMLlPbq7VDP34Wu73ELQAAcAAAAHhWNBIAAAAAAAAAAP` + + `QsAADbAAAAcAAAADUAAADcAwAARQAAALAEAAAXAAAA7AcAAHgAAACkCAAACAAAAGQMAABg` + + `IAAAZA0AAKgaAACqGgAArRoAALIaAAC3GgAAuhoAAMIaAADWGgAA7RoAAP0aAAANGwAAEx` + + `sAACobAAAtGwAAMhsAADgbAAA9GwAAQxsAAEYbAABKGwAATxsAAFMbAABYGwAAXRsAAHsb` + + `AACcGwAAtxsAANEbAAD0GwAAGRwAAD4cAABfHAAAeBwAAIscAACnHAAAvBwAANIcAADrHA` + + `AABx0AABsdAABQHQAAcB0AAIkdAAC1HQAAyh0AAPEdAAAIHgAAJR4AAFQeAABvHgAAmh4A` + + `AMweAADnHgAADB8AACwfAABLHwAAWx8AAHUfAACMHwAAqx8AAL8fAADVHwAA6R8AAP0fAA` + + `AUIAAAOyAAAGAgAACFIAAAqiAAANEgAAD2IAAAGyEAAD4hAABUIQAAXyEAAHchAACAIQAA` + + `miEAAKUhAACoIQAArCEAALEhAAC4IQAAviEAAMIhAADHIQAAziEAANohAADfIQAA4yEAAO` + + `YhAADqIQAA7yEAAPUhAAD6IQAADyIAABMiAAAfIgAAKyIAADciAABDIgAATyIAAFsiAABn` + + `IgAAcyIAAIAiAACNIgAAnSIAAKciAADCIgAA2iIAAOwiAAACIwAAKSMAAE4jAAB4IwAAmi` + + `MAALsjAADXIwAA8CMAAP0jAAAQJAAAHiQAACgkAAA3JAAARyQAAFckAABnJAAAdyQAAHok` + + `AACCJAAApSQAALkkAADHJAAA1yQAANwkAADtJAAA/iQAAAslAAAZJQAAKyUAADQlAABCJQ` + + `AATSUAAFglAABrJQAAeSUAAIYlAACbJQAApCUAAK8lAADBJQAA3SUAAPclAAASJgAAKyYA` + + `ADQmAAA/JgAASSYAAFQmAABkJgAAgiYAAJQmAACcJgAAqiYAAMMmAADOJgAA3CYAAOsmAA` + + `D7JgAACicAABAnAAAYJwAAHicAACsnAAA/JwAAaCcAAHMnAAB9JwAAgycAAJUnAACkJwAA` + + `vCcAAMYnAADWJwAA5icAAPUnAAD/JwAADSgAABIoAAAhKAAALigAAD0oAABLKAAAXCgAAH` + + `coAACFKAAAjigAAJcoAACmKAAAsigAAMAoAADOKAAA3CgAAOsoAADyKAAACikAABcpAAAf` + + `KQAAJykAADEpAAA2KQAAPikAAGIpAABwKQAAfSkAAI8pAACWKQAAnSkAAAwAAAAXAAAAGA` + `AAABkAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAjAAAAJAAAACUA` + `AAAmAAAAJwAAACgAAAApAAAAKgAAACsAAAAsAAAALQAAAC4AAAAvAAAAMAAAADEAAAAyAA` + `AAMwAAADQAAAA1AAAANgAAADcAAAA4AAAAOQAAADoAAAA7AAAAPAAAAD0AAAA+AAAAPwAA` + `AEAAAABBAAAAQgAAAEMAAABEAAAARQAAAEYAAABHAAAATgAAAFkAAABeAAAADAAAAAAAAA` + - `AAAAAADQAAAAAAAABsGQAADgAAAAAAAAB0GQAADwAAAAAAAACAGQAAEAAAAAAAAACIGQAA` + - `EQAAAAIAAAAAAAAAEQAAAAQAAAAAAAAAEgAAAAQAAACUGQAAFgAAAAQAAACcGQAAFAAAAA` + - `QAAACkGQAAFgAAAAQAAACAGQAAFgAAAAQAAACsGQAAFQAAAAUAAAC0GQAAEQAAAAYAAAAA` + + `AAAAAADQAAAAAAAABwGQAADgAAAAAAAAB4GQAADwAAAAAAAACEGQAAEAAAAAAAAACMGQAA` + + `EQAAAAIAAAAAAAAAEQAAAAQAAAAAAAAAEgAAAAQAAACYGQAAFgAAAAQAAACgGQAAFAAAAA` + + `QAAACoGQAAFgAAAAQAAACEGQAAFgAAAAQAAACwGQAAFQAAAAUAAAC4GQAAEQAAAAYAAAAA` + `AAAAEQAAAAcAAAAAAAAAEQAAAAgAAAAAAAAAEQAAAAoAAAAAAAAAEQAAAA0AAAAAAAAAEQ` + - `AAAA4AAAAAAAAAEgAAABIAAACUGQAAEQAAABUAAAAAAAAAEgAAABUAAACUGQAAEQAAABcA` + - `AAAAAAAAEQAAABgAAAAAAAAAFAAAABoAAAC8GQAAFgAAABoAAADEGQAAEQAAACEAAAAAAA` + - `AAEwAAACIAAABsGQAAFAAAACUAAACkGQAAEQAAACcAAAAAAAAAFAAAACcAAACkGQAAEQAA` + - `ADEAAAAAAAAATgAAADIAAAAAAAAATwAAADIAAACUGQAAUAAAADIAAABsGQAAUQAAADIAAA` + - `DMGQAAUgAAADIAAADYGQAAUwAAADIAAADkGQAAVAAAADIAAADsGQAAUwAAADIAAAD0GQAA` + - `UwAAADIAAAD8GQAAUwAAADIAAAAEGgAAUwAAADIAAAAMGgAAUwAAADIAAABkGQAAUwAAAD` + - `IAAABcGQAAVgAAADIAAAAUGgAAVwAAADIAAAAsGgAAUwAAADIAAAA0GgAAUwAAADIAAABM` + - `GQAAUwAAADIAAAA8GgAAVQAAADIAAABEGgAAUwAAADIAAABUGQAAUwAAADIAAACkGQAAVw` + - `AAADIAAACAGQAAUwAAADIAAABQGgAAUwAAADIAAABYGgAAUwAAADIAAAC8GQAAVAAAADIA` + - `AABgGgAAVwAAADIAAABoGgAAWAAAADIAAABwGgAAWQAAADMAAAAAAAAAWwAAADMAAAB4Gg` + - `AAWwAAADMAAACAGgAAXAAAADMAAACIGgAAWgAAADMAAAA8GgAAWgAAADMAAACUGgAAWgAA` + - `ADMAAAC8GQAAXQAAADMAAACcGgAAFAAAADQAAACkGQAABQAMALEAAAAHAAAA0wAAAAkAAA` + + `AAAA4AAAAAAAAAEgAAABIAAACYGQAAEQAAABUAAAAAAAAAEgAAABUAAACYGQAAEQAAABcA` + + `AAAAAAAAEQAAABgAAAAAAAAAFAAAABoAAADAGQAAFgAAABoAAADIGQAAEQAAACEAAAAAAA` + + `AAEwAAACIAAABwGQAAFAAAACUAAACoGQAAEQAAACcAAAAAAAAAFAAAACcAAACoGQAAEQAA` + + `ADEAAAAAAAAATgAAADIAAAAAAAAATwAAADIAAACYGQAAUAAAADIAAABwGQAAUQAAADIAAA` + + `DQGQAAUgAAADIAAADcGQAAUwAAADIAAADoGQAAVAAAADIAAADwGQAAUwAAADIAAAD4GQAA` + + `UwAAADIAAAAAGgAAUwAAADIAAAAIGgAAUwAAADIAAAAQGgAAUwAAADIAAABoGQAAUwAAAD` + + `IAAABgGQAAVgAAADIAAAAYGgAAVwAAADIAAAAwGgAAUwAAADIAAAA4GgAAUwAAADIAAABQ` + + `GQAAUwAAADIAAABAGgAAVQAAADIAAABIGgAAUwAAADIAAABYGQAAUwAAADIAAACoGQAAVw` + + `AAADIAAACEGQAAUwAAADIAAABUGgAAUwAAADIAAABcGgAAUwAAADIAAADAGQAAVAAAADIA` + + `AABkGgAAVwAAADIAAABsGgAAWAAAADIAAAB0GgAAWQAAADMAAAAAAAAAWwAAADMAAAB8Gg` + + `AAWwAAADMAAACEGgAAXAAAADMAAACMGgAAWgAAADMAAABAGgAAWgAAADMAAACYGgAAWgAA` + + `ADMAAADAGQAAXQAAADMAAACgGgAAFAAAADQAAACoGQAABQAMALEAAAAHAAAA0wAAAAkAAA` + `CqAAAACQAAANIAAAALAAAASwAAACoAKwDQAAAAKwAxAM8AAAArAAAA1wAAACwAMQDPAAAA` + `LQAxAM8AAAAuAC8A0AAAAC8AMQDPAAAAMAAxAM8AAAAxAAAABgAAADEAAAAHAAAAMQAAAA` + `gAAAAxAAAACQAAADEAAABIAAAAMQAAAEoAAAAxAAAATAAAADEAMQChAAAAMQAzAKUAAAAx` + @@ -66,168 +66,168 @@ var dexStr = `ZGV4CjAzNQDTYRTF/qrASEUxTObFaqwTSgBKmxVbTMLALQAAcAAAAHhWNBIAAAAAAA `EAAAAxAAIAlAAAADEAHACWAAAAMQAdAJwAAAAxABYAngAAADEAIACjAAAAMQAjAKcAAAAx` + `ACAAqAAAADEANACpAAAAMQAgAKwAAAAxACQAswAAADEAIAC0AAAAMQAnALUAAAAxACkAtg` + `AAADEAMwC9AAAAMQA7AL4AAAAxACAAxwAAADEANADIAAAAMQA1AMkAAAAxACEAygAAADEA` + - `JgDNAAAAMQAgANUAAAAxACcA1gAAACoAAAAAAAAAJQAAAEwZAAALAAAA1BgAAMIrAAAAAA` + - `AAKwAAAAAAAAAlAAAAVBkAAAsAAADkGAAA0ysAAAAAAAAsAAAAAAAAACUAAABUGQAACwAA` + - `APwYAADnKwAAAAAAAC0AAAAAAAAAJQAAAFwZAAALAAAADBkAAPgrAAAAAAAALgAAAAAAAA` + - `AlAAAAZBkAAAsAAAAcGQAACSwAAAAAAAAvAAAAAAAAACUAAABUGQAACwAAACwZAAAiLAAA` + - `AAAAADAAAAAAAAAAJQAAAFQZAAALAAAAPBkAADMsAAAAAAAAMQAAAAEAAAABAAAAAAAAAA` + - `sAAAAAAAAARCwAALErAAACAAAAbisAAHUrAAACAAAAfisAAHUrAAABAAAAhSsAAAIAAACO` + - `KwAAdSsAAAIAAACVKwAAdSsAAAIAAACcKwAAdSsAAAIAAACjKwAAdSsAAAIAAACqKwAAdS` + - `sAAAIAAgABAAAAnCkAAAYAAABbAQUAcBA3AAAADgAGAAQAAgAAAKIpAAAOAAAAEmAzBAsA` + - `VCAFAFQABgAaAQEAcSBPABAAEgAPAAMAAwABAAAArCkAAAgAAABbAQYAWQIHAHAQNwAAAA` + - `4ABgABAAMAAACzKQAAqAAAABUCAEASYRIEFQAIAFJTBwArA5QAAAABIRoCCgAaA9QAcSAV` + - `ADIAVFIGAHEQTQACAAwCbiAsABIAVFEGAHEQTQABAAwBbiAtAAEAVFAGAHEQTQAAAAwAIg` + - `EqAHAgPABRAG4gLwAQAFRQBgASEXEgUQAQAFRQBgBxEE0AAAAMABoBBABuIDEAEABUUAYA` + - `cRBNAAAADABUUQYAcRBNAAEADAFuECoAAQAMAXIQFAABAAoBbiAwABAAVFAGAHEgUQBAAF` + - `RQBgBxEE0AAAAMAG4gMgBAAFRQBgBxEE0AAAAMAG4QKQAAAFRQBgBxEE0AAAAMAG4QKwAA` + - `AFRQBgAaAaYAbiBjABAADAAfABkAVFEGAHEQTQABAAwBbjAmABAEDgABISiDFAACAAgAKQ` + - `B//xQAkAAIACkAc/8AAAABBAAAAAAAhwAAAAsAAACJAAAAjgAAAAIAAgABAAAA1ykAAAYA` + - `AABbAQgAcBA3AAAADgADAAEAAgAAAN4pAAAMAAAAVCAIAHEQTQAAAAwAEwEIAG4gMgAQAA` + - `4AAgACAAEAAADlKQAABgAAAFsBCQBwEDcAAAAOAAsACgABAAAA7CkAAAYAAABUEAkAbhB2` + - `AAAADgACAAIAAQAAAPwpAAAGAAAAWwEKAHAQNwAAAA4ABAACAAIAAAADKgAAPwAAABIRch` + - `AUAAMACgA1EDkAVCAKAFQACwBxIFEAEABUIAoAVAALAHEQTQAAAAwAGgEEAG4gMQAQAFQg` + - `CgBUAAsAcRBNAAAADABUIQoAVBELAHEQTQABAAwBbhAqAAEADAFyEBQAAQAKAW4gMAAQAF` + - `QgCgBUAAsAEgFxIFEAEAAOAAAABwAFAAEAAAARKgAAGgAAAFQgCgBUAAsAcRBQAAAACgA4` + - `AAMADgA9Bf//EgA1UPz/VCEKAFQRCwBxEFMAAQDYAAABKPUHAAUAAwAAACIqAAAfAAAAVC` + - `AKAFQACwBxEFAAAAAKADgAAwAOAD0G//9UIAoAVAALAJABBAZyMDUAQwEMAXIQNgABAAwB` + - `cSBPABAAKOwAAAIAAgABAAAALyoAAAYAAABbAQsAcBA3AAAADgAFAAEAAwAAADYqAABvAA` + - `AAEuNUQAsAIgEaAHEAUgAAAAwCcCAnACEAcSBOABAAVEALAHEQTQAAAAwAEwEIAG4gMgAQ` + - `AFRACwBxEE0AAAAMABUBCABuIC0AEAAiABsAcDAzADADVEELAHEQTQABAAwBbiAuAAEAVE` + - `ELAFRCCwBxEE0AAgAMAm4wVQAhAFRACwBxEE0AAAAMABoBBABuIDEAEABUQAsAcRBNAAAA` + - `DABUQQsAcRBNAAEADAFuECoAAQAMAXIQFAABAAoBbiAwABAAVEALAHEQTQAAAAwAIgEuAH` + - `AgRABBAG4gKAAQAA4AAAACAAIAAQAAAEkqAAAGAAAAWwEMAHAQNwAAAA4AAgABAAEAAABQ` + - `KgAABgAAAFQQDABxEFQAAAAOAAIAAQABAAAAVyoAAAkAAABwEAAAAQASAFwQFQBpARQADg` + - `AAAAIAAQAAAAAAXyoAAAMAAABUEBYAEQAAAAIAAgAAAAAAZSoAAAMAAABbARYAEQEAAAIA` + - `AgACAAAAbCoAAAQAAABwIGkAEAAOAAIAAQAAAAAAcyoAAAMAAABVEBUADwAAAAIAAgAAAA` + - `AAeSoAAAMAAABcARUADwEAAAEAAAAAAAAAgCoAAAMAAABiABQAEQAAAAEAAQABAAAAhSoA` + - `AAQAAABwEGgAAAAOAAEAAQABAAAAiyoAAAQAAABvEAEAAAAOAAcAAwADAAEAkSoAABkAAA` + - `AS8HEQGAAEAAwBbjAXAFEGCgE5AQMADwABECj+DQEaAgoAGgOCAHEwFgAyASj1DQEo8wAA` + - `AQAAAAcAAQABAhEXIw4AAAEAAAABAAAAoioAAAYAAABiABQAbhBXAAAADgAEAAEAAwABAK` + - `kqAAAzAAAAbhBgAAMADABuEF8AAwAMAW4QCAABAAwBEwKAAG4wDQAQAgwAVAEAADkBCgAa` + - `AAoAGgGvAHEgFQAQAA4AVAAAABoBbwBuIBMAEAAMAHEQOwAAACj0DQAaAQoAGgKuAHEwFg` + - `AhACjrAAAAAAAAKQABAAEBIyoCAAEAAgAAALoqAAAJAAAAIgAvAHAgSAAQAG4gbwABAA4A` + - `AAACAAEAAgAAAMMqAAAGAAAAYgAUAG4gWAAQAA4AAwACAAMAAADLKgAABgAAAGIAFABuMF` + - `kAEAIOAAIAAQACAAAA1CoAAAYAAABiABQAbiBaABAADgAEAAEAAwAAANsqAAAkAAAAGgCm` + - `AG4gYwADAAwAHwAZABQBAgACAW4gXAATAAwBbhAbAAEADAFuEB4AAQAMARICbjAlABACIg` + - `AsAHAgQAAwAG4gbwADAA4ABgACAAMAAADlKgAAVwAAABITIgAEABoBcQBwIAQAEAAaAXYA` + - `biA5AFEACgE4ARwAYAEEABMCFQA0IRYAIgAEABoBcgBwIAQAEABuIAYAMAAaAUkAcSAHAB` + - `AADABuMHUABAMOABoB2gBuIDgAFQAKATgBHgBgAQQAEwITADQhGAAaAQMAbiAMABAAGgF0` + - `ABoCXwBuIDoAJQAMAm4wCwAQAhoBcwBuIAUAEAAo024gDABQABoBcwBuIAUAEAAoygAABg` + - `ADAAMAAAD5KgAAPgAAACIABAAaAXAAcCAEABAAGgHaAG4gOAAUAAoBOAEtAGABBAATAhMA` + - `NCEnABoBAwBuIAwAEAAaAXQAGgJfAG4gOgAkAAwCbjALABACGgF1AG4wCgAQBRoBcwBuIA` + - `UAEAAaAU0AcSAHABAADAASIW4wdQADAQ4AbiAMAEAAKOgDAAIAAwAAAAsrAAAJAAAAIgAr` + - `AHAwPgAQAm4gbwABAA4AAAACAAEAAgAAABQrAAAJAAAAIgAwAHAgSgAQAG4gbwABAA4AAA` + - `ACAAEAAQAAABsrAAAJAAAAbhBeAAEADABuEDQAAAAMABEAAAAFAAQAAgAAACArAAAcAAAA` + - `EhAyAgYAEiAyAgMADgAS8DIDCAAaAAAAcCBbAAEAKPduEAkABAAMAG4QEgAAAAwAcCBbAA` + - `EAKOsBAAEAAQAAADIrAAAEAAAAcBBWAAAADgACAAIAAgAAADkrAAAHAAAAbyACABAAbiB3` + - `ABAADgAAAAQAAgACAAAAQisAACgAAABwEGoAAgBvIAMAMgBwEHEAAgBuEGEAAgAMAG4QDg` + - `AAAAwAbiB3AAIAFAACAAIBbiBcAAIADABuEBsAAAAMACIBLQBwIEIAIQBuIBkAEAAOAAcA` + - `AQAFAAEATysAAGAAAABuEGUABgAMAG4QIAAAAAwAbhAcAAAADAA5AAMADgBuECQAAAAKAW` + - `4QIQAAAAoCbhAiAAAACgNuECMAAAAKAHBQZwAWMijsDQAiAAkAcBAPAAAAbhBlAAYADAFu` + - `ECAAAQAMAW4gHwABABQBAgACAW4gXAAWAAwBbhAbAAEADAFSAgMAbhAaAAEACgNuEBAAAA` + - `AKBLFDUgQDALFDUgQCAG4QHQABAAoBbhARAAAACgWxUVIAAgCRAAEAcFBnACZDKK8AAAAA` + - `IgABAAEBJCMEAAIAAgAAAGQrAAAPAAAAUjABAN0AADATASAAMxAHABIQcCBwAAIADgASAC` + - `j7AABkDQAAAAAAAAAAAAAAAAAAcA0AAAAAAAABAAAAAAAAAD4AAAB8DQAAhA0AAAAAAAAA` + - `AAAAAAAAAJANAAAAAAAAAAAAAAAAAACcDQAAAAAAAAAAAAAAAAAAqA0AAAAAAAAAAAAAAA` + - `AAALQNAAAAAAAAAAAAAAAAAAABAAAAHAAAAAEAAAAmAAAAAQAAABQAAAABAAAADwAAAAIA` + - `AAAAAAAAAwAAAAAAAAAAAAAAAgAAACcAJwADAAAAJwAnACkAAAABAAAAAAAAAAIAAAAEAC` + - `IAAQAAACcAAAACAAAAJwA0AAIAAAACAAAAAQAAADEAAAACAAAAMQAaAAQAAAAAAAAAAAAA` + - `AAMAAAAAAAAABAAAAAEAAAADAAAAAgAAAAQAAAABAAAABwAAAAEAAAAJAAAAAQAAAAwAAA` + - `ABAAAADgAAAAkAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAVABYAAQAAABYAAAABAAAA` + - `IgAAAAQAAAAiAAAAAAAAAAEAAAArAAAAAQAAAC8AAAACAAAAMQAAAAIAAAAxACcAAQAAAD` + - `MAAAACAAAADQAAAAIAAAAVAAAAAwAAAB0AAAATAAAAAQAAACUAAAACAAAAMQAzAAAAAQoA` + - `AygpVgADKi8qAAEwAAY8aW5pdD4AEkRFRkFVTFRfSU5QVVRfVFlQRQAVREVGQVVMVF9LRV` + - `lCT0FSRF9DT0RFAA5GSUxFX09QRU5fQ09ERQAORklMRV9TQVZFX0NPREUABEZ5bmUAFUdv` + - `TmF0aXZlQWN0aXZpdHkuamF2YQABSQADSUlJAARJSUlJAANJTEwABElMTEwAAUwAAkxJAA` + - `NMSUkAAkxMAANMTEkAA0xMTAAcTGFuZHJvaWQvYXBwL05hdGl2ZUFjdGl2aXR5OwAfTGFu` + - `ZHJvaWQvY29udGVudC9Db21wb25lbnROYW1lOwAZTGFuZHJvaWQvY29udGVudC9Db250ZX` + - `h0OwAYTGFuZHJvaWQvY29udGVudC9JbnRlbnQ7ACFMYW5kcm9pZC9jb250ZW50L3BtL0Fj` + - `dGl2aXR5SW5mbzsAI0xhbmRyb2lkL2NvbnRlbnQvcG0vUGFja2FnZU1hbmFnZXI7ACNMYW` + - `5kcm9pZC9jb250ZW50L3Jlcy9Db25maWd1cmF0aW9uOwAfTGFuZHJvaWQvY29udGVudC9y` + - `ZXMvUmVzb3VyY2VzOwAXTGFuZHJvaWQvZ3JhcGhpY3MvUmVjdDsAEUxhbmRyb2lkL25ldC` + - `9Vcmk7ABpMYW5kcm9pZC9vcy9CdWlsZCRWRVJTSU9OOwATTGFuZHJvaWQvb3MvQnVuZGxl` + - `OwAUTGFuZHJvaWQvb3MvSUJpbmRlcjsAF0xhbmRyb2lkL3RleHQvRWRpdGFibGU7ABpMYW` + - `5kcm9pZC90ZXh0L1RleHRXYXRjaGVyOwASTGFuZHJvaWQvdXRpbC9Mb2c7ADNMYW5kcm9p` + - `ZC92aWV3L0tleUNoYXJhY3Rlck1hcCRVbmF2YWlsYWJsZUV4Y2VwdGlvbjsAHkxhbmRyb2` + - `lkL3ZpZXcvS2V5Q2hhcmFjdGVyTWFwOwAXTGFuZHJvaWQvdmlldy9LZXlFdmVudDsAKkxh` + - `bmRyb2lkL3ZpZXcvVmlldyRPbkxheW91dENoYW5nZUxpc3RlbmVyOwATTGFuZHJvaWQvdm` + - `lldy9WaWV3OwAlTGFuZHJvaWQvdmlldy9WaWV3R3JvdXAkTGF5b3V0UGFyYW1zOwAVTGFu` + - `ZHJvaWQvdmlldy9XaW5kb3c7ABtMYW5kcm9pZC92aWV3L1dpbmRvd0luc2V0czsALUxhbm` + - `Ryb2lkL3ZpZXcvaW5wdXRtZXRob2QvSW5wdXRNZXRob2RNYW5hZ2VyOwAZTGFuZHJvaWQv` + - `d2lkZ2V0L0VkaXRUZXh0OwApTGFuZHJvaWQvd2lkZ2V0L0ZyYW1lTGF5b3V0JExheW91dF` + - `BhcmFtczsAMExhbmRyb2lkL3dpZGdldC9UZXh0VmlldyRPbkVkaXRvckFjdGlvbkxpc3Rl` + - `bmVyOwAZTGFuZHJvaWQvd2lkZ2V0L1RleHRWaWV3OwAjTGRhbHZpay9hbm5vdGF0aW9uL0` + - `VuY2xvc2luZ01ldGhvZDsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAdTGRh` + - `bHZpay9hbm5vdGF0aW9uL1NpZ25hdHVyZTsADkxqYXZhL2lvL0ZpbGU7ABhMamF2YS9sYW` + - `5nL0NoYXJTZXF1ZW5jZTsAFUxqYXZhL2xhbmcvRXhjZXB0aW9uOwAdTGphdmEvbGFuZy9O` + - `b1N1Y2hNZXRob2RFcnJvcjsAEkxqYXZhL2xhbmcvT2JqZWN0OwAUTGphdmEvbGFuZy9SdW` + - `5uYWJsZTsAEkxqYXZhL2xhbmcvU3RyaW5nOwASTGphdmEvbGFuZy9TeXN0ZW07ABVMamF2` + - `YS9sYW5nL1Rocm93YWJsZTsAJUxvcmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2aXR5JD` + - `EkMTsAI0xvcmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2aXR5JDE7ACNMb3JnL2dvbGFu` + - `Zy9hcHAvR29OYXRpdmVBY3Rpdml0eSQyOwAjTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQW` + - `N0aXZpdHkkMzsAJUxvcmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2aXR5JDQkMTsAI0xv` + - `cmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2aXR5JDQ7ACNMb3JnL2dvbGFuZy9hcHAvR2` + - `9OYXRpdmVBY3Rpdml0eSQ1OwAhTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHk7` + - `ABROVU1CRVJfS0VZQk9BUkRfQ09ERQAJT3BlbiBGaWxlABZQQVNTV09SRF9LRVlCT0FSRF` + - `9DT0RFAAdTREtfSU5UABhTSU5HTEVMSU5FX0tFWUJPQVJEX0NPREUACVNhdmUgRmlsZQAB` + - `VgACVkkAA1ZJSQAFVklJSUkABFZJSUwAAlZMAANWTEkABVZMSUlJAApWTElJSUlJSUlJAA` + - `NWTEwAAlZaAAFaAAJaTAADWkxJAARaTElMAANaTFoAE1tMamF2YS9sYW5nL1N0cmluZzsA` + - `Alx8AAphY2Nlc3MkMDAwAAphY2Nlc3MkMDAyAAphY2Nlc3MkMTAwAAphY2Nlc3MkMjAwAA` + - `phY2Nlc3MkMjAyAAphY2Nlc3MkMzAwAAphY2Nlc3MkNDAwAAphY2Nlc3MkNTAxAAthY2Nl` + - `c3NGbGFncwALYWRkQ2F0ZWdvcnkADmFkZENvbnRlbnRWaWV3AAhhZGRGbGFncwAZYWRkT2` + - `5MYXlvdXRDaGFuZ2VMaXN0ZW5lcgAWYWRkVGV4dENoYW5nZWRMaXN0ZW5lcgAQYWZ0ZXJU` + - `ZXh0Q2hhbmdlZAAUYW5kcm9pZC5hcHAubGliX25hbWUAJWFuZHJvaWQuaW50ZW50LmFjdG` + - `lvbi5DUkVBVEVfRE9DVU1FTlQAI2FuZHJvaWQuaW50ZW50LmFjdGlvbi5PUEVOX0RPQ1VN` + - `RU5UAChhbmRyb2lkLmludGVudC5hY3Rpb24uT1BFTl9ET0NVTUVOVF9UUkVFACBhbmRyb2` + - `lkLmludGVudC5jYXRlZ29yeS5PUEVOQUJMRQAfYW5kcm9pZC5pbnRlbnQuZXh0cmEuTUlN` + - `RV9UWVBFUwAaYW5kcm9pZC5pbnRlbnQuZXh0cmEuVElUTEUAF2FwcGxpY2F0aW9uL3gtZG` + - `lyZWN0b3J5AAtiYWNrUHJlc3NlZAARYmVmb3JlVGV4dENoYW5nZWQADGJyaW5nVG9Gcm9u` + - `dAAIY29udGFpbnMADWNyZWF0ZUNob29zZXIADmRvSGlkZUtleWJvYXJkAA5kb1Nob3dGaW` + - `xlT3BlbgAOZG9TaG93RmlsZVNhdmUADmRvU2hvd0tleWJvYXJkAAFlAAZlcXVhbHMAIWV4` + - `Y2VwdGlvbiByZWFkaW5nIEtleUNoYXJhY3Rlck1hcAASZmlsZVBpY2tlclJldHVybmVkAA` + - `xmaW5kVmlld0J5SWQADmZpbmlzaEFjdGl2aXR5AANnZXQAD2dldEFic29sdXRlUGF0aAAP` + - `Z2V0QWN0aXZpdHlJbmZvAAtnZXRDYWNoZURpcgAMZ2V0Q29tcG9uZW50ABBnZXRDb25maW` + - `d1cmF0aW9uAAdnZXREYXRhAAxnZXREZWNvclZpZXcACWdldEhlaWdodAAJZ2V0SW50ZW50` + - `ABFnZXRQYWNrYWdlTWFuYWdlcgAMZ2V0UmVzb3VyY2VzAAtnZXRSb290VmlldwATZ2V0Um` + - `9vdFdpbmRvd0luc2V0cwAHZ2V0UnVuZQAJZ2V0U3RyaW5nABBnZXRTeXN0ZW1TZXJ2aWNl` + - `ABpnZXRTeXN0ZW1XaW5kb3dJbnNldEJvdHRvbQAYZ2V0U3lzdGVtV2luZG93SW5zZXRMZW` + - `Z0ABlnZXRTeXN0ZW1XaW5kb3dJbnNldFJpZ2h0ABdnZXRTeXN0ZW1XaW5kb3dJbnNldFRv` + - `cAAHZ2V0VGV4dAAJZ2V0VG1wZGlyAAhnZXRXaWR0aAAJZ2V0V2luZG93AA5nZXRXaW5kb3` + - `dUb2tlbgAcZ2V0V2luZG93VmlzaWJsZURpc3BsYXlGcmFtZQAQZ29OYXRpdmVBY3Rpdml0` + - `eQAGaGVpZ2h0AAxoaWRlS2V5Ym9hcmQAF2hpZGVTb2Z0SW5wdXRGcm9tV2luZG93AAlpZ2` + - `5vcmVLZXkADGlucHV0X21ldGhvZAANaW5zZXRzQ2hhbmdlZAAOa2V5Ym9hcmREZWxldGUA` + - `DWtleWJvYXJkVHlwZWQABGxlZnQABmxlbmd0aAAEbG9hZAALbG9hZExpYnJhcnkAEmxvYW` + - `RMaWJyYXJ5IGZhaWxlZAAnbG9hZExpYnJhcnk6IG5vIG1hbmlmZXN0IG1ldGFkYXRhIGZv` + - `dW5kAAltVGV4dEVkaXQACG1ldGFEYXRhAARuYW1lABBvbkFjdGl2aXR5UmVzdWx0AA1vbk` + - `JhY2tQcmVzc2VkABZvbkNvbmZpZ3VyYXRpb25DaGFuZ2VkAAhvbkNyZWF0ZQAOb25FZGl0` + - `b3JBY3Rpb24ADm9uTGF5b3V0Q2hhbmdlAA1vblRleHRDaGFuZ2VkAAhwdXRFeHRyYQAMcm` + - `VxdWVzdEZvY3VzAANydW4ADXJ1bk9uVWlUaHJlYWQAC3NldERhcmtNb2RlAA1zZXRJbWVP` + - `cHRpb25zAAxzZXRJbnB1dFR5cGUAD3NldExheW91dFBhcmFtcwAZc2V0T25FZGl0b3JBY3` + - `Rpb25MaXN0ZW5lcgAMc2V0U2VsZWN0aW9uAAdzZXRUZXh0AAdzZXRUeXBlAA1zZXRWaXNp` + - `YmlsaXR5AApzZXR1cEVudHJ5AAxzaG93RmlsZU9wZW4ADHNob3dGaWxlU2F2ZQAMc2hvd0` + - `tleWJvYXJkAA1zaG93U29mdElucHV0AAVzcGxpdAAWc3RhcnRBY3Rpdml0eUZvclJlc3Vs` + - `dAALc3ViU2VxdWVuY2UABnRoaXMkMAAGdGhpcyQxAAh0b1N0cmluZwADdG9wAAZ1aU1vZG` + - `UAInVua25vd24ga2V5Ym9hcmQgdHlwZSwgdXNlIGRlZmF1bHQADHVwZGF0ZUxheW91dAAL` + - `dXBkYXRlVGhlbWUAEHZhbCRrZXlib2FyZFR5cGUABXZhbHVlAAV3aWR0aAABfABuAQAHDg` + - `BxAwAAAAcOPJcAVQIAAAcOAFgAB0oPLQIPaHmWlwIL4Gm0ARcPW5aWl6WWAlksIzwvAnNZ` + - `AJEBAQAHDgCUAQAHDrQA6wEBAAcOAO4BCQAAAAAAAAAAAAcOWgCEAgEABw4AnwIBAAcdaX` + - `jSARsPiQCRAgQAAAAABw6tAnodLT11AIcCBAAAAAAHDqoaLQD0AQEABw4A9wEABx3htLVb` + - `lra0ARcQAiTgAMQCAQAHDgDHAgAHDloANQAHDjg/LQAeAQAHDgAeAgAABw4AHgIAAAcOAB` + - `4BAAcOAB4CAAAHDgAeAAcOAB4BAAcOAB4BAAcOAL8BAwAAAAcdhzQCeywgHoMAiQEABw5a` + - `ANYBAAcOS6NMS38Cex2HSx4A9AEABw4CNoYAmgEBAAcOWgCuAQIAAAcOWgBRAQAHDloAjQ` + - `EABw6HtIiMAJ4BAQAHHXjheESWAncd4Vq0ajwAsgECAAAHDnjhWrdaWqUCex0AVQEABw4C` + - `MYYAxAIABw6MADoABw4ArwIDAAAABw4CDGgCeR08bEsAwAIABw48AM4CAQAHDjw8AOUBAQ` + - `AHDjw8PLW0jAA/AAcOwwIOLAJ2HYeFTB5atbT/0ADTAgEABw6WPBsAAh4B2AEaPwIfAmgE` + - `ALIBHgIeAdgBGloCIAHYARwBFwICHgHYARpXAh4B2AEabgIeAdgBGkkCHgHYARpxAh4B2A` + - `EaXQdEAAAIBAAEAQQCBAIEAwQBAAEBAQWQIDyAgATAGz0B3BsAAgEBBpAgAZAgPoCABIgc` + - `PwGoHAABAQEIkCBAgIAEiB9BAaQfAAEBAQmQIEKAgATMH0MB6B8AAQEDCpAgRICABIQgRQ` + - `GgIAEBsCEBAfQhAAEBAQuQIEiAgATEIkkB4CIAAQEBDJAgSoCABNAkSwHsJAgCFgwNGgEa` + - `ARoBGgEaARoBGgEKFQIBAkyBgASIJQGIIKwlAYggxCUBiCDcJQGIIPQlAYggjCYBiCCkJg` + - `GIILwmAYgg1CYCggIABYICAAcI7CYECMAnAYICAAGCAgABggIAAQLcJwaCAgABAuAoAQiE` + - `KQEIoCkBCLwpVwDYKQEAsCoBAPArAQD8LAMBoC0HAMQtBwToLQEBsC4BAcguAQHoLggAyC` + - `8BBKQxAAARAAAAAAAAAAEAAAAAAAAAAQAAANsAAABwAAAAAgAAADUAAADcAwAAAwAAAEUA` + - `AACwBAAABAAAABcAAADsBwAABQAAAHgAAACkCAAABgAAAAgAAABkDAAAAxAAAAgAAABkDQ` + - `AAASAAACwAAADADQAABiAAAAcAAADUGAAAARAAACYAAABMGQAAAiAAANsAAACkGgAAAyAA` + - `ACwAAACcKQAABCAAAAkAAABuKwAABSAAAAEAAACxKwAAACAAAAgAAADCKwAAABAAAAEAAA` + - `DwLAAA` + + `JgDNAAAAMQAgANUAAAAxACcA1gAAACoAAAAAAAAAJQAAAFAZAAALAAAA2BgAAMYrAAAAAA` + + `AAKwAAAAAAAAAlAAAAWBkAAAsAAADoGAAA1ysAAAAAAAAsAAAAAAAAACUAAABYGQAACwAA` + + `AAAZAADrKwAAAAAAAC0AAAAAAAAAJQAAAGAZAAALAAAAEBkAAPwrAAAAAAAALgAAAAAAAA` + + `AlAAAAaBkAAAsAAAAgGQAADSwAAAAAAAAvAAAAAAAAACUAAABYGQAACwAAADAZAAAmLAAA` + + `AAAAADAAAAAAAAAAJQAAAFgZAAALAAAAQBkAADcsAAAAAAAAMQAAAAEAAAABAAAAAAAAAA` + + `sAAAAAAAAASCwAALUrAAACAAAAcisAAHkrAAACAAAAgisAAHkrAAABAAAAiSsAAAIAAACS` + + `KwAAeSsAAAIAAACZKwAAeSsAAAIAAACgKwAAeSsAAAIAAACnKwAAeSsAAAIAAACuKwAAeS` + + `sAAAIAAgABAAAAoCkAAAYAAABbAQUAcBA3AAAADgAGAAQAAgAAAKYpAAAOAAAAEmAzBAsA` + + `VCAFAFQABgAaAQEAcSBPABAAEgAPAAMAAwABAAAAsCkAAAgAAABbAQYAWQIHAHAQNwAAAA` + + `4ABgABAAMAAAC3KQAAqgAAABUCAEASYRIEFQAIAFJTBwArA5YAAAABIRoCCgAaA9QAcSAV` + + `ADIAVFIGAHEQTQACAAwCFQMAArYxbiAsABIAVFEGAHEQTQABAAwBbiAtAAEAVFAGAHEQTQ` + + `AAAAwAIgEqAHAgPABRAG4gLwAQAFRQBgASEXEgUQAQAFRQBgBxEE0AAAAMABoBBABuIDEA` + + `EABUUAYAcRBNAAAADABUUQYAcRBNAAEADAFuECoAAQAMAXIQFAABAAoBbiAwABAAVFAGAH` + + `EgUQBAAFRQBgBxEE0AAAAMAG4gMgBAAFRQBgBxEE0AAAAMAG4QKQAAAFRQBgBxEE0AAAAM` + + `AG4QKwAAAFRQBgAaAaYAbiBjABAADAAfABkAVFEGAHEQTQABAAwBbjAmABAEDgABISiAFA` + + `ACAAgAKQB8/xQAkAAIACkAcP8AAQQAAAAAAIoAAAALAAAAjAAAAJEAAAACAAIAAQAAANsp` + + `AAAGAAAAWwEIAHAQNwAAAA4AAwABAAIAAADiKQAADAAAAFQgCABxEE0AAAAMABMBCABuID` + + `IAEAAOAAIAAgABAAAA6SkAAAYAAABbAQkAcBA3AAAADgALAAoAAQAAAPApAAAGAAAAVBAJ` + + `AG4QdgAAAA4AAgACAAEAAAAAKgAABgAAAFsBCgBwEDcAAAAOAAQAAgACAAAAByoAAD8AAA` + + `ASEXIQFAADAAoANRA5AFQgCgBUAAsAcSBRABAAVCAKAFQACwBxEE0AAAAMABoBBABuIDEA` + + `EABUIAoAVAALAHEQTQAAAAwAVCEKAFQRCwBxEE0AAQAMAW4QKgABAAwBchAUAAEACgFuID` + + `AAEABUIAoAVAALABIBcSBRABAADgAAAAcABQABAAAAFSoAABoAAABUIAoAVAALAHEQUAAA` + + `AAoAOAADAA4APQX//xIANVD8/1QhCgBUEQsAcRBTAAEA2AAAASj1BwAFAAMAAAAmKgAAHw` + + `AAAFQgCgBUAAsAcRBQAAAACgA4AAMADgA9Bv//VCAKAFQACwCQAQQGcjA1AEMBDAFyEDYA` + + `AQAMAXEgTwAQACjsAAACAAIAAQAAADMqAAAGAAAAWwELAHAQNwAAAA4ABQABAAMAAAA6Kg` + + `AAbwAAABLjVEALACIBGgBxAFIAAAAMAnAgJwAhAHEgTgAQAFRACwBxEE0AAAAMABMBCABu` + + `IDIAEABUQAsAcRBNAAAADAAVAQgAbiAtABAAIgAbAHAwMwAwA1RBCwBxEE0AAQAMAW4gLg` + + `ABAFRBCwBUQgsAcRBNAAIADAJuMFUAIQBUQAsAcRBNAAAADAAaAQQAbiAxABAAVEALAHEQ` + + `TQAAAAwAVEELAHEQTQABAAwBbhAqAAEADAFyEBQAAQAKAW4gMAAQAFRACwBxEE0AAAAMAC` + + `IBLgBwIEQAQQBuICgAEAAOAAAAAgACAAEAAABNKgAABgAAAFsBDABwEDcAAAAOAAIAAQAB` + + `AAAAVCoAAAYAAABUEAwAcRBUAAAADgACAAEAAQAAAFsqAAAJAAAAcBAAAAEAEgBcEBUAaQ` + + `EUAA4AAAACAAEAAAAAAGMqAAADAAAAVBAWABEAAAACAAIAAAAAAGkqAAADAAAAWwEWABEB` + + `AAACAAIAAgAAAHAqAAAEAAAAcCBpABAADgACAAEAAAAAAHcqAAADAAAAVRAVAA8AAAACAA` + + `IAAAAAAH0qAAADAAAAXAEVAA8BAAABAAAAAAAAAIQqAAADAAAAYgAUABEAAAABAAEAAQAA` + + `AIkqAAAEAAAAcBBoAAAADgABAAEAAQAAAI8qAAAEAAAAbxABAAAADgAHAAMAAwABAJUqAA` + + `AZAAAAEvBxEBgABAAMAW4wFwBRBgoBOQEDAA8AARAo/g0BGgIKABoDggBxMBYAMgEo9Q0B` + + `KPMAAAEAAAAHAAEAAQIRFyMOAAABAAAAAQAAAKYqAAAGAAAAYgAUAG4QVwAAAA4ABAABAA` + + `MAAQCtKgAAMwAAAG4QYAADAAwAbhBfAAMADAFuEAgAAQAMARMCgABuMA0AEAIMAFQBAAA5` + + `AQoAGgAKABoBrwBxIBUAEAAOAFQAAAAaAW8AbiATABAADABxEDsAAAAo9A0AGgEKABoCrg` + + `BxMBYAIQAo6wAAAAAAACkAAQABASMqAgABAAIAAAC+KgAACQAAACIALwBwIEgAEABuIG8A` + + `AQAOAAAAAgABAAIAAADHKgAABgAAAGIAFABuIFgAEAAOAAMAAgADAAAAzyoAAAYAAABiAB` + + `QAbjBZABACDgACAAEAAgAAANgqAAAGAAAAYgAUAG4gWgAQAA4ABAABAAMAAADfKgAAJAAA` + + `ABoApgBuIGMAAwAMAB8AGQAUAQIAAgFuIFwAEwAMAW4QGwABAAwBbhAeAAEADAESAm4wJQ` + + `AQAiIALABwIEAAMABuIG8AAwAOAAYAAgADAAAA6SoAAFcAAAASEyIABAAaAXEAcCAEABAA` + + `GgF2AG4gOQBRAAoBOAEcAGABBAATAhUANCEWACIABAAaAXIAcCAEABAAbiAGADAAGgFJAH` + + `EgBwAQAAwAbjB1AAQDDgAaAdoAbiA4ABUACgE4AR4AYAEEABMCEwA0IRgAGgEDAG4gDAAQ` + + `ABoBdAAaAl8AbiA6ACUADAJuMAsAEAIaAXMAbiAFABAAKNNuIAwAUAAaAXMAbiAFABAAKM` + + `oAAAYAAwADAAAA/SoAAD4AAAAiAAQAGgFwAHAgBAAQABoB2gBuIDgAFAAKATgBLQBgAQQA` + + `EwITADQhJwAaAQMAbiAMABAAGgF0ABoCXwBuIDoAJAAMAm4wCwAQAhoBdQBuMAoAEAUaAX` + + `MAbiAFABAAGgFNAHEgBwAQAAwAEiFuMHUAAwEOAG4gDABAACjoAwACAAMAAAAPKwAACQAA` + + `ACIAKwBwMD4AEAJuIG8AAQAOAAAAAgABAAIAAAAYKwAACQAAACIAMABwIEoAEABuIG8AAQ` + + `AOAAAAAgABAAEAAAAfKwAACQAAAG4QXgABAAwAbhA0AAAADAARAAAABQAEAAIAAAAkKwAA` + + `HAAAABIQMgIGABIgMgIDAA4AEvAyAwgAGgAAAHAgWwABACj3bhAJAAQADABuEBIAAAAMAH` + + `AgWwABACjrAQABAAEAAAA2KwAABAAAAHAQVgAAAA4AAgACAAIAAAA9KwAABwAAAG8gAgAQ` + + `AG4gdwAQAA4AAAAEAAIAAgAAAEYrAAAoAAAAcBBqAAIAbyADADIAcBBxAAIAbhBhAAIADA` + + `BuEA4AAAAMAG4gdwACABQAAgACAW4gXAACAAwAbhAbAAAADAAiAS0AcCBCACEAbiAZABAA` + + `DgAHAAEABQABAFMrAABgAAAAbhBlAAYADABuECAAAAAMAG4QHAAAAAwAOQADAA4AbhAkAA` + + `AACgFuECEAAAAKAm4QIgAAAAoDbhAjAAAACgBwUGcAFjIo7A0AIgAJAHAQDwAAAG4QZQAG` + + `AAwBbhAgAAEADAFuIB8AAQAUAQIAAgFuIFwAFgAMAW4QGwABAAwBUgIDAG4QGgABAAoDbh` + + `AQAAAACgSxQ1IEAwCxQ1IEAgBuEB0AAQAKAW4QEQAAAAoFsVFSAAIAkQABAHBQZwAmQyiv` + + `AAAAACIAAQABASQjBAACAAIAAABoKwAADwAAAFIwAQDdAAAwEwEgADMQBwASEHAgcAACAA` + + `4AEgAo+wAAZA0AAAAAAAAAAAAAAAAAAHANAAAAAAAAAQAAAAAAAAA+AAAAfA0AAIQNAAAA` + + `AAAAAAAAAAAAAACQDQAAAAAAAAAAAAAAAAAAnA0AAAAAAAAAAAAAAAAAAKgNAAAAAAAAAA` + + `AAAAAAAAC0DQAAAAAAAAAAAAAAAAAAAQAAABwAAAABAAAAJgAAAAEAAAAUAAAAAQAAAA8A` + + `AAACAAAAAAAAAAMAAAAAAAAAAAAAAAIAAAAnACcAAwAAACcAJwApAAAAAQAAAAAAAAACAA` + + `AABAAiAAEAAAAnAAAAAgAAACcANAACAAAAAgAAAAEAAAAxAAAAAgAAADEAGgAEAAAAAAAA` + + `AAAAAAADAAAAAAAAAAQAAAABAAAAAwAAAAIAAAAEAAAAAQAAAAcAAAABAAAACQAAAAEAAA` + + `AMAAAAAQAAAA4AAAAJAAAAFQAAAAAAAAAAAAAAAAAAAAAAAAACAAAAFQAWAAEAAAAWAAAA` + + `AQAAACIAAAAEAAAAIgAAAAAAAAABAAAAKwAAAAEAAAAvAAAAAgAAADEAAAACAAAAMQAnAA` + + `EAAAAzAAAAAgAAAA0AAAACAAAAFQAAAAMAAAAdAAAAEwAAAAEAAAAlAAAAAgAAADEAMwAA` + + `AAEKAAMoKVYAAyovKgABMAAGPGluaXQ+ABJERUZBVUxUX0lOUFVUX1RZUEUAFURFRkFVTF` + + `RfS0VZQk9BUkRfQ09ERQAORklMRV9PUEVOX0NPREUADkZJTEVfU0FWRV9DT0RFAARGeW5l` + + `ABVHb05hdGl2ZUFjdGl2aXR5LmphdmEAAUkAA0lJSQAESUlJSQADSUxMAARJTExMAAFMAA` + + `JMSQADTElJAAJMTAADTExJAANMTEwAHExhbmRyb2lkL2FwcC9OYXRpdmVBY3Rpdml0eTsA` + + `H0xhbmRyb2lkL2NvbnRlbnQvQ29tcG9uZW50TmFtZTsAGUxhbmRyb2lkL2NvbnRlbnQvQ2` + + `9udGV4dDsAGExhbmRyb2lkL2NvbnRlbnQvSW50ZW50OwAhTGFuZHJvaWQvY29udGVudC9w` + + `bS9BY3Rpdml0eUluZm87ACNMYW5kcm9pZC9jb250ZW50L3BtL1BhY2thZ2VNYW5hZ2VyOw` + + `AjTGFuZHJvaWQvY29udGVudC9yZXMvQ29uZmlndXJhdGlvbjsAH0xhbmRyb2lkL2NvbnRl` + + `bnQvcmVzL1Jlc291cmNlczsAF0xhbmRyb2lkL2dyYXBoaWNzL1JlY3Q7ABFMYW5kcm9pZC` + + `9uZXQvVXJpOwAaTGFuZHJvaWQvb3MvQnVpbGQkVkVSU0lPTjsAE0xhbmRyb2lkL29zL0J1` + + `bmRsZTsAFExhbmRyb2lkL29zL0lCaW5kZXI7ABdMYW5kcm9pZC90ZXh0L0VkaXRhYmxlOw` + + `AaTGFuZHJvaWQvdGV4dC9UZXh0V2F0Y2hlcjsAEkxhbmRyb2lkL3V0aWwvTG9nOwAzTGFu` + + `ZHJvaWQvdmlldy9LZXlDaGFyYWN0ZXJNYXAkVW5hdmFpbGFibGVFeGNlcHRpb247AB5MYW` + + `5kcm9pZC92aWV3L0tleUNoYXJhY3Rlck1hcDsAF0xhbmRyb2lkL3ZpZXcvS2V5RXZlbnQ7` + + `ACpMYW5kcm9pZC92aWV3L1ZpZXckT25MYXlvdXRDaGFuZ2VMaXN0ZW5lcjsAE0xhbmRyb2` + + `lkL3ZpZXcvVmlldzsAJUxhbmRyb2lkL3ZpZXcvVmlld0dyb3VwJExheW91dFBhcmFtczsA` + + `FUxhbmRyb2lkL3ZpZXcvV2luZG93OwAbTGFuZHJvaWQvdmlldy9XaW5kb3dJbnNldHM7AC` + + `1MYW5kcm9pZC92aWV3L2lucHV0bWV0aG9kL0lucHV0TWV0aG9kTWFuYWdlcjsAGUxhbmRy` + + `b2lkL3dpZGdldC9FZGl0VGV4dDsAKUxhbmRyb2lkL3dpZGdldC9GcmFtZUxheW91dCRMYX` + + `lvdXRQYXJhbXM7ADBMYW5kcm9pZC93aWRnZXQvVGV4dFZpZXckT25FZGl0b3JBY3Rpb25M` + + `aXN0ZW5lcjsAGUxhbmRyb2lkL3dpZGdldC9UZXh0VmlldzsAI0xkYWx2aWsvYW5ub3RhdG` + + `lvbi9FbmNsb3NpbmdNZXRob2Q7AB5MZGFsdmlrL2Fubm90YXRpb24vSW5uZXJDbGFzczsA` + + `HUxkYWx2aWsvYW5ub3RhdGlvbi9TaWduYXR1cmU7AA5MamF2YS9pby9GaWxlOwAYTGphdm` + + `EvbGFuZy9DaGFyU2VxdWVuY2U7ABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsAHUxqYXZhL2xh` + + `bmcvTm9TdWNoTWV0aG9kRXJyb3I7ABJMamF2YS9sYW5nL09iamVjdDsAFExqYXZhL2xhbm` + + `cvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsAEkxqYXZhL2xhbmcvU3lzdGVtOwAV` + + `TGphdmEvbGFuZy9UaHJvd2FibGU7ACVMb3JnL2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdm` + + `l0eSQxJDE7ACNMb3JnL2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdml0eSQxOwAjTG9yZy9n` + + `b2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHkkMjsAI0xvcmcvZ29sYW5nL2FwcC9Hb05hdG` + + `l2ZUFjdGl2aXR5JDM7ACVMb3JnL2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdml0eSQ0JDE7` + + `ACNMb3JnL2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdml0eSQ0OwAjTG9yZy9nb2xhbmcvYX` + + `BwL0dvTmF0aXZlQWN0aXZpdHkkNTsAIUxvcmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2` + + `aXR5OwAUTlVNQkVSX0tFWUJPQVJEX0NPREUACU9wZW4gRmlsZQAWUEFTU1dPUkRfS0VZQk` + + `9BUkRfQ09ERQAHU0RLX0lOVAAYU0lOR0xFTElORV9LRVlCT0FSRF9DT0RFAAlTYXZlIEZp` + + `bGUAAVYAAlZJAANWSUkABVZJSUlJAARWSUlMAAJWTAADVkxJAAVWTElJSQAKVkxJSUlJSU` + + `lJSQADVkxMAAJWWgABWgACWkwAA1pMSQAEWkxJTAADWkxaABNbTGphdmEvbGFuZy9TdHJp` + + `bmc7AAJcfAAKYWNjZXNzJDAwMAAKYWNjZXNzJDAwMgAKYWNjZXNzJDEwMAAKYWNjZXNzJD` + + `IwMAAKYWNjZXNzJDIwMgAKYWNjZXNzJDMwMAAKYWNjZXNzJDQwMAAKYWNjZXNzJDUwMQAL` + + `YWNjZXNzRmxhZ3MAC2FkZENhdGVnb3J5AA5hZGRDb250ZW50VmlldwAIYWRkRmxhZ3MAGW` + + `FkZE9uTGF5b3V0Q2hhbmdlTGlzdGVuZXIAFmFkZFRleHRDaGFuZ2VkTGlzdGVuZXIAEGFm` + + `dGVyVGV4dENoYW5nZWQAFGFuZHJvaWQuYXBwLmxpYl9uYW1lACVhbmRyb2lkLmludGVudC` + + `5hY3Rpb24uQ1JFQVRFX0RPQ1VNRU5UACNhbmRyb2lkLmludGVudC5hY3Rpb24uT1BFTl9E` + + `T0NVTUVOVAAoYW5kcm9pZC5pbnRlbnQuYWN0aW9uLk9QRU5fRE9DVU1FTlRfVFJFRQAgYW` + + `5kcm9pZC5pbnRlbnQuY2F0ZWdvcnkuT1BFTkFCTEUAH2FuZHJvaWQuaW50ZW50LmV4dHJh` + + `Lk1JTUVfVFlQRVMAGmFuZHJvaWQuaW50ZW50LmV4dHJhLlRJVExFABdhcHBsaWNhdGlvbi` + + `94LWRpcmVjdG9yeQALYmFja1ByZXNzZWQAEWJlZm9yZVRleHRDaGFuZ2VkAAxicmluZ1Rv` + + `RnJvbnQACGNvbnRhaW5zAA1jcmVhdGVDaG9vc2VyAA5kb0hpZGVLZXlib2FyZAAOZG9TaG` + + `93RmlsZU9wZW4ADmRvU2hvd0ZpbGVTYXZlAA5kb1Nob3dLZXlib2FyZAABZQAGZXF1YWxz` + + `ACFleGNlcHRpb24gcmVhZGluZyBLZXlDaGFyYWN0ZXJNYXAAEmZpbGVQaWNrZXJSZXR1cm` + + `5lZAAMZmluZFZpZXdCeUlkAA5maW5pc2hBY3Rpdml0eQADZ2V0AA9nZXRBYnNvbHV0ZVBh` + + `dGgAD2dldEFjdGl2aXR5SW5mbwALZ2V0Q2FjaGVEaXIADGdldENvbXBvbmVudAAQZ2V0Q2` + + `9uZmlndXJhdGlvbgAHZ2V0RGF0YQAMZ2V0RGVjb3JWaWV3AAlnZXRIZWlnaHQACWdldElu` + + `dGVudAARZ2V0UGFja2FnZU1hbmFnZXIADGdldFJlc291cmNlcwALZ2V0Um9vdFZpZXcAE2` + + `dldFJvb3RXaW5kb3dJbnNldHMAB2dldFJ1bmUACWdldFN0cmluZwAQZ2V0U3lzdGVtU2Vy` + + `dmljZQAaZ2V0U3lzdGVtV2luZG93SW5zZXRCb3R0b20AGGdldFN5c3RlbVdpbmRvd0luc2` + + `V0TGVmdAAZZ2V0U3lzdGVtV2luZG93SW5zZXRSaWdodAAXZ2V0U3lzdGVtV2luZG93SW5z` + + `ZXRUb3AAB2dldFRleHQACWdldFRtcGRpcgAIZ2V0V2lkdGgACWdldFdpbmRvdwAOZ2V0V2` + + `luZG93VG9rZW4AHGdldFdpbmRvd1Zpc2libGVEaXNwbGF5RnJhbWUAEGdvTmF0aXZlQWN0` + + `aXZpdHkABmhlaWdodAAMaGlkZUtleWJvYXJkABdoaWRlU29mdElucHV0RnJvbVdpbmRvdw` + + `AJaWdub3JlS2V5AAxpbnB1dF9tZXRob2QADWluc2V0c0NoYW5nZWQADmtleWJvYXJkRGVs` + + `ZXRlAA1rZXlib2FyZFR5cGVkAARsZWZ0AAZsZW5ndGgABGxvYWQAC2xvYWRMaWJyYXJ5AB` + + `Jsb2FkTGlicmFyeSBmYWlsZWQAJ2xvYWRMaWJyYXJ5OiBubyBtYW5pZmVzdCBtZXRhZGF0` + + `YSBmb3VuZAAJbVRleHRFZGl0AAhtZXRhRGF0YQAEbmFtZQAQb25BY3Rpdml0eVJlc3VsdA` + + `ANb25CYWNrUHJlc3NlZAAWb25Db25maWd1cmF0aW9uQ2hhbmdlZAAIb25DcmVhdGUADm9u` + + `RWRpdG9yQWN0aW9uAA5vbkxheW91dENoYW5nZQANb25UZXh0Q2hhbmdlZAAIcHV0RXh0cm` + + `EADHJlcXVlc3RGb2N1cwADcnVuAA1ydW5PblVpVGhyZWFkAAtzZXREYXJrTW9kZQANc2V0` + + `SW1lT3B0aW9ucwAMc2V0SW5wdXRUeXBlAA9zZXRMYXlvdXRQYXJhbXMAGXNldE9uRWRpdG` + + `9yQWN0aW9uTGlzdGVuZXIADHNldFNlbGVjdGlvbgAHc2V0VGV4dAAHc2V0VHlwZQANc2V0` + + `VmlzaWJpbGl0eQAKc2V0dXBFbnRyeQAMc2hvd0ZpbGVPcGVuAAxzaG93RmlsZVNhdmUADH` + + `Nob3dLZXlib2FyZAANc2hvd1NvZnRJbnB1dAAFc3BsaXQAFnN0YXJ0QWN0aXZpdHlGb3JS` + + `ZXN1bHQAC3N1YlNlcXVlbmNlAAZ0aGlzJDAABnRoaXMkMQAIdG9TdHJpbmcAA3RvcAAGdW` + + `lNb2RlACJ1bmtub3duIGtleWJvYXJkIHR5cGUsIHVzZSBkZWZhdWx0AAx1cGRhdGVMYXlv` + + `dXQAC3VwZGF0ZVRoZW1lABB2YWwka2V5Ym9hcmRUeXBlAAV2YWx1ZQAFd2lkdGgAAXwAbg` + + `EABw4AcQMAAAAHDjyXAFUCAAAHDgBYAAdKDy0CD2h5w5cCC+BptAEXD1uWlpellgJZLCM8` + + `LwJzWQCRAQEABw4AlAEABw60AOsBAQAHDgDuAQkAAAAAAAAAAAAHDloAhAIBAAcOAJ8CAQ` + + `AHHWl40gEbD4kAkQIEAAAAAAcOrQJ6HS09dQCHAgQAAAAABw6qGi0A9AEBAAcOAPcBAAcd` + + `4bS1W5a2tAEXEAIk4ADEAgEABw4AxwIABw5aADUABw44Py0AHgEABw4AHgIAAAcOAB4CAA` + + `AHDgAeAQAHDgAeAgAABw4AHgAHDgAeAQAHDgAeAQAHDgC/AQMAAAAHHYc0AnssIB6DAIkB` + + `AAcOWgDWAQAHDkujTEt/Ansdh0seAPQBAAcOAjaGAJoBAQAHDloArgECAAAHDloAUQEABw` + + `5aAI0BAAcOh7SIjACeAQEABx144XhElgJ3HeFatGo8ALIBAgAABw544Vq3WlqlAnsdAFUB` + + `AAcOAjGGAMQCAAcOjAA6AAcOAK8CAwAAAAcOAgxoAnkdPGxLAMACAAcOPADOAgEABw48PA` + + `DlAQEABw48PDy1tIwAPwAHDsMCDiwCdh2HhUweWrW0/9AA0wIBAAcOljwbAAIeAdgBGj8C` + + `HwJoBACyAR4CHgHYARpaAiAB2AEcARcCAh4B2AEaVwIeAdgBGm4CHgHYARpJAh4B2AEacQ` + + `IeAdgBGl0HRAAACAQABAEEAgQCBAMEAQABAQEFkCA8gIAEwBs9AdwbAAIBAQaQIAGQID6A` + + `gASIHD8BqBwAAQEBCJAgQICABIwfQQGoHwABAQEJkCBCgIAE0B9DAewfAAEBAwqQIESAgA` + + `SIIEUBpCABAbQhAQH4IQABAQELkCBIgIAEyCJJAeQiAAEBAQyQIEqAgATUJEsB8CQIAhYM` + + `DRoBGgEaARoBGgEaARoBChUCAQJMgYAEjCUBiCCwJQGIIMglAYgg4CUBiCD4JQGIIJAmAY` + + `ggqCYBiCDAJgGIINgmAoICAAWCAgAHCPAmBAjEJwGCAgABggIAAYICAAEC4CcGggIAAQLk` + + `KAEIiCkBCKQpAQjAKVcA3CkBALQqAQD0KwEAgC0DAaQtBwDILQcE7C0BAbQuAQHMLgEB7C` + + `4IAMwvAQSoMQAAEQAAAAAAAAABAAAAAAAAAAEAAADbAAAAcAAAAAIAAAA1AAAA3AMAAAMA` + + `AABFAAAAsAQAAAQAAAAXAAAA7AcAAAUAAAB4AAAApAgAAAYAAAAIAAAAZAwAAAMQAAAIAA` + + `AAZA0AAAEgAAAsAAAAwA0AAAYgAAAHAAAA2BgAAAEQAAAmAAAAUBkAAAIgAADbAAAAqBoA` + + `AAMgAAAsAAAAoCkAAAQgAAAJAAAAcisAAAUgAAABAAAAtSsAAAAgAAAIAAAAxisAAAAQAA` + + `ABAAAA9CwAAA==` + `` diff --git a/internal/driver/mobile/app/GoNativeActivity.java b/internal/driver/mobile/app/GoNativeActivity.java index 866f3246bd..5521b957ae 100644 --- a/internal/driver/mobile/app/GoNativeActivity.java +++ b/internal/driver/mobile/app/GoNativeActivity.java @@ -104,7 +104,7 @@ public void run() { default: Log.e("Fyne", "unknown keyboard type, use default"); } - mTextEdit.setImeOptions(imeOptions); + mTextEdit.setImeOptions(imeOptions|EditorInfo.IME_FLAG_NO_FULLSCREEN); mTextEdit.setInputType(inputType); mTextEdit.setOnEditorActionListener(new OnEditorActionListener() { From ec07817af87f98fcc340bee0d8ce4ff50b4d98cd Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 9 Sep 2023 11:32:21 +0100 Subject: [PATCH 09/32] Update the version number --- cmd/fyne_demo/FyneApp.toml | 2 +- cmd/hello/FyneApp.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/fyne_demo/FyneApp.toml b/cmd/fyne_demo/FyneApp.toml index 6300dc49aa..eb3cd500c0 100644 --- a/cmd/fyne_demo/FyneApp.toml +++ b/cmd/fyne_demo/FyneApp.toml @@ -2,7 +2,7 @@ Icon = "../../theme/icons/fyne.png" Name = "Fyne Demo" ID = "io.fyne.demo" - Version = "2.3.0" + Version = "2.4.0" Build = 11 [Development] diff --git a/cmd/hello/FyneApp.toml b/cmd/hello/FyneApp.toml index d59c3ed642..12bf58730b 100644 --- a/cmd/hello/FyneApp.toml +++ b/cmd/hello/FyneApp.toml @@ -2,5 +2,5 @@ Icon = "../../theme/icons/fyne.png" Name = "Fyne Hello" ID = "io.fyne.hello" - Version = "2.3.0" + Version = "2.4.0" Build = 2 From f61e72355c9ea047dc7f3411e4083819fe98ffe4 Mon Sep 17 00:00:00 2001 From: pbrown12303 Date: Thu, 7 Sep 2023 15:53:07 -0400 Subject: [PATCH 10/32] Modified docTabsRenderer.updateIndicator For the docTabs.current < 0 case, modified this function to call docTabsRenderer.moveIndicator (pos 0,0, size 0,0) to give consistent rendering when first drawn and after all tabs have been removed. --- container/doctabs.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/container/doctabs.go b/container/doctabs.go index 4fc8a482ce..877f62f521 100644 --- a/container/doctabs.go +++ b/container/doctabs.go @@ -388,7 +388,7 @@ func (r *docTabsRenderer) scrollToSelected() { func (r *docTabsRenderer) updateIndicator(animate bool) { if r.docTabs.current < 0 { r.indicator.FillColor = color.Transparent - r.indicator.Refresh() + r.moveIndicator(fyne.NewPos(0, 0), fyne.NewSize(0, 0), animate) return } From cd3f086722057e8909fc4e312af28ba3b4a1e266 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 11 Sep 2023 11:35:11 +0100 Subject: [PATCH 11/32] Fix issue where group-within-group would not render themed SVG Fixes #3900 --- internal/svg/svg.go | 2 ++ internal/svg/testdata/info_GroupRects.svg | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/svg/svg.go b/internal/svg/svg.go index 6ba8b64c2d..131f5faac0 100644 --- a/internal/svg/svg.go +++ b/internal/svg/svg.go @@ -212,6 +212,7 @@ type objGroup struct { Ellipses []*ellipseObj `xml:"ellipse"` Rects []*rectObj `xml:"rect"` Polygons []*polygonObj `xml:"polygon"` + Groups []*objGroup `xml:"g"` } func replacePathsFill(paths []*pathObj, hexColor string, opacity string) { @@ -266,6 +267,7 @@ func replaceGroupObjectFill(groups []*objGroup, hexColor string, opacity string) replacePathsFill(grp.Paths, hexColor, opacity) replaceRectsFill(grp.Rects, hexColor, opacity) replacePolygonsFill(grp.Polygons, hexColor, opacity) + replaceGroupObjectFill(grp.Groups, hexColor, opacity) } } diff --git a/internal/svg/testdata/info_GroupRects.svg b/internal/svg/testdata/info_GroupRects.svg index 15a290a814..8b037ae3c9 100644 --- a/internal/svg/testdata/info_GroupRects.svg +++ b/internal/svg/testdata/info_GroupRects.svg @@ -5,7 +5,9 @@ - + + + From 46038c123ce11208e194c374d824c1d3768cb7dd Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 11 Sep 2023 12:17:48 +0100 Subject: [PATCH 12/32] Subtle change in test image, but it now works as tested --- .../svg/testdata/colorized/group_rects.png | Bin 1164 -> 1149 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/internal/svg/testdata/colorized/group_rects.png b/internal/svg/testdata/colorized/group_rects.png index f1137e1df4ec7a1f3a24ac4014ccb19023f6574b..a2abd02fcc219beb91a6b289e40b61a8cf2a5361 100644 GIT binary patch delta 1128 zcmV-u1eg1a3H=C=B!5InL_t(|oa|Z6ZX87r{w9gz5GO2!UIcIfzcVGLjez_-l2 zU3GCK1?J}F&}cNK0bB!cvZ}BG>;SmM%)eAwR9XQdGCTdY?0Ob~2V&oA05$>aCP{)I z2#m7E+|J24nSTT@p7EWZ04y?d>`eh8GKc*=5w7tUfI9&0l@&@v$K~9-AqQUL4*)lr z`QL#Rkez?uLB0pzwj9D*-h_yZ{Ht=Fv`2gh;ESS7$yH!sVF7U*zvD2Q4uCJ2xl@O@ z1`$0E;9UUEs#nX*{7v54Tn`e*@w9{dDu5fl$Qv-TF@M7w5+iK9MnsqL)@CXouJW21 z?#OT58Hlk1;zB+&M;ta6n-F`H84iHH`u zoGL-5*IRmmOM!Lue9kYYO3<=3H>UzRCwG;Rqk@*Lxj7Zk!DCWt*pWi&@JTiNSkFr- zkR-{fG@bewcO-YCz$N82yTWZBQVO(MEt3iA-pq_E6C4(#kual%S6Z!BYHn5>_)$7$ zeSbVB-Gfo#s`5aF%U#z*rvlQeTv5Zb(mfa|i0B0=A#E%(bIOGxudPBvrY87Qb1BSq38WBNcm$tk1G)@l5X$MNuydLle|4TEj+}3t<@kX5J5h{0CyLHoj)&l>6)> z*F8p2v=)ZpgsKrtwt6uP!-r86dHEA!Hh&*F$hVpK`@FSfE;tq?jeW%$GY9MwB3d#9 z_yDcNV^(WXZ|3g+ZWVo_Y;*UxGdmD>y2a!*j|uT~CD(yP3l)8DU)ZMhftwaTKpS7A)2 z0K5rc+S3)&GJvHzJ=Z}m`OZkkY|2qf7ojV`@@`g;Dg!V1jpRv9>Dx4=doZD{S+}ff u*2g9>bLBQq9S;3X{gDC(jsF4w0RR7=BTRmgS?B`*0000|n2#g7kB!5#$L_t(|oa|XoZ(BtW|K+(MC27N-#zM42szlQYwF=UPLjm;& z0uI1|FOXM93rPn+8^ApP zcL3b7);8DI*Z&w*_S7iMM06OyX#g(*c(Nq>?*P6B&|v1@s_Ib503vd`y8>V-f*V=@ z*8qIQ%zI^-OB+B$Cjp!V@I+bOA$SPj0yFXa*3GJM3?YaE-eFE&d&|cFyawRQLEoqiT9gB8)N~WT$ISd-6o2J?q-nZmjMDFPw)@O4XeZD#Zi5}2$Z;wd`q4Gx0@z$bgPNkYZ$C zkR*wg-O11d_}XfGIIdcyFt5g$3^-)KJvBZOSEo|YvbAnO1MaACsEiyJv}~$> ziID3SMC59M4^#)u72F?H>0uUu$b47bJxi|yWkaWEEx~rsi`}w?NmGi2-p2_V64Y%_}|7w*7j&Kvw zEy(NdSH+&Z3;bAysp=EcK>)7+I2q}Q=_-J&Dm~XxANdYR$80H3OAkX^F3T&Ubh@MD zH}X(Xm%dG1x(9RWne~D9%z8VNnEfa5uPo{(3KMU@p8)^>|NjTLVaByZO>qDK002ov JPDHLkV1fYrC7b{N From f0861e31ce5460b4a54cd2f9306e33b37554bcc3 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 12 Sep 2023 21:38:24 +0100 Subject: [PATCH 13/32] Add missing test colour --- test/theme.go | 1 + 1 file changed, 1 insertion(+) diff --git a/test/theme.go b/test/theme.go index 138207e984..d7ac6b81c4 100644 --- a/test/theme.go +++ b/test/theme.go @@ -31,6 +31,7 @@ func Theme() fyne.Theme { theme.ColorNameForeground: color.NRGBA{R: 0xff, G: 0xff, B: 0xff, A: 0xff}, theme.ColorNameHover: color.NRGBA{R: 0x88, G: 0xff, B: 0xff, A: 0x22}, theme.ColorNameHeaderBackground: color.NRGBA{R: 0x22, G: 0x22, B: 0x22, A: 0xff}, + theme.ColorNameHyperlink: color.NRGBA{R: 0xff, G: 0xcc, B: 0x80, A: 0xff}, theme.ColorNameInputBackground: color.NRGBA{R: 0x66, G: 0x66, B: 0x66, A: 0xff}, theme.ColorNameInputBorder: color.NRGBA{R: 0x86, G: 0x86, B: 0x86, A: 0xff}, theme.ColorNameMenuBackground: color.NRGBA{R: 0x56, G: 0x56, B: 0x56, A: 0xff}, From 4057b5476924287d3394fc2632fda17167b450ce Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 3 Oct 2023 23:12:11 +0100 Subject: [PATCH 14/32] Fix broken build tag line --- internal/driver/mobile/app/app.go | 4 ++-- internal/driver/mobile/app/x11.c | 4 ++-- internal/driver/mobile/app/x11.go | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/driver/mobile/app/app.go b/internal/driver/mobile/app/app.go index 3c21778f4a..de56614499 100644 --- a/internal/driver/mobile/app/app.go +++ b/internal/driver/mobile/app/app.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build freebsd || linux || darwin || windows -// +build freebsd linux darwin windows +//go:build freebsd || linux || darwin || windows || openbsd +// +build freebsd linux darwin windows openbsd package app diff --git a/internal/driver/mobile/app/x11.c b/internal/driver/mobile/app/x11.c index 938cdb1028..c0c86ade4f 100644 --- a/internal/driver/mobile/app/x11.c +++ b/internal/driver/mobile/app/x11.c @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (linux && !android) || freebsd -// +build linux,!android freebsd +//go:build (linux && !android) || freebsd || openbsd +// +build linux,!android freebsd openbsd #include "_cgo_export.h" #include diff --git a/internal/driver/mobile/app/x11.go b/internal/driver/mobile/app/x11.go index 300297c3b1..c9e2d524cd 100644 --- a/internal/driver/mobile/app/x11.go +++ b/internal/driver/mobile/app/x11.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:build (linux && !android) || freebsd -// +build linux,!android freebsd +//go:build (linux && !android) || freebsd || openbsd +// +build linux,!android freebsd openbsd package app @@ -16,6 +16,7 @@ than screens with touch panels. /* #cgo LDFLAGS: -lEGL -lGLESv2 -lX11 #cgo freebsd CFLAGS: -I/usr/local/include/ +#cgo openbsd CFLAGS: -I/usr/X11R6/include/ void createWindow(void); void processEvents(void); From c7fe4585b78f1c75dec495ff6181e39e652117e5 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 11 Sep 2023 15:44:52 +0100 Subject: [PATCH 15/32] Align canvas menu buttons to match content of canvas, not outer edge --- internal/driver/mobile/canvas.go | 4 ++++ internal/driver/mobile/menu.go | 3 +++ 2 files changed, 7 insertions(+) diff --git a/internal/driver/mobile/canvas.go b/internal/driver/mobile/canvas.go index 623c520ed0..ec2375ba3b 100644 --- a/internal/driver/mobile/canvas.go +++ b/internal/driver/mobile/canvas.go @@ -7,6 +7,7 @@ import ( "time" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/driver/mobile" "fyne.io/fyne/v2/internal/app" "fyne.io/fyne/v2/internal/driver" @@ -167,6 +168,9 @@ func (c *mobileCanvas) setMenu(menu fyne.CanvasObject) { } func (c *mobileCanvas) setWindowHead(head fyne.CanvasObject) { + if c.padded { + head = container.NewPadded(head) + } c.windowHead = head c.SetMobileWindowHeadTree(head) } diff --git a/internal/driver/mobile/menu.go b/internal/driver/mobile/menu.go index 50e24d06db..43123baccc 100644 --- a/internal/driver/mobile/menu.go +++ b/internal/driver/mobile/menu.go @@ -55,6 +55,9 @@ func (c *mobileCanvas) showMenu(menu *fyne.MainMenu) { for _, item := range menu.Items { panel.Add(newMenuLabel(item, panel, c)) } + if c.padded { + panel = container.NewPadded(panel) + } bg := canvas.NewRectangle(theme.BackgroundColor()) shadow := canvas.NewHorizontalGradient(theme.ShadowColor(), color.Transparent) From 3949c51f08a3f39ab8e8ae8787492e502aa96b64 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 11 Sep 2023 15:54:59 +0100 Subject: [PATCH 16/32] Update test to check old structure (without padding) --- internal/driver/mobile/menu_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/driver/mobile/menu_test.go b/internal/driver/mobile/menu_test.go index 4681ae04d4..8581330ebb 100644 --- a/internal/driver/mobile/menu_test.go +++ b/internal/driver/mobile/menu_test.go @@ -32,6 +32,7 @@ func TestMobileCanvas_DismissBar(t *testing.T) { func TestMobileCanvas_DismissMenu(t *testing.T) { c := NewCanvas().(*mobileCanvas) + c.padded = false c.SetContent(canvas.NewRectangle(theme.BackgroundColor())) menu := fyne.NewMainMenu( fyne.NewMenu("Test", fyne.NewMenuItem("TapMe", func() {}))) From db62982919aa1b377bace54c78e6719d5409a968 Mon Sep 17 00:00:00 2001 From: Bernhard Feichtinger <43303168+BieHDC@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:09:28 +0200 Subject: [PATCH 17/32] Fix Compilation with Android NDK r26 --- app/app_mobile_and.c | 10 +++++----- internal/driver/mobile/android.c | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/app/app_mobile_and.c b/app/app_mobile_and.c index 4f5aecdade..cbe6dc2336 100644 --- a/app/app_mobile_and.c +++ b/app/app_mobile_and.c @@ -42,10 +42,10 @@ jobject getSystemService(uintptr_t jni_env, uintptr_t ctx, char *service) { JNIEnv *env = (JNIEnv*)jni_env; jstring serviceStr = (*env)->NewStringUTF(env, service); - jclass ctxClass = (*env)->GetObjectClass(env, ctx); + jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx); jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); - return (jobject)(*env)->CallObjectMethod(env, ctx, getSystemService, serviceStr); + return (jobject)(*env)->CallObjectMethod(env, (jobject)ctx, getSystemService, serviceStr); } int nextId = 1; @@ -81,7 +81,7 @@ void openURL(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *url) { jclass contextClass = find_class(env, "android/content/Context"); jmethodID start = find_method(env, contextClass, "startActivity", "(Landroid/content/Intent;)V"); - (*env)->CallVoidMethod(env, ctx, start, intent); + (*env)->CallVoidMethod(env, (jobject)ctx, start, intent); } void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *title, char *body) { @@ -94,7 +94,7 @@ void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char jobject builder = (*env)->NewObject(env, cls, constructor, ctx); jclass mgrCls = find_class(env, "android/app/NotificationManager"); - jobject mgr = getSystemService(env, ctx, "notification"); + jobject mgr = getSystemService((uintptr_t)env, ctx, "notification"); if (isOreoOrLater(env)) { jstring channelId = (*env)->NewStringUTF(env, "fyne-notif"); @@ -128,4 +128,4 @@ void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char jmethodID notify = find_method(env, mgrCls, "notify", "(ILandroid/app/Notification;)V"); (*env)->CallVoidMethod(env, mgr, notify, nextId, notif); nextId++; -} \ No newline at end of file +} diff --git a/internal/driver/mobile/android.c b/internal/driver/mobile/android.c index 3c57531b03..f1c5bcc620 100644 --- a/internal/driver/mobile/android.c +++ b/internal/driver/mobile/android.c @@ -41,7 +41,7 @@ static jmethodID find_static_method(JNIEnv *env, jclass clazz, const char *name, return m; } -char* getString(uintptr_t jni_env, uintptr_t ctx, jstring str) { +const char* getString(uintptr_t jni_env, uintptr_t ctx, jstring str) { JNIEnv *env = (JNIEnv*)jni_env; const char *chars = (*env)->GetStringUTFChars(env, str, NULL); @@ -65,11 +65,11 @@ jobject parseURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { jobject getClipboard(uintptr_t jni_env, uintptr_t ctx) { JNIEnv *env = (JNIEnv*)jni_env; - jclass ctxClass = (*env)->GetObjectClass(env, ctx); + jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx); jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); jstring service = (*env)->NewStringUTF(env, "clipboard"); - jobject ret = (jobject)(*env)->CallObjectMethod(env, ctx, getSystemService, service); + jobject ret = (*env)->CallObjectMethod(env, (jobject)ctx, getSystemService, service); jthrowable err = (*env)->ExceptionOccurred(env); if (err != NULL) { @@ -80,7 +80,7 @@ jobject getClipboard(uintptr_t jni_env, uintptr_t ctx) { return ret; } -char *getClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) { +const char *getClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx) { JNIEnv *env = (JNIEnv*)jni_env; jobject mgr = getClipboard(jni_env, ctx); if (mgr == NULL) { @@ -120,10 +120,10 @@ void setClipboardContent(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, ch jobject getContentResolver(uintptr_t jni_env, uintptr_t ctx) { JNIEnv *env = (JNIEnv*)jni_env; - jclass ctxClass = (*env)->GetObjectClass(env, ctx); + jclass ctxClass = (*env)->GetObjectClass(env, (jobject)ctx); jmethodID getContentResolver = find_method(env, ctxClass, "getContentResolver", "()Landroid/content/ContentResolver;"); - return (jobject)(*env)->CallObjectMethod(env, ctx, getContentResolver); + return (jobject)(*env)->CallObjectMethod(env, (jobject)ctx, getContentResolver); } void* openStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { @@ -165,7 +165,7 @@ void* saveStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { return (*env)->NewGlobalRef(env, stream); } -char* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* total) { +jbyte* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* total) { JNIEnv *env = (JNIEnv*)jni_env; jclass streamClass = (*env)->GetObjectClass(env, stream); jmethodID read = find_method(env, streamClass, "read", "([BII)I"); @@ -178,12 +178,12 @@ char* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* t return NULL; } - char* bytes = malloc(sizeof(char)*count); + jbyte* bytes = (jbyte*)malloc(sizeof(jbyte)*count); (*env)->GetByteArrayRegion(env, data, 0, count, bytes); return bytes; } -void writeStream(uintptr_t jni_env, uintptr_t ctx, void* stream, char* buf, int len) { +void writeStream(uintptr_t jni_env, uintptr_t ctx, void* stream, jbyte* buf, int len) { JNIEnv *env = (JNIEnv*)jni_env; jclass streamClass = (*env)->GetObjectClass(env, stream); jmethodID write = find_method(env, streamClass, "write", "([BII)V"); @@ -246,7 +246,7 @@ bool canListContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { return false; } - char *str = getString(jni_env, ctx, type); + const char *str = getString(jni_env, ctx, type); return strcmp(str, "vnd.android.document/directory") == 0; } @@ -297,7 +297,7 @@ bool createListableURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { return false; } -char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { +const char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { JNIEnv *env = (JNIEnv*)jni_env; jobject resolver = getContentResolver(jni_env, ctx); jobject uri = parseURI(jni_env, ctx, uriCstr); @@ -322,7 +322,7 @@ char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { if (((jboolean)(*env)->CallBooleanMethod(env, cursor, first)) == JNI_TRUE) { jstring name = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0); - char *fname = getString(jni_env, ctx, name); + const char *fname = getString(jni_env, ctx, name); return fname; } @@ -401,7 +401,7 @@ char* listContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) { jmethodID toString = (*env)->GetMethodID(env, uriClass, "toString", "()Ljava/lang/String;"); jstring s = (jstring)(*env)->CallObjectMethod(env, childUri, toString); - char *uid = getString(jni_env, ctx, s); + const char *uid = getString(jni_env, ctx, s); // append char *old = ret; From 78b93f9d0e2535f8b0cc61c24cf0ee4a5445d2c4 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 20 Sep 2023 22:46:41 +0100 Subject: [PATCH 18/32] Fix issue where table tap could hang Fixes #4264 --- widget/table.go | 4 ++-- widget/table_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/widget/table.go b/widget/table.go index a8f2e47ba9..fdb9b81229 100644 --- a/widget/table.go +++ b/widget/table.go @@ -549,11 +549,11 @@ func (t *Table) Tapped(e *fyne.PointEvent) { } col := t.columnAt(e.Position) - if col == -1 { + if col == noCellMatch { return // out of col range } row := t.rowAt(e.Position) - if row == -1 { + if row == noCellMatch { return // out of row range } t.Select(TableCellID{row, col}) diff --git a/widget/table_test.go b/widget/table_test.go index eed19396c1..8ed6eb5cae 100644 --- a/widget/table_test.go +++ b/widget/table_test.go @@ -646,6 +646,36 @@ func TestTable_Selection(t *testing.T) { assert.Equal(t, 1, selectedRow) } +func TestTable_Selection_OnHeader(t *testing.T) { + test.NewApp() + defer test.NewApp() + + table := NewTableWithHeaders( + func() (int, int) { return 5, 5 }, + func() fyne.CanvasObject { + return NewLabel("placeholder") + }, + func(id TableCellID, c fyne.CanvasObject) { + text := fmt.Sprintf("Cell %d, %d", id.Row, id.Col) + c.(*Label).SetText(text) + }) + assert.Nil(t, table.selectedCell) + + w := test.NewWindow(table) + defer w.Close() + w.Resize(fyne.NewSize(180, 180)) + + selected := false + table.OnSelected = func(TableCellID) { + selected = true + } + test.TapCanvas(w.Canvas(), fyne.NewPos(35, 5)) + assert.False(t, selected) + + test.TapCanvas(w.Canvas(), fyne.NewPos(5, 58)) + assert.False(t, selected) +} + func TestTable_Select(t *testing.T) { test.NewApp() defer test.NewApp() From 9270eb67e5c7db3be5f6352aee2c003a1e730ad1 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 23 Sep 2023 19:08:27 +0100 Subject: [PATCH 19/32] Fixing icon name to match API --- cmd/fyne_demo/tutorials/icons.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/fyne_demo/tutorials/icons.go b/cmd/fyne_demo/tutorials/icons.go index 74d56ff752..0d0ae952b6 100644 --- a/cmd/fyne_demo/tutorials/icons.go +++ b/cmd/fyne_demo/tutorials/icons.go @@ -145,9 +145,9 @@ func loadIcons() []iconInfo { {"ViewRefreshIcon", theme.ViewRefreshIcon()}, {"VisibilityIcon", theme.VisibilityIcon()}, {"VisibilityOffIcon", theme.VisibilityOffIcon()}, - {"ZoomFitIcon", theme.ZoomFitIcon()}, - {"ZoomInIcon", theme.ZoomInIcon()}, - {"ZoomOutIcon", theme.ZoomOutIcon()}, + {"ViewZoomFitIcon", theme.ZoomFitIcon()}, + {"ViewZoomInIcon", theme.ZoomInIcon()}, + {"ViewZoomOutIcon", theme.ZoomOutIcon()}, {"MoreHorizontalIcon", theme.MoreHorizontalIcon()}, {"MoreVerticalIcon", theme.MoreVerticalIcon()}, From dc6424bc108d60e723d4a6cff0bfa97ca35295be Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 25 Sep 2023 19:38:57 +0100 Subject: [PATCH 20/32] Handle unicode in colour parsing Fixes #4270 --- theme/json.go | 2 +- theme/json_test.go | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/theme/json.go b/theme/json.go index 3712574bbb..a0bbf81b67 100644 --- a/theme/json.go +++ b/theme/json.go @@ -39,7 +39,7 @@ type hexColor string func (h hexColor) color() (color.Color, error) { data := h - switch len(h) { + switch len([]rune(h)) { case 8, 6: case 9, 7: // remove # prefix data = h[1:] diff --git a/theme/json_test.go b/theme/json_test.go index 59c676be5d..1b071008bb 100644 --- a/theme/json_test.go +++ b/theme/json_test.go @@ -28,6 +28,10 @@ func TestFromJSON(t *testing.T) { assert.Equal(t, float32(5), th.Size(SizeNameInlineIcon)) assert.Equal(t, "NotoMono-Regular.ttf", th.Font(fyne.TextStyle{Monospace: true}).Name()) assert.Equal(t, "cancel_Paths.svg", th.Icon(IconNameCancel).Name()) + + th, _ = FromJSON("{\"Colors\":{\"foreground\":\"\xb1\"}}") + c := th.Color(ColorNameForeground, VariantLight) + assert.NotNil(t, c) } func TestFromTOML_Resource(t *testing.T) { From 7d16ae4393e0f678946128bf4b89795ea3a19054 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 23 Sep 2023 20:25:08 +0100 Subject: [PATCH 21/32] Move file item to use two wrapped lines and ellipsis Fixes #2165 --- dialog/fileitem.go | 36 +++++++++++++++++++----------------- dialog/fileitem_test.go | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/dialog/fileitem.go b/dialog/fileitem.go index 86e5c15ac7..36112a2771 100644 --- a/dialog/fileitem.go +++ b/dialog/fileitem.go @@ -11,7 +11,6 @@ import ( const ( fileIconSize = 64 fileInlineIconSize = 24 - fileTextSize = 24 fileIconCellWidth = fileIconSize * 1.25 ) @@ -26,14 +25,16 @@ type fileDialogItem struct { func (i *fileDialogItem) CreateRenderer() fyne.WidgetRenderer { text := widget.NewLabelWithStyle(i.name, fyne.TextAlignCenter, fyne.TextStyle{}) - text.Wrapping = fyne.TextTruncate + text.Truncation = fyne.TextTruncateEllipsis + text.Wrapping = fyne.TextWrapBreak icon := widget.NewFileIcon(i.location) return &fileItemRenderer{ - item: i, - icon: icon, - text: text, - objects: []fyne.CanvasObject{icon, text}, + item: i, + icon: icon, + text: text, + objects: []fyne.CanvasObject{icon, text}, + fileTextSize: widget.NewLabel("M\nM").MinSize().Height, // cache two-line label height, } } @@ -76,7 +77,8 @@ func (f *fileDialog) newFileItem(location fyne.URI, dir, up bool) *fileDialogIte } type fileItemRenderer struct { - item *fileDialogItem + item *fileDialogItem + fileTextSize float32 icon *widget.FileIcon text *widget.Label @@ -89,32 +91,32 @@ func (s fileItemRenderer) Layout(size fyne.Size) { s.icon.Move(fyne.NewPos((size.Width-fileIconSize)/2, 0)) s.text.Alignment = fyne.TextAlignCenter - s.text.Resize(fyne.NewSize(size.Width, fileTextSize)) - s.text.Move(fyne.NewPos(0, size.Height-s.text.MinSize().Height)) + s.text.Resize(fyne.NewSize(size.Width, s.fileTextSize)) + s.text.Move(fyne.NewPos(0, size.Height-s.fileTextSize)) } else { s.icon.Resize(fyne.NewSize(fileInlineIconSize, fileInlineIconSize)) s.icon.Move(fyne.NewPos(theme.Padding(), (size.Height-fileInlineIconSize)/2)) s.text.Alignment = fyne.TextAlignLeading - s.text.Resize(fyne.NewSize(size.Width, fileTextSize)) - s.text.Move(fyne.NewPos(fileInlineIconSize, (size.Height-s.text.MinSize().Height)/2)) + textMin := s.text.MinSize() + s.text.Resize(fyne.NewSize(size.Width, textMin.Height)) + s.text.Move(fyne.NewPos(fileInlineIconSize, (size.Height-textMin.Height)/2)) } s.text.Refresh() } func (s fileItemRenderer) MinSize() fyne.Size { - var padding fyne.Size - if s.item.picker.view == gridView { - padding = fyne.NewSize(fileIconCellWidth-fileIconSize, theme.Padding()) - return fyne.NewSize(fileIconSize, fileIconSize+fileTextSize).Add(padding) + return fyne.NewSize(fileIconCellWidth, fileIconSize+s.fileTextSize) } - padding = fyne.NewSize(theme.Padding(), theme.Padding()*4) - return fyne.NewSize(fileInlineIconSize+s.text.MinSize().Width, fileTextSize).Add(padding) + textMin := s.text.MinSize() + return fyne.NewSize(fileInlineIconSize+textMin.Width+theme.Padding(), textMin.Height) } func (s fileItemRenderer) Refresh() { + s.fileTextSize = widget.NewLabel("M\nM").MinSize().Height // cache two-line label height + s.text.SetText(s.item.name) s.icon.SetURI(s.item.location) } diff --git a/dialog/fileitem_test.go b/dialog/fileitem_test.go index 3e2b972cdd..2a6d603b0b 100644 --- a/dialog/fileitem_test.go +++ b/dialog/fileitem_test.go @@ -6,7 +6,9 @@ import ( "github.com/stretchr/testify/assert" + "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/storage" + "fyne.io/fyne/v2/test" ) func TestFileItem_Name(t *testing.T) { @@ -106,3 +108,19 @@ func TestNewFileItem_ParentFolder(t *testing.T) { assert.Equal(t, "(Parent)", item.name) assert.Equal(t, parentDir.String()+"/", f.data[0].String()) } + +func TestFileItem_Wrap(t *testing.T) { + f := &fileDialog{file: &FileDialog{}} + _ = f.makeUI() + item := f.newFileItem(storage.NewFileURI("/path/to/filename.txt"), false, false) + item.Resize(item.MinSize()) + label := test.WidgetRenderer(item).(*fileItemRenderer).text + assert.Equal(t, "filename", label.Text) + texts := test.WidgetRenderer(label).Objects() + assert.Equal(t, 1, len(texts)) + + item.setLocation(storage.NewFileURI("/path/to/averylongfilename.svg"), false, false) + texts = test.WidgetRenderer(label).Objects() + assert.Equal(t, 2, len(texts)) + assert.Equal(t, "averylon", texts[0].(*canvas.Text).Text) +} From c00d0296a35b789822ee59ac02a0e1dc6e0303fd Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 23 Sep 2023 20:26:58 +0100 Subject: [PATCH 22/32] Fix the dialog size calculations to reflect --- dialog/file.go | 9 +++++---- dialog/file_test.go | 5 +++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/dialog/file.go b/dialog/file.go index af3ce3ef23..95a5f252f7 100644 --- a/dialog/file.go +++ b/dialog/file.go @@ -196,8 +196,8 @@ func (f *fileDialog) makeUI() fyne.CanvasObject { f.filesScroll = container.NewScroll(nil) // filesScroll's content will be set by setView function. verticalExtra := float32(float64(fileIconSize) * 0.25) - f.filesScroll.SetMinSize(fyne.NewSize(fileIconCellWidth*2+theme.Padding(), - (fileIconSize+fileTextSize)+theme.Padding()*2+verticalExtra)) + itemMin := f.newFileItem(storage.NewFileURI("filename.txt"), false, false).MinSize() + f.filesScroll.SetMinSize(itemMin.AddWidthHeight(itemMin.Width+theme.Padding()*3, verticalExtra)) f.breadcrumb = container.NewHBox() f.breadcrumbScroll = container.NewHScroll(container.NewPadded(f.breadcrumb)) @@ -576,8 +576,9 @@ func (f *FileDialog) effectiveStartingDir() fyne.ListableURI { func showFile(file *FileDialog) *fileDialog { d := &fileDialog{file: file, initialFileName: file.initialFileName} ui := d.makeUI() - size := ui.MinSize().Add(fyne.NewSize(fileIconCellWidth*2+theme.Padding()*6+theme.Padding(), - (fileIconSize+fileTextSize)+theme.Padding()*6)) + pad := theme.Padding() + itemMin := d.newFileItem(storage.NewFileURI("filename.txt"), false, false).MinSize() + size := ui.MinSize().Add(itemMin.AddWidthHeight(itemMin.Width+pad*4, pad*2)) d.win = widget.NewModalPopUp(ui, file.parent.Canvas()) d.win.Resize(size) diff --git a/dialog/file_test.go b/dialog/file_test.go index 37928eb412..b1f9fde167 100644 --- a/dialog/file_test.go +++ b/dialog/file_test.go @@ -117,8 +117,9 @@ func TestFileDialogResize(t *testing.T) { d := &fileDialog{file: file} open := widget.NewButton("open", func() {}) ui := container.NewBorder(nil, nil, nil, open) - originalSize := ui.MinSize().Add(fyne.NewSize(fileIconCellWidth*2+theme.Padding()*4, - (fileIconSize+fileTextSize)+theme.Padding()*4)) + pad := theme.Padding() + itemMin := d.newFileItem(storage.NewFileURI("filename.txt"), false, false).MinSize() + originalSize := ui.MinSize().Add(itemMin.AddWidthHeight(itemMin.Width+pad*6, pad*3)) d.win = widget.NewModalPopUp(ui, file.parent.Canvas()) d.win.Resize(originalSize) file.dialog = d From ba26ef33f50fece50643fd82e1319ab1d8af8fcd Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Sat, 23 Sep 2023 20:46:46 +0100 Subject: [PATCH 23/32] Fix ineffassign --- dialog/fileitem.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dialog/fileitem.go b/dialog/fileitem.go index 36112a2771..ba1c0e0959 100644 --- a/dialog/fileitem.go +++ b/dialog/fileitem.go @@ -85,7 +85,7 @@ type fileItemRenderer struct { objects []fyne.CanvasObject } -func (s fileItemRenderer) Layout(size fyne.Size) { +func (s *fileItemRenderer) Layout(size fyne.Size) { if s.item.picker.view == gridView { s.icon.Resize(fyne.NewSize(fileIconSize, fileIconSize)) s.icon.Move(fyne.NewPos((size.Width-fileIconSize)/2, 0)) @@ -105,7 +105,7 @@ func (s fileItemRenderer) Layout(size fyne.Size) { s.text.Refresh() } -func (s fileItemRenderer) MinSize() fyne.Size { +func (s *fileItemRenderer) MinSize() fyne.Size { if s.item.picker.view == gridView { return fyne.NewSize(fileIconCellWidth, fileIconSize+s.fileTextSize) } @@ -114,16 +114,16 @@ func (s fileItemRenderer) MinSize() fyne.Size { return fyne.NewSize(fileInlineIconSize+textMin.Width+theme.Padding(), textMin.Height) } -func (s fileItemRenderer) Refresh() { +func (s *fileItemRenderer) Refresh() { s.fileTextSize = widget.NewLabel("M\nM").MinSize().Height // cache two-line label height s.text.SetText(s.item.name) s.icon.SetURI(s.item.location) } -func (s fileItemRenderer) Objects() []fyne.CanvasObject { +func (s *fileItemRenderer) Objects() []fyne.CanvasObject { return s.objects } -func (s fileItemRenderer) Destroy() { +func (s *fileItemRenderer) Destroy() { } From efa64d93c347d2adc1d73a738f2c1b697c72f698 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 25 Sep 2023 19:31:11 +0100 Subject: [PATCH 24/32] Check if uri is empty rather than assuming / Fixes #4271 --- internal/repository/memory.go | 10 +++++----- internal/repository/memory_test.go | 10 ++++++++++ 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/internal/repository/memory.go b/internal/repository/memory.go index e7870a07a1..3537f58523 100644 --- a/internal/repository/memory.go +++ b/internal/repository/memory.go @@ -1,13 +1,13 @@ package repository import ( - "fyne.io/fyne/v2" - "fyne.io/fyne/v2/storage" - "fyne.io/fyne/v2/storage/repository" - "fmt" "io" "strings" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/storage" + "fyne.io/fyne/v2/storage/repository" ) // declare conformance to interfaces @@ -297,7 +297,7 @@ func (m *InMemoryRepository) List(u fyne.URI) ([]fyne.URI, error) { // does not have one. pSplit := strings.Split(p, "/") ncomp := len(pSplit) - if p[len(p)-1] == '/' { + if len(p) > 0 && p[len(p)-1] == '/' { ncomp-- } diff --git a/internal/repository/memory_test.go b/internal/repository/memory_test.go index 17bb5d0eff..b61b59c1da 100644 --- a/internal/repository/memory_test.go +++ b/internal/repository/memory_test.go @@ -68,6 +68,10 @@ func TestInMemoryRepositoryParsing(t *testing.T) { baz, _ := storage.ParseURI("mem:///baz") assert.Nil(t, err) assert.NotNil(t, baz) + + empty, _ := storage.ParseURI("mem:") + assert.Nil(t, err) + assert.NotNil(t, empty) } func TestInMemoryRepositoryExists(t *testing.T) { @@ -328,6 +332,7 @@ func TestInMemoryRepositoryListing(t *testing.T) { // set up our repository - it's OK if we already registered it m := NewInMemoryRepository("mem") repository.Register("mem", m) + m.Data[""] = []byte{1, 2, 3} m.Data["/foo"] = []byte{1, 2, 3} m.Data["/foo/bar"] = []byte{1, 2, 3} m.Data["/foo/baz/"] = []byte{1, 2, 3} @@ -346,6 +351,11 @@ func TestInMemoryRepositoryListing(t *testing.T) { stringListing = append(stringListing, u.String()) } assert.ElementsMatch(t, []string{"mem:///foo/bar", "mem:///foo/baz/"}, stringListing) + + empty, _ := storage.ParseURI("mem:") // invalid path + canList, err = storage.CanList(empty) + assert.NotNil(t, err) + assert.False(t, canList) } func TestInMemoryRepositoryCreateListable(t *testing.T) { From 1beff996fabf409773461ecdd51706dd777ae974 Mon Sep 17 00:00:00 2001 From: Jordan Goulder Date: Thu, 28 Sep 2023 13:58:48 -0400 Subject: [PATCH 25/32] Fix #4260: Synchronize access to fileDialog.data file list (#4279) This change adds a RWMutex to synchronize access to the fileDialog.data file list. The refreshDir function clears and rebuilds fileDialog.data, meanwhile, several other functions are indexing into it. This allowed for scenarios where fileDialog.data was either nil or smaller than expected when indexing into it. Fixes #4260 --- dialog/file.go | 39 ++++++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/dialog/file.go b/dialog/file.go index 95a5f252f7..5327d24e64 100644 --- a/dialog/file.go +++ b/dialog/file.go @@ -7,6 +7,7 @@ import ( "path/filepath" "runtime" "strings" + "sync" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -54,7 +55,9 @@ type fileDialog struct { showHidden bool view viewLayout - data []fyne.URI + + data []fyne.URI + dataLock sync.RWMutex win *widget.PopUp selected fyne.URI @@ -334,7 +337,9 @@ func (f *fileDialog) loadFavorites() { } func (f *fileDialog) refreshDir(dir fyne.ListableURI) { + f.dataLock.Lock() f.data = nil + f.dataLock.Unlock() files, err := dir.List() if err != nil { @@ -366,7 +371,10 @@ func (f *fileDialog) refreshDir(dir fyne.ListableURI) { icons = append(icons, file) } } + + f.dataLock.Lock() f.data = icons + f.dataLock.Unlock() f.files.Refresh() f.filesScroll.Offset = fyne.NewPos(0, 0) @@ -480,20 +488,26 @@ func (f *fileDialog) setSelected(file fyne.URI, id int) { func (f *fileDialog) setView(view viewLayout) { f.view = view count := func() int { + f.dataLock.RLock() + defer f.dataLock.RUnlock() + return len(f.data) } template := func() fyne.CanvasObject { return f.newFileItem(storage.NewFileURI("./tempfile"), true, false) } update := func(id widget.GridWrapItemID, o fyne.CanvasObject) { - dir := f.data[id] - parent := id == 0 && len(dir.Path()) < len(f.dir.Path()) - _, isDir := dir.(fyne.ListableURI) - o.(*fileDialogItem).setLocation(dir, isDir || parent, parent) + if dir, ok := f.getDataItem(id); ok { + parent := id == 0 && len(dir.Path()) < len(f.dir.Path()) + _, isDir := dir.(fyne.ListableURI) + o.(*fileDialogItem).setLocation(dir, isDir || parent, parent) + } } choose := func(id int) { - f.selectedID = id - f.setSelected(f.data[id], id) + if file, ok := f.getDataItem(id); ok { + f.selectedID = id + f.setSelected(file, id) + } } if f.view == gridView { grid := widget.NewGridWrap(count, template, update) @@ -511,6 +525,17 @@ func (f *fileDialog) setView(view viewLayout) { f.filesScroll.Refresh() } +func (f *fileDialog) getDataItem(id int) (fyne.URI, bool) { + f.dataLock.RLock() + defer f.dataLock.RUnlock() + + if id >= len(f.data) { + return nil, false + } + + return f.data[id], true +} + // effectiveStartingDir calculates the directory at which the file dialog should // open, based on the values of startingDirectory, CWD, home, and any error // conditions which occur. From e309f7078bd5d697be61534eb81c54788ab050da Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Fri, 29 Sep 2023 14:39:38 +0100 Subject: [PATCH 26/32] Fix accidental truncation when multi-byte runes had ellipsis trunc Fixes #4283 --- widget/richtext.go | 4 ++-- widget/richtext_test.go | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/widget/richtext.go b/widget/richtext.go index f358d6f5b8..9ef858d79e 100644 --- a/widget/richtext.go +++ b/widget/richtext.go @@ -1017,8 +1017,8 @@ func lineBounds(seg *TextSegment, wrap fyne.TextWrap, trunc fyne.TextTruncation, } default: if trunc == fyne.TextTruncateEllipsis { - txt := seg.Text[low:high] - end, full := truncateLimit(txt, seg.Visual().(*canvas.Text), int(measureWidth), []rune{'…'}) + txt := []rune(seg.Text)[low:high] + end, full := truncateLimit(string(txt), seg.Visual().(*canvas.Text), int(measureWidth), []rune{'…'}) high = low + end bounds = append(bounds, rowBoundary{[]RichTextSegment{seg}, reuse, low, high, !full}) reuse++ diff --git a/widget/richtext_test.go b/widget/richtext_test.go index 00c5ef4074..136ae13411 100644 --- a/widget/richtext_test.go +++ b/widget/richtext_test.go @@ -969,6 +969,16 @@ func TestText_lineBounds(t *testing.T) { }, ellipses: 1, }, + { + name: "Multi_byte_ellipsis_not_truncated", + text: "🪃 234", + trunc: fyne.TextTruncateEllipsis, + wrap: fyne.TextWrapOff, + want: [][2]int{ + {0, 5}, + }, + ellipses: 0, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From 6822d1ae73840a046838dd8532410d64fb9b9507 Mon Sep 17 00:00:00 2001 From: Samy A Date: Thu, 21 Sep 2023 10:38:54 +0100 Subject: [PATCH 27/32] dirty temporary fix to https://github.com/fyne-io/fyne/issues/3909 --- container/doctabs.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/container/doctabs.go b/container/doctabs.go index 877f62f521..cb276f6b91 100644 --- a/container/doctabs.go +++ b/container/doctabs.go @@ -363,6 +363,13 @@ func (r *docTabsRenderer) buildTabButtons(count int, buttons *fyne.Container) { func (r *docTabsRenderer) scrollToSelected() { buttons := r.scroller.Content.(*fyne.Container) + + // https://github.com/fyne-io/fyne/issues/3909 + // very dirty temporary fix to this crash! + if r.docTabs.current < 0 || r.docTabs.current >= len(buttons.Objects) { + return + } + button := buttons.Objects[r.docTabs.current] pos := button.Position() size := button.Size() From 674f8cb8b9bf6fda7c59075a8bf6b84245cf9f62 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Tue, 12 Sep 2023 11:36:39 +0100 Subject: [PATCH 28/32] Only focus collections on desktop Fixes #4236 --- widget/gridwrap.go | 13 ++++++++----- widget/list.go | 11 +++++++---- widget/table.go | 14 ++++++++------ widget/tree.go | 12 +++++++----- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/widget/gridwrap.go b/widget/gridwrap.go index 666e153e46..8a782edd5e 100644 --- a/widget/gridwrap.go +++ b/widget/gridwrap.go @@ -528,13 +528,16 @@ func (l *gridWrapLayout) setupGridItem(li *gridWrapItem, id GridWrapItemID, focu f(id, li.child) } li.onTapped = func() { - l.list.RefreshItem(l.list.currentFocus) - canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list) - if canvas != nil { - canvas.Focus(l.list) + if !fyne.CurrentDevice().IsMobile() { + l.list.RefreshItem(l.list.currentFocus) + canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list) + if canvas != nil { + canvas.Focus(l.list) + } + + l.list.currentFocus = id } - l.list.currentFocus = id l.list.Select(id) } } diff --git a/widget/list.go b/widget/list.go index 1183d2b923..0aeb686c31 100644 --- a/widget/list.go +++ b/widget/list.go @@ -611,12 +611,15 @@ func (l *listLayout) setupListItem(li *listItem, id ListItemID, focus bool) { f(id, li.child) } li.onTapped = func() { - canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list) - if canvas != nil { - canvas.Focus(l.list) + if !fyne.CurrentDevice().IsMobile() { + canvas := fyne.CurrentApp().Driver().CanvasForObject(l.list) + if canvas != nil { + canvas.Focus(l.list) + } + + l.list.currentFocus = id } - l.list.currentFocus = id l.list.Select(id) } } diff --git a/widget/table.go b/widget/table.go index fdb9b81229..41b8c15e99 100644 --- a/widget/table.go +++ b/widget/table.go @@ -558,13 +558,15 @@ func (t *Table) Tapped(e *fyne.PointEvent) { } t.Select(TableCellID{row, col}) - t.RefreshItem(t.currentFocus) - canvas := fyne.CurrentApp().Driver().CanvasForObject(t) - if canvas != nil { - canvas.Focus(t) + if !fyne.CurrentDevice().IsMobile() { + t.RefreshItem(t.currentFocus) + canvas := fyne.CurrentApp().Driver().CanvasForObject(t) + if canvas != nil { + canvas.Focus(t) + } + t.currentFocus = TableCellID{row, col} + t.RefreshItem(t.currentFocus) } - t.currentFocus = TableCellID{row, col} - t.RefreshItem(t.currentFocus) } // columnAt returns a positive integer (or 0) for the column that is found at the `pos` X position. diff --git a/widget/tree.go b/widget/tree.go index 99a476f02c..bd71c2efb7 100644 --- a/widget/tree.go +++ b/widget/tree.go @@ -883,12 +883,14 @@ func (n *treeNode) Tapped(*fyne.PointEvent) { } n.tree.Select(n.uid) - canvas := fyne.CurrentApp().Driver().CanvasForObject(n.tree) - if canvas != nil { - canvas.Focus(n.tree) + if !fyne.CurrentDevice().IsMobile() { + canvas := fyne.CurrentApp().Driver().CanvasForObject(n.tree) + if canvas != nil { + canvas.Focus(n.tree) + } + n.tree.currentFocus = n.uid + n.Refresh() } - n.tree.currentFocus = n.uid - n.Refresh() } func (n *treeNode) partialRefresh() { From 93e9d6c49b2d5feec81a6e78dc6d10d78fc10c5a Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Mon, 4 Sep 2023 15:53:28 +0100 Subject: [PATCH 29/32] Updating README following release Various fixes too --- README.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 99d2f0cdc0..a7e456a083 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

Go API Reference Latest Release - Join us on Slack + Join us on Slack
Code Status Build Status @@ -14,22 +14,22 @@ It is designed to build applications that run on desktop and mobile devices with a single codebase. -Version 2.3 is the current release of the Fyne API, it added a refined theme design, -cloud storage, improved text handling for international languages and many +Version 2.4 is the current release of the Fyne API, it added rounded corners, emoji, +layout debug support and table headers, along with a large number of smaller feature additions. We are now working towards the next big release, codenamed -[Dalwhinnie](https://github.com/fyne-io/fyne/milestone/18) +[Elgin](https://github.com/fyne-io/fyne/milestone/21) and more news will follow in our news feeds and GitHub project. # Prerequisites -To develop apps using Fyne you will need Go version 1.14 or later, a C compiler and your system's development tools. +To develop apps using Fyne you will need Go version 1.17 or later, a C compiler and your system's development tools. If you're not sure if that's all installed or you don't know how then check out our [Getting Started](https://fyne.io/develop/) document. Using the standard go tools you can install Fyne's core library using: - go get fyne.io/fyne/v2 + go get fyne.io/fyne/v2@latest After importing a new module, run the following command before compiling the code for the first time. Avoid running it before writing code that uses the module to prevent accidental removal of dependencies: @@ -45,19 +45,19 @@ To run a showcase of the features of Fyne execute the following: And you should see something like this (after you click a few buttons):

- Fyne Demo Dark Theme + Fyne Demo Dark Theme

Or if you are using the light theme:

- Fyne Demo Light Theme + Fyne Demo Light Theme

And even running on a mobile device:

- Fyne Demo Mobile Light Theme + Fyne Demo Mobile Light Theme

# Getting Started @@ -102,16 +102,13 @@ It should look like this:
- Fyne Hello Dark Theme + Fyne Hello Dark Theme - Fyne Hello Dark Theme + Fyne Hello Dark Theme
-> Note that Windows applications load from a command prompt by default, which means if you click an icon you may see a command window. -> To fix this add the parameters `-ldflags -H=windowsgui` to your run or build commands. - ## Run in mobile simulation There is a helpful mobile simulation mode that gives a hint of how your app would work on a mobile device: @@ -186,4 +183,6 @@ These are optional applications but can help to create a more complete desktop e ## FyneDesk (Linux / BSD) -To go all the way with Fyne on your desktop / laptop computer you could install [FyneDesk](https://github.com/fyne-io/fynedesk) as well :) +To go all the way with Fyne on your desktop / laptop computer you could install [FyneDesk](https://github.com/fyshos/fynedesk) as well :) + +![FyneDesk screenshopt in dark mode](https://fyshos.com/img/desktop.png) From d6c36e394482d316ea32c701907f57b3d6072355 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 4 Oct 2023 09:55:02 +0100 Subject: [PATCH 30/32] Proposed changes for v2.4.1 --- CHANGELOG.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd3de7649..d6f8635122 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,9 +3,34 @@ 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.4.1 - 8 October 2023 + +### Fixed + +* Left key on tree now collapses open branch +* Avoid memory leak in Android driver code +* Entry Field on Android in Landscape Mode Shows "0" (#4036) +* DocTabs Indicator remains visible after last tab is removed (#4220) +* Some SVG resources don't update appearance correctly with the theme (#3900) +* Fix mobile simulation builds on OpenBSD +* Fix alignment of menu button on mobile +* Fix Compilation with Android NDK r26 +* Clicking table headers causes high CPU consumption (#4264) +* Frequent clicking on table may cause the program to not respond (#4210) +* Application stops responding when scrolling a table (#4263) +* Possible crash parsing malformed JSON color (#4270) +* NewFolderOpen: incomplete filenames (#2165) +* Resolve issue where storage.List could crash with short URI (#4271) +* Application crash when fast clicking the folders inside the file dialog (#4260) +* TextTruncateEllipsis abnormally truncates strings with multi-byte UTF-8 characters (#4283) +* Last character doesn't appear in Select when there is a special character (#4293) +* Resolve random crash in DocTab (#3909) +* Selecting items from a list caused the keyboard to popup on Android (#4236) + + ## 2.4.0 - 1 September 2023 -## Added +### Added * Rounded corners in rectangle (#1090) * Support for emoji in text @@ -43,7 +68,7 @@ More detailed release notes can be found on the [releases page](https://github.c * Add `--pprof` option to fyne build commands to enable profiling * Support compiling from Android (termux) -## Changed +### Changed * Go 1.17 or later is now required. * Theme updated for rounded corners on buttons and input widgets @@ -60,7 +85,7 @@ More detailed release notes can be found on the [releases page](https://github.c * Improving performance of lookup for theme data * Improved application startup time -## Fixed +### Fixed * Rendering performance enhancements * `dialog.NewProgressInfinite` is deprecated, but dialog.NewCustom isn't equivalent From dea2af703ff17dec4bb8b1ca2d848804f6c52c37 Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Wed, 4 Oct 2023 18:56:55 +0100 Subject: [PATCH 31/32] Adding missed locks for list/table --- data/binding/listbinding.go | 6 ++++++ data/binding/treebinding.go | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/data/binding/listbinding.go b/data/binding/listbinding.go index a0bb5d2d16..539f307c5a 100644 --- a/data/binding/listbinding.go +++ b/data/binding/listbinding.go @@ -16,6 +16,9 @@ type listBase struct { // GetItem returns the DataItem at the specified index. func (b *listBase) GetItem(i int) (DataItem, error) { + b.lock.RLock() + defer b.lock.RUnlock() + if i < 0 || i >= len(b.items) { return nil, errOutOfBounds } @@ -25,6 +28,9 @@ func (b *listBase) GetItem(i int) (DataItem, error) { // Length returns the number of items in this data list. func (b *listBase) Length() int { + b.lock.RLock() + defer b.lock.RUnlock() + return len(b.items) } diff --git a/data/binding/treebinding.go b/data/binding/treebinding.go index 557f401724..6d9c817a87 100644 --- a/data/binding/treebinding.go +++ b/data/binding/treebinding.go @@ -21,6 +21,9 @@ type treeBase struct { // GetItem returns the DataItem at the specified id. func (t *treeBase) GetItem(id string) (DataItem, error) { + t.lock.RLock() + defer t.lock.RUnlock() + if item, ok := t.items[id]; ok { return item, nil } @@ -30,6 +33,9 @@ func (t *treeBase) GetItem(id string) (DataItem, error) { // ChildIDs returns the ordered IDs of items in this data tree that are children of the specified ID. func (t *treeBase) ChildIDs(id string) []string { + t.lock.RLock() + defer t.lock.RUnlock() + if ids, ok := t.ids[id]; ok { return ids } From 6c63361573fbdf1fe88b82478c1d26482ef8a35b Mon Sep 17 00:00:00 2001 From: Andy Williams Date: Thu, 5 Oct 2023 13:35:48 +0100 Subject: [PATCH 32/32] Unresolve for this potential release --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d6f8635122..c9f4488539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,6 @@ More detailed release notes can be found on the [releases page](https://github.c * Possible crash parsing malformed JSON color (#4270) * NewFolderOpen: incomplete filenames (#2165) * Resolve issue where storage.List could crash with short URI (#4271) -* Application crash when fast clicking the folders inside the file dialog (#4260) * TextTruncateEllipsis abnormally truncates strings with multi-byte UTF-8 characters (#4283) * Last character doesn't appear in Select when there is a special character (#4293) * Resolve random crash in DocTab (#3909)