diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 390029dd94..d8863c1a31 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -2,7 +2,7 @@ name: Bug report about: Create a report to help us improve title: '' -labels: 'bug' +labels: 'unverified' assignees: '' --- diff --git a/CHANGELOG.md b/CHANGELOG.md index 50ad3e8977..46fccdce9b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ This file lists the main changes with each version of the Fyne toolkit. More detailed release notes can be found on the [releases page](https://github.com/fyne-io/fyne/releases). +## 2.0.3 - 30 April 2021 + +### Fixed + +* Optimisations for TextGrid rendering +* Data binding with widget.List sometimes crash while scrolling (#2125) +* Fix compilation on FreeBSD 13 +* DataLists should notify only once when change. +* Keyboard will appear on Android in disabled Entry Widget (#2139) +* Save dialog with filename for Android +* form widget can't draw hinttext of appended item. (#2028) +* Don't create empty shortcuts (#2148) +* Install directory for windows install command contains ".exe" +* Fix compilation for Linux Wayland apps +* Fix tab button layout on mobile (#2117) +* Options popup does not move if a SelectEntry widget moves with popup open +* Speed improvements to Select and SelectEntry drop down +* theme/fonts has an apache LICENSE file but it should have SIL OFL (#2193) +* Fix build requirements for target macOS platforms (#2154) +* ScrollEvent.Position and ScrollEvent.AbsolutePosition is 0,0 (#2199) + + ## 2.0.2 - 1 April 2021 ### Changed diff --git a/README.md b/README.md index 27908ea92e..e23bafe647 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

Go API Reference - 2.0.2 release + 2.0.3 release Join us on Slack
Code Status diff --git a/cmd/fyne/commands/get.go b/cmd/fyne/commands/get.go index 449afe260b..e475ccf70c 100644 --- a/cmd/fyne/commands/get.go +++ b/cmd/fyne/commands/get.go @@ -1,6 +1,7 @@ package commands import ( + "flag" "fmt" "os" "os/exec" @@ -33,6 +34,7 @@ func (g *Getter) Get(pkg string) error { // AddFlags adds available flags to the current flags parser func (g *Getter) AddFlags() { + flag.StringVar(&g.icon, "icon", "Icon.png", "The name of the application icon file") } // PrintHelp prints help for this command when used in a command-line context diff --git a/cmd/fyne/commands/package-windows.go b/cmd/fyne/commands/package-windows.go index 82aba0ca7f..f309e9f41b 100644 --- a/cmd/fyne/commands/package-windows.go +++ b/cmd/fyne/commands/package-windows.go @@ -89,6 +89,7 @@ func (p *packager) packageWindows() error { if err != nil { return errors.Wrap(err, "Failed to write .syso file") } + defer os.Remove(outPath) err = os.Remove(icoPath) if err != nil { @@ -106,7 +107,7 @@ func (p *packager) packageWindows() error { } if p.install { - err := runAsAdminWindows("copy", "\"\""+p.exe+"\"\"", "\"\""+filepath.Join(os.Getenv("ProgramFiles"), p.name)+"\"\"") + err := runAsAdminWindows("copy", "\"\""+p.exe+"\"\"", "\"\""+filepath.Join(p.dir, p.name)+"\"\"") if err != nil { return errors.Wrap(err, "Failed to run as administrator") } diff --git a/cmd/fyne/commands/package.go b/cmd/fyne/commands/package.go index 86556640fa..ce005583fb 100644 --- a/cmd/fyne/commands/package.go +++ b/cmd/fyne/commands/package.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "log" + "runtime" "strconv" "strings" @@ -42,7 +43,7 @@ func NewPackager() Command { func (p *packager) AddFlags() { flag.StringVar(&p.os, "os", "", "The operating system to target (android, android/arm, android/arm64, android/amd64, android/386, darwin, freebsd, ios, linux, netbsd, openbsd, windows)") - flag.StringVar(&p.exe, "executable", "", "The path to the executable, default is the current dir main binary") + flag.StringVar(&p.exe, "executable", "", "Specify an existing binary instead of building before package") flag.StringVar(&p.srcDir, "sourceDir", "", "The directory to package, if executable is not set") flag.StringVar(&p.name, "name", "", "The name of the application, default is the executable file name") flag.StringVar(&p.icon, "icon", "Icon.png", "The name of the application icon file") @@ -106,7 +107,9 @@ func (p *packager) doPackage() error { if !util.Exists(p.exe) { return fmt.Errorf("unable to build directory to expected executable, %s", p.exe) } - defer p.removeBuild() + if runtime.GOOS != "windows" { + defer p.removeBuild() + } } switch p.os { @@ -154,6 +157,10 @@ func (p *packager) validate() error { if p.exe == "" { p.exe = filepath.Join(p.srcDir, exeName) + + if util.Exists(p.exe) { // the exe was not specified, assume stale + p.removeBuild() + } } else if p.os == "ios" || p.os == "android" { _, _ = fmt.Fprint(os.Stderr, "Parameter -executable is ignored for mobile builds.\n") } diff --git a/cmd/fyne/commands/release.go b/cmd/fyne/commands/release.go index 39865edb25..077dd22cea 100644 --- a/cmd/fyne/commands/release.go +++ b/cmd/fyne/commands/release.go @@ -311,6 +311,9 @@ func (r *releaser) validate() error { if r.certificate == "" { r.certificate = "3rd Party Mac Developer Application" } + if r.profile == "" { + return errors.New("missing required -profile parameter for macOS release") + } if r.category == "" { return errors.New("missing required -category parameter for macOS release") } else if !isValidMacOSCategory(r.category) { diff --git a/cmd/fyne/internal/mobile/dex.go b/cmd/fyne/internal/mobile/dex.go index c8a8df6669..03d6968f78 100644 --- a/cmd/fyne/internal/mobile/dex.go +++ b/cmd/fyne/internal/mobile/dex.go @@ -6,191 +6,192 @@ package mobile -var dexStr = `ZGV4CjAzNQDm5OAXFST/M4oWO8uK7rX3ZYH75wE14SUoJgAAcAAAAHhWNBIAAAAAAAAAAF` + - `glAAC+AAAAcAAAAC4AAABoAwAAOwAAACAEAAATAAAA5AYAAGUAAAB8BwAABgAAAKQKAADE` + - `GgAAZAsAAO4VAADwFQAA9RUAAPoVAAACFgAAFhYAAC0WAAA9FgAATRYAAFMWAABqFgAAbR` + - `YAAHIWAAB4FgAAfRYAAIMWAACGFgAAihYAAI8WAACTFgAAmBYAAJ0WAAC7FgAA3BYAAPcW` + - `AAARFwAANBcAAFkXAAByFwAAhRcAAKEXAAC2FwAAzBcAAOUXAAABGAAAFRgAAEoYAABqGA` + - `AAlhgAAKsYAADSGAAA6RgAAAYZAAA1GQAAUBkAAHsZAACgGQAAwBkAAN8ZAADvGQAACRoA` + - `ACAaAAA/GgAAUxoAAGkaAAB9GgAAkRoAAKgaAADNGgAA8hoAABcbAAA+GwAAYxsAAIYbAA` + - `CcGwAApxsAALAbAADKGwAA1RsAANgbAADcGwAA4RsAAOgbAADuGwAA8hsAAPcbAAD+GwAA` + - `ChwAAA8cAAASHAAAFhwAABscAAAwHAAANBwAAEAcAABMHAAAWBwAAGQcAABwHAAAfBwAAI` + - `kcAACWHAAAphwAALAcAADLHAAA4xwAAPUcAAALHQAAMh0AAFcdAACBHQAAox0AAMQdAADd` + - `HQAA8B0AAP4dAAAIHgAAFx4AACceAAA3HgAARx4AAFceAABaHgAAYh4AAIUeAACZHgAApx` + - `4AAKweAAC9HgAAzh4AANseAADpHgAA8h4AAAAfAAALHwAAFh8AACkfAAA2HwAASx8AAFQf` + - `AABfHwAAcR8AAI0fAACnHwAAwh8AANsfAADmHwAA8B8AAPsfAAALIAAAKSAAADsgAABDIA` + - `AAUSAAAGogAAB4IAAAhyAAAJcgAACmIAAArCAAALQgAAC6IAAAxyAAANsgAAAEIQAADyEA` + - `ABkhAAAfIQAAKSEAADshAABFIQAAVSEAAGQhAABuIQAAfCEAAIEhAACQIQAAnyEAAK0hAA` + - `C+IQAAxyEAANAhAADfIQAA6yEAAPkhAAAHIgAAFSIAACQiAAArIgAAQyIAAFAiAABYIgAA` + - `YCIAAGoiAABvIgAAkyIAAKEiAACzIgAAuiIAAMEiAAAKAAAAFQAAABYAAAAXAAAAGAAAAB` + - `kAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAjAAAAJAAAACUAAAAm` + - `AAAAJwAAACgAAAApAAAAKgAAACsAAAAsAAAALQAAAC4AAAAvAAAAMAAAADEAAAAyAAAAMw` + - `AAADQAAAA1AAAANgAAADcAAAA4AAAAOQAAADoAAAA7AAAAPAAAAD0AAAA+AAAARAAAAE4A` + - `AABRAAAACgAAAAAAAAAAAAAACwAAAAAAAADkFAAADAAAAAAAAADsFAAADQAAAAAAAAD4FA` + - `AADgAAAAAAAAAAFQAADwAAAAIAAAAAAAAADwAAAAQAAAAAAAAAEAAAAAQAAAAMFQAAFAAA` + - `AAQAAAAUFQAAEgAAAAQAAAAcFQAAFAAAAAQAAAAkFQAAEwAAAAUAAAAsFQAADwAAAAYAAA` + - `AAAAAADwAAAAgAAAAAAAAADwAAAAsAAAAAAAAAEAAAABAAAAAMFQAADwAAABIAAAAAAAAA` + - `EAAAABIAAAAMFQAADwAAABQAAAAAAAAADwAAABUAAAAAAAAAEgAAABcAAAA0FQAAFAAAAB` + - `cAAAA8FQAADwAAABwAAAAAAAAAEQAAAB0AAADkFAAAEgAAACAAAAAcFQAADwAAACIAAAAA` + - `AAAAEgAAACIAAAAcFQAAEgAAACIAAAA0FQAAFAAAACIAAABEFQAADwAAACoAAAAAAAAARA` + - `AAACsAAAAAAAAARQAAACsAAAAMFQAARgAAACsAAADkFAAARwAAACsAAABMFQAASAAAACsA` + - `AABYFQAASQAAACsAAABkFQAASgAAACsAAABsFQAASQAAACsAAAB0FQAASQAAACsAAAB8FQ` + - `AASQAAACsAAACEFQAASQAAACsAAADcFAAASQAAACsAAADUFAAATAAAACsAAACMFQAATQAA` + - `ACsAAACkFQAASQAAACsAAACsFQAASQAAACsAAAC0FQAASwAAACsAAAC8FQAASQAAACsAAA` + - `DMFAAASQAAACsAAAAcFQAASQAAACsAAADIFQAASQAAACsAAAA0FQAASgAAACsAAADQFQAA` + - `TQAAACsAAABEFQAATgAAACwAAAAAAAAAUAAAACwAAADYFQAAUAAAACwAAADgFQAATwAAAC` + - `wAAAC0FQAATwAAACwAAADoFQAAEgAAAC0AAAAcFQAABQAKAJsAAAAHAAAAlAAAAAcAAAC3` + - `AAAACQAAAEEAAAAlACoAtAAAACUAAAC6AAAAJgAqALQAAAAnACoAtAAAACgAKQC1AAAAKQ` + - `AqALQAAAAqAAAABAAAACoAAAAFAAAAKgAAAAYAAAAqAAAABwAAACoAAAA/AAAAKgAAAEIA` + - `AAAqACoAjAAAACoAFwCaAAAAKgAiAJ0AAAABAB4AAwAAAAEAJgCfAAAABAAwAAMAAAAEAA` + - `kAWgAAAAQABwBcAAAABAAIAGoAAAAEAAUAeAAAAAQADQB5AAAABAAKAKIAAAAEAAkAqgAA` + - `AAYACwB2AAAABwAeAAMAAAAHAAAAjQAAAAcAAAC8AAAACAAZALYAAAAKABoAgQAAAA4AAw` + - `BvAAAADgAEAG8AAAAQAAEAdAAAABAADwCWAAAAEgApAF0AAAASAAAAewAAABIAEAB+AAAA` + - `EgATAH8AAAASAAAAiAAAABIADgCKAAAAEgAlAIsAAAAUABAAegAAABUAAACDAAAAFQAAAI` + - `QAAAAVAAAAhQAAABUAAACGAAAAFgA2AI8AAAAWADcAsAAAABcAIwADAAAAFwAoAF4AAAAX` + - `AB4AaAAAABcANQCjAAAAFwAfAKYAAAAXAB8ApwAAABcALACoAAAAFwAtAKkAAAAXAB8Aqw` + - `AAABgAIAADAAAAHAAZAHUAAAAdAAAAlQAAAB0AFwCzAAAAHQAZALYAAAAgAB4AAwAAACIA` + - `OABpAAAAIgA5AHAAAAAiAAAAlQAAACIAOgCxAAAAIwAwAJcAAAAlADMAAwAAACUAHgCkAA` + - `AAJgAyAAMAAAAmAB4ApAAAACcAMgADAAAAJwAqAKAAAAAoADEAAwAAACgAJwBfAAAAKAAu` + - `AGcAAAAoAC4AoQAAACkAMgADAAAAKQAeAKQAAAAqAB4AAwAAACoAFABTAAAAKgAVAFQAAA` + - `AqABsAVQAAACoAHABWAAAAKgAdAFcAAAAqADQAWAAAACoAKwBbAAAAKgAeAGsAAAAqADAA` + - `bAAAACoAMABtAAAAKgAfAG4AAAAqADAAcgAAACoAEQBzAAAAKgAWAHcAAAAqAAYAfAAAAC` + - `oADAB9AAAAKgACAIAAAAAqABgAggAAACoAGQCHAAAAKgASAIkAAAAqAB4AjgAAACoAIQCR` + - `AAAAKgAeAJIAAAAqADAAkwAAACoAHgCWAAAAKgAiAJ4AAAAqACYAnwAAACoALwClAAAAKg` + - `AeAKwAAAAqADAArQAAACoAMACuAAAAKgAfAK8AAAAqACQAsgAAACoAHgC5AAAAJQAAAAAA` + - `AAAgAAAAzBQAAAkAAAB0FAAAcSQAAAAAAAAmAAAAAAAAACAAAADMFAAACQAAAIwUAACFJA` + - `AAAAAAACcAAAAAAAAAIAAAANQUAAAJAAAAnBQAAJYkAAAAAAAAKAAAAAAAAAAgAAAA3BQA` + - `AAkAAACsFAAApyQAAAAAAAApAAAAAAAAACAAAADMFAAACQAAALwUAADAJAAAAAAAACoAAA` + - `ABAAAAAQAAAAAAAAAJAAAAAAAAANEkAABiJAAAAgAAAC0kAAA0JAAAAQAAAD0kAAACAAAA` + - `RiQAADQkAAACAAAATSQAADQkAAACAAAAVCQAADQkAAACAAAAWyQAADQkAAADAAMAAQAAAM` + - `QiAAAIAAAAWwEEAFkCBQBwEDAAAAAOAAYAAQADAAAAyyIAAHgAAAAVAQBAEmISBBQAkAAI` + - `AFJTBQArA2UAAAAaAggAGgO4AHEgEAAyAFRSBABxEEMAAgAMAm4gJgASAFRRBABxEEMAAQ` + - `AMAW4gJwABAFRQBAAaAQAAcSBGABAAVFAEAHEQQwAAAAwAGgEAAG4gKQAQAFRQBABxEEMA` + - `AAAMAG4gKgBAAFRQBABxEEMAAAAMAG4QJAAAAFRQBABxEEMAAAAMAG4QJQAAAFRQBAAaAZ` + - `AAbiBUABAADAAfABYAVFEEAHEQQwABAAwBbjAhABAEDgABISisFACSAAgAASEopwAAAAED` + - `AAAAAAAKAAAAXQAAAF8AAAACAAIAAQAAAOYiAAAGAAAAWwEGAHAQMAAAAA4AAwABAAIAAA` + - `DsIgAADAAAAFQgBgBxEEMAAAAMABMBCABuICoAEAAOAAIAAgABAAAA8iIAAAYAAABbAQcA` + - `cBAwAAAADgALAAoAAQAAAPkiAAAGAAAAVBAHAG4QZAAAAA4AAgACAAEAAAAJIwAABgAAAF` + - `sBCABwEDAAAAAOAAIAAgAAAAAAECMAAAEAAAAOAAAABQAFAAAAAAAXIwAAAQAAAA4AAAAI` + - `AAUAAwAAACEjAABQAAAAchAtAAQACgBUMQgAVBEJAHEQRQABAAwBbhAzAAEACgE3EC0AVD` + - `AIAFQACQBUMQgAVBEJAHEQRQABAAwBbhAzAAEACgFyEC0ABAAKAnIwLgAUAgwBchAvAAEA` + - `DAFxIEgAEABUMAgAVAAJAHIQLwAEAAwBcSBGABAADgByEC0ABAAKAFQxCABUEQkAcRBFAA` + - `EADAFuEDMAAQAKATUQ5P8o4gIAAgABAAAANSMAAAYAAABbAQkAcBAwAAAADgAFAAEAAwAA` + - `ADwjAABOAAAAEuNUQAkAIgEXAHEARwAAAAwCcCAiACEAcSBEABAAVEAJAHEQQwAAAAwAEw` + - `EIAG4gKgAQAFRACQBxEEMAAAAMABQBkAAIAG4gJwAQACIAGABwMCsAMANUQQkAcRBDAAEA` + - `DAFuICgAAQBUQQkAVEIJAHEQQwACAAwCbjBJACEAVEAJAHEQQwAAAAwAIgEoAHAgPABBAG` + - `4gIwAQAA4AAgABAAEAAABLIwAACgAAAHAQAAABABoAAABbEBIAaQEQAA4AAgABAAAAAABT` + - `IwAAAwAAAFQQEQARAAAAAgACAAAAAABZIwAAAwAAAFsBEQARAQAAAgABAAAAAABgIwAAAw` + - `AAAFQQEgARAAAAAgACAAAAAABmIwAAAwAAAFsBEgARAQAAAQAAAAAAAABtIwAAAwAAAGIA` + - `EAARAAAAAgACAAIAAAByIwAABAAAAHAgWgAQAA4ABwADAAMAAQB5IwAAGQAAABLwcRATAA` + - `QADAFuMBIAUQYKATkBAwAPAAEQKP4NARoCCAAaA3EAcTARADIBKPUNASjzAAABAAAABwAB` + - `AAECDxceDgAAAQAAAAEAAACKIwAABgAAAGIAEABuEEoAAAAOAAQAAQADAAEAkCMAADMAAA` + - `BuEFIAAwAMAG4QUQADAAwBbhAGAAEADAETAoAAbjAKABACDABUAQAAOQEKABoACAAaAZkA` + - `cSAQABAADgBUAAAAGgFgAG4gDwAQAAwAcRA1AAAAKPQNABoBCAAaApgAcTARACEAKOsAAA` + - `AAAAApAAEAAQEeKgIAAQACAAAAoSMAAAkAAAAiACkAcCBAABAAbiBeAAEADgAAAAIAAQAC` + - `AAAAqiMAAAYAAABiABAAbiBLABAADgACAAEAAgAAALIjAAAGAAAAYgAQAG4gTAAQAA4AAg` + - `ABAAIAAAC6IwAABgAAAGIAEABuIE0AEAAOAAQAAQADAAAAwSMAACQAAAAaAJAAbiBUAAMA` + - `DAAfABYAFAECAAIBbiBPABMADAFuEBYAAQAMAW4QGQABAAwBEgJuMCAAEAIiACYAcCA4AD` + - `AAbiBeAAMADgAGAAIAAwAAAMojAABXAAAAEhMiAAQAGgFiAHAgAgAQABoBZgBuIDIAUQAK` + - `ATgBHABgAQMAEwIVADQhFgAiAAQAGgFjAHAgAgAQAG4gBAAwABoBQABxIAUAEAAMAG4wYw` + - `AEAw4AGgG9AG4gMQAVAAoBOAEeAGABAwATAhMANCEYABoBAgBuIAkAEAAaAWUAGgJSAG4g` + - `NAAlAAwCbjAIABACGgFkAG4gAwAQACjTbiAJAFAAGgFkAG4gAwAQACjKAAAFAAIAAwAAAN` + - `4jAAA5AAAAIgAEABoBYQBwIAIAEAAaAb0AbiAxABQACgE4ASgAYAEDABMCEwA0ISIAGgEC` + - `AG4gCQAQABoBZQAaAlIAbiA0ACQADAJuMAgAEAIaAWQAbiADABAAGgFDAHEgBQAQAAwAEi` + - `FuMGMAAwEOAG4gCQBAACjtAAADAAIAAwAAAOwjAAAJAAAAIgAlAHAwNgAQAm4gXgABAA4A` + - `AAACAAEAAQAAAPUjAAAJAAAAbhBQAAEADABuECwAAAAMABEAAAAFAAQAAgAAAPojAAAcAA` + - `AAEhAyAgYAEiAyAgMADgAS8DIDCAAaAAAAcCBOAAEAKPduEAcABAAMAG4QDgAAAAwAcCBO` + - `AAEAKOsEAAIAAgAAAAwkAAAdAAAAcBBbAAIAbyABADIAcBBfAAIAFAACAAIBbiBPAAIADA` + - `BuEBYAAAAMACIBJwBwIDoAIQBuIBQAEAAOAAAABwABAAUAAQAYJAAAYAAAAG4QVgAGAAwA` + - `bhAbAAAADABuEBcAAAAMADkAAwAOAG4QHwAAAAoBbhAcAAAACgJuEB0AAAAKA24QHgAAAA` + - `oAcFBYABYyKOwNACIABwBwEAsAAABuEFYABgAMAW4QGwABAAwBbiAaAAEAFAECAAIBbiBP` + - `ABYADAFuEBYAAQAMAVICAgBuEBUAAQAKA24QDAAAAAoEsUNSBAIAsUNSBAEAbhAYAAEACg` + - `FuEA0AAAAKBbFRUgABAJEAAQBwUFgAJkMorwAAAAAiAAEAAQEfI2QLAAAAAAAAAQAAAAAA` + - `AAA2AAAAcAsAAHgLAAAAAAAAAAAAAAAAAACECwAAAAAAAAAAAAAAAAAAkAsAAAAAAAAAAA` + - `AAAAAAAJwLAAAAAAAAAAAAAAAAAAABAAAAIQAAAAEAAAARAAAAAQAAAA0AAAACAAAAAAAA` + - `AAMAAAAAAAAAAAAAAAIAAAAiACIAAwAAACIAIgAkAAAAAQAAAAAAAAACAAAABAAdAAEAAA` + - `AiAAAAAgAAACIALQACAAAAAgAAAAEAAAAqAAAAAgAAACoAFwACAAAAKgAiAAQAAAAAAAAA` + - `AAAAAAMAAAAAAAAABAAAAAEAAAADAAAAAgAAAAQAAAABAAAABwAAAAEAAAAKAAAAAQAAAA` + - `wAAAAJAAAAEgAAAAAAAAAAAAAAAAAAAAAAAAACAAAAEgATAAEAAAATAAAAAQAAAB0AAAAE` + - `AAAAHQAAAAAAAAABAAAAKQAAAAIAAAAqAAAAAgAAAAsAAAACAAAAEgAAAAEAAAAgAAAAAy` + - `gpVgADKi8qAAY8aW5pdD4AEkRFRkFVTFRfSU5QVVRfVFlQRQAVREVGQVVMVF9LRVlCT0FS` + - `RF9DT0RFAA5GSUxFX09QRU5fQ09ERQAORklMRV9TQVZFX0NPREUABEZ5bmUAFUdvTmF0aX` + - `ZlQWN0aXZpdHkuamF2YQABSQADSUlJAARJSUlJAANJTEwABElMTEwAAUwAAkxJAANMSUkA` + - `AkxMAANMTEkAA0xMTAAcTGFuZHJvaWQvYXBwL05hdGl2ZUFjdGl2aXR5OwAfTGFuZHJvaW` + - `QvY29udGVudC9Db21wb25lbnROYW1lOwAZTGFuZHJvaWQvY29udGVudC9Db250ZXh0OwAY` + - `TGFuZHJvaWQvY29udGVudC9JbnRlbnQ7ACFMYW5kcm9pZC9jb250ZW50L3BtL0FjdGl2aX` + - `R5SW5mbzsAI0xhbmRyb2lkL2NvbnRlbnQvcG0vUGFja2FnZU1hbmFnZXI7ABdMYW5kcm9p` + - `ZC9ncmFwaGljcy9SZWN0OwARTGFuZHJvaWQvbmV0L1VyaTsAGkxhbmRyb2lkL29zL0J1aW` + - `xkJFZFUlNJT047ABNMYW5kcm9pZC9vcy9CdW5kbGU7ABRMYW5kcm9pZC9vcy9JQmluZGVy` + - `OwAXTGFuZHJvaWQvdGV4dC9FZGl0YWJsZTsAGkxhbmRyb2lkL3RleHQvVGV4dFdhdGNoZX` + - `I7ABJMYW5kcm9pZC91dGlsL0xvZzsAM0xhbmRyb2lkL3ZpZXcvS2V5Q2hhcmFjdGVyTWFw` + - `JFVuYXZhaWxhYmxlRXhjZXB0aW9uOwAeTGFuZHJvaWQvdmlldy9LZXlDaGFyYWN0ZXJNYX` + - `A7ACpMYW5kcm9pZC92aWV3L1ZpZXckT25MYXlvdXRDaGFuZ2VMaXN0ZW5lcjsAE0xhbmRy` + - `b2lkL3ZpZXcvVmlldzsAJUxhbmRyb2lkL3ZpZXcvVmlld0dyb3VwJExheW91dFBhcmFtcz` + - `sAFUxhbmRyb2lkL3ZpZXcvV2luZG93OwAbTGFuZHJvaWQvdmlldy9XaW5kb3dJbnNldHM7` + - `AC1MYW5kcm9pZC92aWV3L2lucHV0bWV0aG9kL0lucHV0TWV0aG9kTWFuYWdlcjsAGUxhbm` + - `Ryb2lkL3dpZGdldC9FZGl0VGV4dDsAKUxhbmRyb2lkL3dpZGdldC9GcmFtZUxheW91dCRM` + - `YXlvdXRQYXJhbXM7ACNMZGFsdmlrL2Fubm90YXRpb24vRW5jbG9zaW5nTWV0aG9kOwAeTG` + - `RhbHZpay9hbm5vdGF0aW9uL0lubmVyQ2xhc3M7AB1MZGFsdmlrL2Fubm90YXRpb24vU2ln` + - `bmF0dXJlOwAOTGphdmEvaW8vRmlsZTsAGExqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOwAVTG` + - `phdmEvbGFuZy9FeGNlcHRpb247AB1MamF2YS9sYW5nL05vU3VjaE1ldGhvZEVycm9yOwAS` + - `TGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy` + - `9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3RlbTsAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwAj` + - `TG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHkkMTsAI0xvcmcvZ29sYW5nL2FwcC` + - `9Hb05hdGl2ZUFjdGl2aXR5JDI7ACNMb3JnL2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdml0` + - `eSQzOwAlTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHkkNCQxOwAjTG9yZy9nb2` + - `xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHkkNDsAIUxvcmcvZ29sYW5nL2FwcC9Hb05hdGl2` + - `ZUFjdGl2aXR5OwAUTlVNQkVSX0tFWUJPQVJEX0NPREUACU9wZW4gRmlsZQAHU0RLX0lOVA` + - `AYU0lOR0xFTElORV9LRVlCT0FSRF9DT0RFAAlTYXZlIEZpbGUAAVYAAlZJAANWSUkABVZJ` + - `SUlJAARWSUlMAAJWTAADVkxJAAVWTElJSQAKVkxJSUlJSUlJSQADVkxMAAFaAAJaTAADWk` + - `xJABNbTGphdmEvbGFuZy9TdHJpbmc7AAJcfAAKYWNjZXNzJDAwMAAKYWNjZXNzJDAwMgAK` + - `YWNjZXNzJDEwMAAKYWNjZXNzJDEwMgAKYWNjZXNzJDIwMAAKYWNjZXNzJDMwMAALYWNjZX` + - `NzRmxhZ3MAC2FkZENhdGVnb3J5AA5hZGRDb250ZW50VmlldwAIYWRkRmxhZ3MAGWFkZE9u` + - `TGF5b3V0Q2hhbmdlTGlzdGVuZXIAFmFkZFRleHRDaGFuZ2VkTGlzdGVuZXIAEGFmdGVyVG` + - `V4dENoYW5nZWQAFGFuZHJvaWQuYXBwLmxpYl9uYW1lACVhbmRyb2lkLmludGVudC5hY3Rp` + - `b24uQ1JFQVRFX0RPQ1VNRU5UACNhbmRyb2lkLmludGVudC5hY3Rpb24uT1BFTl9ET0NVTU` + - `VOVAAoYW5kcm9pZC5pbnRlbnQuYWN0aW9uLk9QRU5fRE9DVU1FTlRfVFJFRQAgYW5kcm9p` + - `ZC5pbnRlbnQuY2F0ZWdvcnkuT1BFTkFCTEUAH2FuZHJvaWQuaW50ZW50LmV4dHJhLk1JTU` + - `VfVFlQRVMAF2FwcGxpY2F0aW9uL3gtZGlyZWN0b3J5ABFiZWZvcmVUZXh0Q2hhbmdlZAAM` + - `YnJpbmdUb0Zyb250AAhjb250YWlucwANY3JlYXRlQ2hvb3NlcgAOZG9IaWRlS2V5Ym9hcm` + - `QADmRvU2hvd0ZpbGVPcGVuAA5kb1Nob3dGaWxlU2F2ZQAOZG9TaG93S2V5Ym9hcmQAAWUA` + - `BmVxdWFscwAhZXhjZXB0aW9uIHJlYWRpbmcgS2V5Q2hhcmFjdGVyTWFwABJmaWxlUGlja2` + - `VyUmV0dXJuZWQADGZpbmRWaWV3QnlJZAADZ2V0AA9nZXRBYnNvbHV0ZVBhdGgAD2dldEFj` + - `dGl2aXR5SW5mbwALZ2V0Q2FjaGVEaXIADGdldENvbXBvbmVudAAHZ2V0RGF0YQAMZ2V0RG` + - `Vjb3JWaWV3AAlnZXRIZWlnaHQACWdldEludGVudAARZ2V0UGFja2FnZU1hbmFnZXIAC2dl` + - `dFJvb3RWaWV3ABNnZXRSb290V2luZG93SW5zZXRzAAdnZXRSdW5lAAlnZXRTdHJpbmcAEG` + - `dldFN5c3RlbVNlcnZpY2UAGmdldFN5c3RlbVdpbmRvd0luc2V0Qm90dG9tABhnZXRTeXN0` + - `ZW1XaW5kb3dJbnNldExlZnQAGWdldFN5c3RlbVdpbmRvd0luc2V0UmlnaHQAF2dldFN5c3` + - `RlbVdpbmRvd0luc2V0VG9wAAlnZXRUbXBkaXIACGdldFdpZHRoAAlnZXRXaW5kb3cADmdl` + - `dFdpbmRvd1Rva2VuABxnZXRXaW5kb3dWaXNpYmxlRGlzcGxheUZyYW1lABBnb05hdGl2ZU` + - `FjdGl2aXR5AAZoZWlnaHQADGhpZGVLZXlib2FyZAAXaGlkZVNvZnRJbnB1dEZyb21XaW5k` + - `b3cADGlucHV0X21ldGhvZAANaW5zZXRzQ2hhbmdlZAAOa2V5Ym9hcmREZWxldGUADWtleW` + - `JvYXJkVHlwZWQABGxlZnQABmxlbmd0aAAEbG9hZAALbG9hZExpYnJhcnkAEmxvYWRMaWJy` + - `YXJ5IGZhaWxlZAAnbG9hZExpYnJhcnk6IG5vIG1hbmlmZXN0IG1ldGFkYXRhIGZvdW5kAA` + - `ltVGV4dEVkaXQACG1ldGFEYXRhAARuYW1lAAhvbGRTdGF0ZQAQb25BY3Rpdml0eVJlc3Vs` + - `dAAIb25DcmVhdGUADm9uTGF5b3V0Q2hhbmdlAA1vblRleHRDaGFuZ2VkAAhwdXRFeHRyYQ` + - `AMcmVxdWVzdEZvY3VzAANydW4ADXJ1bk9uVWlUaHJlYWQADXNldEltZU9wdGlvbnMADHNl` + - `dElucHV0VHlwZQAPc2V0TGF5b3V0UGFyYW1zAAdzZXRUZXh0AAdzZXRUeXBlAA1zZXRWaX` + - `NpYmlsaXR5AApzZXR1cEVudHJ5AAxzaG93RmlsZU9wZW4ADHNob3dGaWxlU2F2ZQAMc2hv` + - `d0tleWJvYXJkAA1zaG93U29mdElucHV0AAVzcGxpdAAWc3RhcnRBY3Rpdml0eUZvclJlc3` + - `VsdAALc3ViU2VxdWVuY2UABnRoaXMkMAAGdGhpcyQxAAh0b1N0cmluZwADdG9wACJ1bmtu` + - `b3duIGtleWJvYXJkIHR5cGUsIHVzZSBkZWZhdWx0AAx1cGRhdGVMYXlvdXQAEHZhbCRrZX` + - `lib2FyZFR5cGUABXZhbHVlAAV3aWR0aAABfABPAgAABw4AUgAHdxACDll5lpd4tJaWl6WW` + - `Am0sIEsCdB0AfAEABw4AfwAHDrQA1AEBAAcOANcBCQAAAAAAAAAAAAcOWgDpAQEABw4A+w` + - `EBAAcOAPcBBAAAAAAHDgDsAQQAAAAABw4BEg8BHxO0AnsdAN0BAQAHDgDgAQAHHeG0xFuW` + - `tQIU4AAvAAcOOE4tABoBAAcOABoCAAAHDgAaAQAHDgAaAgAABw4AGgAHDgAaAgAABw4AqQ` + - `EDAAAABx2HNAJ7LCAegwB0AAcOWgDAAQAHDkujTEt/Ansdh0seAN0BAAcOAiKGAIUBAQAH` + - `DloAmQEBAAcOWgBLAQAHDloAeAAHDoe0iIwAiQEBAAcdeOF4RJYCdx3hWrRqPACdAQEABw` + - `544Vq3WqUZAE8BAAcOAiKGADQABw4AhAIDAAAABw4CDGgCeR08bEsAzwEBAAcOPDw9tIwA` + - `OQAHDsMCDiwCdh2HhUweWrW0/9AAAhkBuwEaTQIaAlkEAJwBHgIbAbsBHAEXAQIZAbsBGk` + - `oCGQG7ARpdAhkBuwEaQQIZAbsBGl8GRJAACAQABAEEAgQCBAEAAgEBBJAgAZAgNoCABKgX` + - `NwHIFwABAQEGkCA4gIAEyBk5AeQZAAEBAQeQIDqAgASMGjsBqBoAAQEDCJAgPICABMQaPQ` + - `HgGgEB9BoBAYgbAAEBAQmQIECAgAS4HEEB1BwHAhIIChoBGgEaARoBGgEaAQoRAgECQoGA` + - `BIAeAYggpB4BiCC8HgGIINQeAYgg7B4BiCCEHwGIIJwfBoICAAUItB8ECIggAYICAAGCAg` + - `ABggIAAQKkIAQCqCEBCMwhAQjoIQEIhCJKAKAiAQD4IgEAuCQBALwlCADgJQcEhCYBAcwm` + - `BwCYJwARAAAAAAAAAAEAAAAAAAAAAQAAAL4AAABwAAAAAgAAAC4AAABoAwAAAwAAADsAAA` + - `AgBAAABAAAABMAAADkBgAABQAAAGUAAAB8BwAABgAAAAYAAACkCgAAAxAAAAYAAABkCwAA` + - `ASAAACIAAACoCwAABiAAAAUAAAB0FAAAARAAACAAAADMFAAAAiAAAL4AAADuFQAAAyAAAC` + - `IAAADEIgAABCAAAAcAAAAtJAAABSAAAAEAAABiJAAAACAAAAYAAABxJAAAABAAAAEAAABY` + - `JQAA` + +var dexStr = `ZGV4CjAzNQAM8J6VgTepd211FPPtsu4FeyftNcBZIIp0JgAAcAAAAHhWNBIAAAAAAAAAAK` + + `QlAAC/AAAAcAAAAC4AAABsAwAAPQAAACQEAAATAAAAAAcAAGYAAACYBwAABgAAAMgKAADs` + + `GgAAiAsAABoWAAAcFgAAIRYAACYWAAAuFgAAQhYAAFkWAABpFgAAeRYAAH8WAACWFgAAmR` + + `YAAJ4WAACkFgAAqRYAAK8WAACyFgAAthYAALsWAAC/FgAAxBYAAMkWAADnFgAACBcAACMX` + + `AAA9FwAAYBcAAIUXAACeFwAAsRcAAM0XAADiFwAA+BcAABEYAAAtGAAAQRgAAHYYAACWGA` + + `AAwhgAANcYAAD+GAAAFRkAADIZAABhGQAAfBkAAKcZAADMGQAA7BkAAAsaAAAbGgAANRoA` + + `AEwaAABrGgAAfxoAAJUaAACpGgAAvRoAANQaAAD5GgAAHhsAAEMbAABqGwAAjxsAALIbAA` + + `DIGwAA0xsAANwbAAD2GwAAARwAAAQcAAAIHAAADRwAABQcAAAaHAAAHhwAACMcAAAqHAAA` + + `NhwAADscAAA+HAAAQhwAAEccAABcHAAAYBwAAGwcAAB4HAAAhBwAAJAcAACcHAAAqBwAAL` + + `UcAADCHAAA0hwAANwcAAD3HAAADx0AACEdAAA3HQAAXh0AAIMdAACtHQAAzx0AAPAdAAAM` + + `HgAAJR4AADgeAABGHgAAUB4AAF8eAABvHgAAfx4AAI8eAACfHgAAoh4AAKoeAADNHgAA4R` + + `4AAO8eAAD0HgAABR8AABYfAAAjHwAAMR8AADofAABIHwAAUx8AAF4fAABxHwAAfh8AAJMf` + + `AACcHwAApx8AALkfAADVHwAA7x8AAAogAAAjIAAALiAAADggAABDIAAAUyAAAHEgAACDIA` + + `AAiyAAAJkgAACyIAAAwCAAAM8gAADfIAAA7iAAAPQgAAD8IAAAAiEAAA8hAAAjIQAATCEA` + + `AFchAABhIQAAZyEAAHEhAACDIQAAjSEAAJ0hAACsIQAAtiEAAMQhAADJIQAA2CEAAOchAA` + + `D1IQAABiIAAA8iAAAYIgAAJyIAADMiAABBIgAATyIAAF0iAABsIgAAcyIAAIsiAACYIgAA` + + `oCIAAKgiAACyIgAAtyIAANsiAADpIgAA+yIAAAIjAAAJIwAACgAAABUAAAAWAAAAFwAAAB` + + `gAAAAZAAAAGgAAABsAAAAcAAAAHQAAAB4AAAAfAAAAIAAAACEAAAAiAAAAIwAAACQAAAAl` + + `AAAAJgAAACcAAAAoAAAAKQAAACoAAAArAAAALAAAAC0AAAAuAAAALwAAADAAAAAxAAAAMg` + + `AAADMAAAA0AAAANQAAADYAAAA3AAAAOAAAADkAAAA6AAAAOwAAADwAAAA9AAAAPgAAAEQA` + + `AABOAAAAUQAAAAoAAAAAAAAAAAAAAAsAAAAAAAAAEBUAAAwAAAAAAAAAGBUAAA0AAAAAAA` + + `AAJBUAAA4AAAAAAAAALBUAAA8AAAACAAAAAAAAAA8AAAAEAAAAAAAAABAAAAAEAAAAOBUA` + + `ABQAAAAEAAAAQBUAABIAAAAEAAAASBUAABQAAAAEAAAAJBUAABQAAAAEAAAAUBUAABMAAA` + + `AFAAAAWBUAAA8AAAAGAAAAAAAAAA8AAAAIAAAAAAAAAA8AAAALAAAAAAAAABAAAAAQAAAA` + + `OBUAAA8AAAASAAAAAAAAABAAAAASAAAAOBUAAA8AAAAUAAAAAAAAAA8AAAAVAAAAAAAAAB` + + `IAAAAXAAAAYBUAABQAAAAXAAAAaBUAAA8AAAAcAAAAAAAAABEAAAAdAAAAEBUAABIAAAAg` + + `AAAASBUAAA8AAAAiAAAAAAAAABIAAAAiAAAASBUAABIAAAAiAAAAYBUAABQAAAAiAAAAcB` + + `UAAA8AAAAqAAAAAAAAAEQAAAArAAAAAAAAAEUAAAArAAAAOBUAAEYAAAArAAAAEBUAAEcA` + + `AAArAAAAeBUAAEgAAAArAAAAhBUAAEkAAAArAAAAkBUAAEoAAAArAAAAmBUAAEkAAAArAA` + + `AAoBUAAEkAAAArAAAAqBUAAEkAAAArAAAAsBUAAEkAAAArAAAACBUAAEkAAAArAAAAABUA` + + `AEwAAAArAAAAuBUAAE0AAAArAAAA0BUAAEkAAAArAAAA2BUAAEkAAAArAAAA4BUAAEsAAA` + + `ArAAAA6BUAAEkAAAArAAAA+BQAAEkAAAArAAAASBUAAE0AAAArAAAAJBUAAEkAAAArAAAA` + + `9BUAAEkAAAArAAAAYBUAAEoAAAArAAAA/BUAAE0AAAArAAAAcBUAAE4AAAAsAAAAAAAAAF` + + `AAAAAsAAAABBYAAFAAAAAsAAAADBYAAE8AAAAsAAAA4BUAAE8AAAAsAAAAFBYAABIAAAAt` + + `AAAASBUAAAUACgCcAAAABwAAAJUAAAAHAAAAuAAAAAkAAABBAAAAJQAqALUAAAAlAAAAuw` + + `AAACYAKgC1AAAAJwAqALUAAAAoACkAtgAAACkAKgC1AAAAKgAAAAQAAAAqAAAABQAAACoA` + + `AAAGAAAAKgAAAAcAAAAqAAAAPwAAACoAAABCAAAAKgAqAI0AAAAqABcAmwAAACoAIgCeAA` + + `AAAQAfAAMAAAABACcAoAAAAAQAMQADAAAABAAJAFoAAAAEAAcAXAAAAAQACABrAAAABAAF` + + `AHkAAAAEAA4AegAAAAQACgCjAAAABAALAKMAAAAEAAkAqwAAAAYADAB3AAAABwAfAAMAAA` + + `AHAAAAjgAAAAcAAAC9AAAACAAaALcAAAAKABsAggAAAA4AAwBwAAAADgAEAHAAAAAQAAEA` + + `dQAAABAAEACXAAAAEgAqAF0AAAASAAAAfAAAABIAEQB/AAAAEgAUAIAAAAASAAAAiQAAAB` + + `IADwCLAAAAEgAmAIwAAAAUABEAewAAABUAAACEAAAAFQAAAIUAAAAVAAAAhgAAABUAAACH` + + `AAAAFgA4AJAAAAAWADkAsQAAABcAJAADAAAAFwApAF4AAAAXAB8AaQAAABcANwCkAAAAFw` + + `AgAKcAAAAXACAAqAAAABcALQCpAAAAFwAuAKoAAAAXACAArAAAABgAIQADAAAAHAAaAHYA` + + `AAAdAAAAlgAAAB0AGAC0AAAAHQAaALcAAAAgAB8AAwAAACIAOgBqAAAAIgA7AHEAAAAiAA` + + `AAlgAAACIAPACyAAAAIwAxAJgAAAAlADUAAwAAACUAHwClAAAAJgA0AAMAAAAmAB8ApQAA` + + `ACcANAADAAAAJwArAKEAAAAoADMAAwAAACgAKABfAAAAKAAvAGgAAAAoAC8AogAAACkANA` + + `ADAAAAKQAfAKUAAAAqAB8AAwAAACoAFQBTAAAAKgAWAFQAAAAqABwAVQAAACoAHQBWAAAA` + + `KgAeAFcAAAAqADYAWAAAACoALABbAAAAKgAfAGwAAAAqADEAbQAAACoAMgBuAAAAKgAgAG` + + `8AAAAqADEAcwAAACoAEgB0AAAAKgAXAHgAAAAqAAYAfQAAACoADQB+AAAAKgACAIEAAAAq` + + `ABkAgwAAACoAGgCIAAAAKgATAIoAAAAqAB8AjwAAACoAIgCSAAAAKgAfAJMAAAAqADEAlA` + + `AAACoAHwCXAAAAKgAjAJ8AAAAqACcAoAAAACoAMACmAAAAKgAfAK0AAAAqADEArgAAACoA` + + `MgCvAAAAKgAgALAAAAAqACUAswAAACoAHwC6AAAAJQAAAAAAAAAgAAAA+BQAAAkAAACgFA` + + `AAviQAAAAAAAAmAAAAAAAAACAAAAD4FAAACQAAALgUAADSJAAAAAAAACcAAAAAAAAAIAAA` + + `AAAVAAAJAAAAyBQAAOMkAAAAAAAAKAAAAAAAAAAgAAAACBUAAAkAAADYFAAA9CQAAAAAAA` + + `ApAAAAAAAAACAAAAD4FAAACQAAAOgUAAANJQAAAAAAACoAAAABAAAAAQAAAAAAAAAJAAAA` + + `AAAAAB4lAACvJAAAAgAAAHokAACBJAAAAQAAAIokAAACAAAAkyQAAIEkAAACAAAAmiQAAI` + + `EkAAACAAAAoSQAAIEkAAACAAAAqCQAAIEkAAADAAMAAQAAAAwjAAAIAAAAWwEEAFkCBQBw` + + `EDEAAAAOAAYAAQADAAAAEyMAAHgAAAAVAQBAEmISBBQAkAAIAFJTBQArA2UAAAAaAggAGg` + + `O5AHEgEQAyAFRSBABxEEQAAgAMAm4gJwASAFRRBABxEEQAAQAMAW4gKAABAFRQBAAaAQAA` + + `cSBHABAAVFAEAHEQRAAAAAwAGgEAAG4gKgAQAFRQBABxEEQAAAAMAG4gKwBAAFRQBABxEE` + + `QAAAAMAG4QJQAAAFRQBABxEEQAAAAMAG4QJgAAAFRQBAAaAZEAbiBVABAADAAfABYAVFEE` + + `AHEQRAABAAwBbjAiABAEDgABISisFACSAAgAASEopwAAAAEDAAAAAAAKAAAAXQAAAF8AAA` + + `ACAAIAAQAAAC4jAAAGAAAAWwEGAHAQMQAAAA4AAwABAAIAAAA0IwAADAAAAFQgBgBxEEQA` + + `AAAMABMBCABuICsAEAAOAAIAAgABAAAAOiMAAAYAAABbAQcAcBAxAAAADgALAAoAAQAAAE` + + `EjAAAGAAAAVBAHAG4QZQAAAA4AAgACAAEAAABRIwAABgAAAFsBCABwEDEAAAAOAAIAAgAA` + + `AAAAWCMAAAEAAAAOAAAABQAFAAAAAABfIwAAAQAAAA4AAAAIAAUAAwAAAGkjAABQAAAAch` + + `AuAAQACgBUMQgAVBEJAHEQRgABAAwBbhA0AAEACgE3EC0AVDAIAFQACQBUMQgAVBEJAHEQ` + + `RgABAAwBbhA0AAEACgFyEC4ABAAKAnIwLwAUAgwBchAwAAEADAFxIEkAEABUMAgAVAAJAH` + + `IQMAAEAAwBcSBHABAADgByEC4ABAAKAFQxCABUEQkAcRBGAAEADAFuEDQAAQAKATUQ5P8o` + + `4gIAAgABAAAAfSMAAAYAAABbAQkAcBAxAAAADgAFAAEAAwAAAIQjAABOAAAAEuNUQAkAIg` + + `EXAHEASAAAAAwCcCAjACEAcSBFABAAVEAJAHEQRAAAAAwAEwEIAG4gKwAQAFRACQBxEEQA` + + `AAAMABQBkAAIAG4gKAAQACIAGABwMCwAMANUQQkAcRBEAAEADAFuICkAAQBUQQkAVEIJAH` + + `EQRAACAAwCbjBKACEAVEAJAHEQRAAAAAwAIgEoAHAgPQBBAG4gJAAQAA4AAgABAAEAAACT` + + `IwAACgAAAHAQAAABABoAAABbEBIAaQEQAA4AAgABAAAAAACbIwAAAwAAAFQQEQARAAAAAg` + + `ACAAAAAAChIwAAAwAAAFsBEQARAQAAAgABAAAAAACoIwAAAwAAAFQQEgARAAAAAgACAAAA` + + `AACuIwAAAwAAAFsBEgARAQAAAQAAAAAAAAC1IwAAAwAAAGIAEAARAAAAAgACAAIAAAC6Iw` + + `AABAAAAHAgWwAQAA4ABwADAAMAAQDBIwAAGQAAABLwcRAUAAQADAFuMBMAUQYKATkBAwAP` + + `AAEQKP4NARoCCAAaA3IAcTASADIBKPUNASjzAAABAAAABwABAAECDxceDgAAAQAAAAEAAA` + + `DSIwAABgAAAGIAEABuEEsAAAAOAAQAAQADAAEA2CMAADMAAABuEFMAAwAMAG4QUgADAAwB` + + `bhAGAAEADAETAoAAbjALABACDABUAQAAOQEKABoACAAaAZoAcSARABAADgBUAAAAGgFgAG` + + `4gEAAQAAwAcRA2AAAAKPQNABoBCAAaApkAcTASACEAKOsAAAAAAAApAAEAAQEeKgIAAQAC` + + `AAAA6SMAAAkAAAAiACkAcCBBABAAbiBfAAEADgAAAAIAAQACAAAA8iMAAAYAAABiABAAbi` + + `BMABAADgADAAIAAwAAAPojAAAGAAAAYgAQAG4wTQAQAg4AAgABAAIAAAADJAAABgAAAGIA` + + `EABuIE4AEAAOAAQAAQADAAAACiQAACQAAAAaAJEAbiBVAAMADAAfABYAFAECAAIBbiBQAB` + + `MADAFuEBcAAQAMAW4QGgABAAwBEgJuMCEAEAIiACYAcCA5ADAAbiBfAAMADgAGAAIAAwAA` + + `ABMkAABXAAAAEhMiAAQAGgFiAHAgAgAQABoBZwBuIDMAUQAKATgBHABgAQMAEwIVADQhFg` + + `AiAAQAGgFjAHAgAgAQAG4gBAAwABoBQABxIAUAEAAMAG4wZAAEAw4AGgG+AG4gMgAVAAoB` + + `OAEeAGABAwATAhMANCEYABoBAgBuIAoAEAAaAWUAGgJSAG4gNQAlAAwCbjAJABACGgFkAG` + + `4gAwAQACjTbiAKAFAAGgFkAG4gAwAQACjKAAAGAAMAAwAAACckAAA+AAAAIgAEABoBYQBw` + + `IAIAEAAaAb4AbiAyABQACgE4AS0AYAEDABMCEwA0IScAGgECAG4gCgAQABoBZQAaAlIAbi` + + `A1ACQADAJuMAkAEAIaAWYAbjAIABAFGgFkAG4gAwAQABoBQwBxIAUAEAAMABIhbjBkAAMB` + + `DgBuIAoAQAAo6AMAAgADAAAAOSQAAAkAAAAiACUAcDA3ABACbiBfAAEADgAAAAIAAQABAA` + + `AAQiQAAAkAAABuEFEAAQAMAG4QLQAAAAwAEQAAAAUABAACAAAARyQAABwAAAASEDICBgAS` + + `IDICAwAOABLwMgMIABoAAABwIE8AAQAo924QBwAEAAwAbhAPAAAADABwIE8AAQAo6wQAAg` + + `ACAAAAWSQAAB0AAABwEFwAAgBvIAEAMgBwEGAAAgAUAAIAAgFuIFAAAgAMAG4QFwAAAAwA` + + `IgEnAHAgOwAhAG4gFQAQAA4AAAAHAAEABQABAGUkAABgAAAAbhBXAAYADABuEBwAAAAMAG` + + `4QGAAAAAwAOQADAA4AbhAgAAAACgFuEB0AAAAKAm4QHgAAAAoDbhAfAAAACgBwUFkAFjIo` + + `7A0AIgAHAHAQDAAAAG4QVwAGAAwBbhAcAAEADAFuIBsAAQAUAQIAAgFuIFAAFgAMAW4QFw` + + `ABAAwBUgICAG4QFgABAAoDbhANAAAACgSxQ1IEAgCxQ1IEAQBuEBkAAQAKAW4QDgAAAAoF` + + `sVFSAAEAkQABAHBQWQAmQyivAAAAACIAAQABAR8jiAsAAAAAAAABAAAAAAAAADcAAACUCw` + + `AAnAsAAAAAAAAAAAAAAAAAAKgLAAAAAAAAAAAAAAAAAAC0CwAAAAAAAAAAAAAAAAAAwAsA` + + `AAAAAAAAAAAAAAAAAAEAAAAhAAAAAQAAABEAAAABAAAADQAAAAIAAAAAAAAAAwAAAAAAAA` + + `AAAAAAAgAAACIAIgADAAAAIgAiACQAAAABAAAAAAAAAAIAAAAEAB0AAQAAACIAAAACAAAA` + + `IgAtAAIAAAACAAAAAQAAACoAAAACAAAAKgAXAAIAAAAqACIABAAAAAAAAAAAAAAAAwAAAA` + + `AAAAAEAAAAAQAAAAMAAAACAAAABAAAAAEAAAAHAAAAAQAAAAoAAAABAAAADAAAAAkAAAAS` + + `AAAAAAAAAAAAAAAAAAAAAAAAAAIAAAASABMAAQAAABMAAAABAAAAHQAAAAQAAAAdAAAAAA` + + `AAAAEAAAApAAAAAgAAACoAAAACAAAACwAAAAIAAAASAAAAAQAAACAAAAADKClWAAMqLyoA` + + `Bjxpbml0PgASREVGQVVMVF9JTlBVVF9UWVBFABVERUZBVUxUX0tFWUJPQVJEX0NPREUADk` + + `ZJTEVfT1BFTl9DT0RFAA5GSUxFX1NBVkVfQ09ERQAERnluZQAVR29OYXRpdmVBY3Rpdml0` + + `eS5qYXZhAAFJAANJSUkABElJSUkAA0lMTAAESUxMTAABTAACTEkAA0xJSQACTEwAA0xMSQ` + + `ADTExMABxMYW5kcm9pZC9hcHAvTmF0aXZlQWN0aXZpdHk7AB9MYW5kcm9pZC9jb250ZW50` + + `L0NvbXBvbmVudE5hbWU7ABlMYW5kcm9pZC9jb250ZW50L0NvbnRleHQ7ABhMYW5kcm9pZC` + + `9jb250ZW50L0ludGVudDsAIUxhbmRyb2lkL2NvbnRlbnQvcG0vQWN0aXZpdHlJbmZvOwAj` + + `TGFuZHJvaWQvY29udGVudC9wbS9QYWNrYWdlTWFuYWdlcjsAF0xhbmRyb2lkL2dyYXBoaW` + + `NzL1JlY3Q7ABFMYW5kcm9pZC9uZXQvVXJpOwAaTGFuZHJvaWQvb3MvQnVpbGQkVkVSU0lP` + + `TjsAE0xhbmRyb2lkL29zL0J1bmRsZTsAFExhbmRyb2lkL29zL0lCaW5kZXI7ABdMYW5kcm` + + `9pZC90ZXh0L0VkaXRhYmxlOwAaTGFuZHJvaWQvdGV4dC9UZXh0V2F0Y2hlcjsAEkxhbmRy` + + `b2lkL3V0aWwvTG9nOwAzTGFuZHJvaWQvdmlldy9LZXlDaGFyYWN0ZXJNYXAkVW5hdmFpbG` + + `FibGVFeGNlcHRpb247AB5MYW5kcm9pZC92aWV3L0tleUNoYXJhY3Rlck1hcDsAKkxhbmRy` + + `b2lkL3ZpZXcvVmlldyRPbkxheW91dENoYW5nZUxpc3RlbmVyOwATTGFuZHJvaWQvdmlldy` + + `9WaWV3OwAlTGFuZHJvaWQvdmlldy9WaWV3R3JvdXAkTGF5b3V0UGFyYW1zOwAVTGFuZHJv` + + `aWQvdmlldy9XaW5kb3c7ABtMYW5kcm9pZC92aWV3L1dpbmRvd0luc2V0czsALUxhbmRyb2` + + `lkL3ZpZXcvaW5wdXRtZXRob2QvSW5wdXRNZXRob2RNYW5hZ2VyOwAZTGFuZHJvaWQvd2lk` + + `Z2V0L0VkaXRUZXh0OwApTGFuZHJvaWQvd2lkZ2V0L0ZyYW1lTGF5b3V0JExheW91dFBhcm` + + `FtczsAI0xkYWx2aWsvYW5ub3RhdGlvbi9FbmNsb3NpbmdNZXRob2Q7AB5MZGFsdmlrL2Fu` + + `bm90YXRpb24vSW5uZXJDbGFzczsAHUxkYWx2aWsvYW5ub3RhdGlvbi9TaWduYXR1cmU7AA` + + `5MamF2YS9pby9GaWxlOwAYTGphdmEvbGFuZy9DaGFyU2VxdWVuY2U7ABVMamF2YS9sYW5n` + + `L0V4Y2VwdGlvbjsAHUxqYXZhL2xhbmcvTm9TdWNoTWV0aG9kRXJyb3I7ABJMamF2YS9sYW` + + `5nL09iamVjdDsAFExqYXZhL2xhbmcvUnVubmFibGU7ABJMamF2YS9sYW5nL1N0cmluZzsA` + + `EkxqYXZhL2xhbmcvU3lzdGVtOwAVTGphdmEvbGFuZy9UaHJvd2FibGU7ACNMb3JnL2dvbG` + + `FuZy9hcHAvR29OYXRpdmVBY3Rpdml0eSQxOwAjTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZl` + + `QWN0aXZpdHkkMjsAI0xvcmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2aXR5JDM7ACVMb3` + + `JnL2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdml0eSQ0JDE7ACNMb3JnL2dvbGFuZy9hcHAv` + + `R29OYXRpdmVBY3Rpdml0eSQ0OwAhTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdH` + + `k7ABROVU1CRVJfS0VZQk9BUkRfQ09ERQAJT3BlbiBGaWxlAAdTREtfSU5UABhTSU5HTEVM` + + `SU5FX0tFWUJPQVJEX0NPREUACVNhdmUgRmlsZQABVgACVkkAA1ZJSQAFVklJSUkABFZJSU` + + `wAAlZMAANWTEkABVZMSUlJAApWTElJSUlJSUlJAANWTEwAAVoAAlpMAANaTEkAE1tMamF2` + + `YS9sYW5nL1N0cmluZzsAAlx8AAphY2Nlc3MkMDAwAAphY2Nlc3MkMDAyAAphY2Nlc3MkMT` + + `AwAAphY2Nlc3MkMTAyAAphY2Nlc3MkMjAwAAphY2Nlc3MkMzAwAAthY2Nlc3NGbGFncwAL` + + `YWRkQ2F0ZWdvcnkADmFkZENvbnRlbnRWaWV3AAhhZGRGbGFncwAZYWRkT25MYXlvdXRDaG` + + `FuZ2VMaXN0ZW5lcgAWYWRkVGV4dENoYW5nZWRMaXN0ZW5lcgAQYWZ0ZXJUZXh0Q2hhbmdl` + + `ZAAUYW5kcm9pZC5hcHAubGliX25hbWUAJWFuZHJvaWQuaW50ZW50LmFjdGlvbi5DUkVBVE` + + `VfRE9DVU1FTlQAI2FuZHJvaWQuaW50ZW50LmFjdGlvbi5PUEVOX0RPQ1VNRU5UAChhbmRy` + + `b2lkLmludGVudC5hY3Rpb24uT1BFTl9ET0NVTUVOVF9UUkVFACBhbmRyb2lkLmludGVudC` + + `5jYXRlZ29yeS5PUEVOQUJMRQAfYW5kcm9pZC5pbnRlbnQuZXh0cmEuTUlNRV9UWVBFUwAa` + + `YW5kcm9pZC5pbnRlbnQuZXh0cmEuVElUTEUAF2FwcGxpY2F0aW9uL3gtZGlyZWN0b3J5AB` + + `FiZWZvcmVUZXh0Q2hhbmdlZAAMYnJpbmdUb0Zyb250AAhjb250YWlucwANY3JlYXRlQ2hv` + + `b3NlcgAOZG9IaWRlS2V5Ym9hcmQADmRvU2hvd0ZpbGVPcGVuAA5kb1Nob3dGaWxlU2F2ZQ` + + `AOZG9TaG93S2V5Ym9hcmQAAWUABmVxdWFscwAhZXhjZXB0aW9uIHJlYWRpbmcgS2V5Q2hh` + + `cmFjdGVyTWFwABJmaWxlUGlja2VyUmV0dXJuZWQADGZpbmRWaWV3QnlJZAADZ2V0AA9nZX` + + `RBYnNvbHV0ZVBhdGgAD2dldEFjdGl2aXR5SW5mbwALZ2V0Q2FjaGVEaXIADGdldENvbXBv` + + `bmVudAAHZ2V0RGF0YQAMZ2V0RGVjb3JWaWV3AAlnZXRIZWlnaHQACWdldEludGVudAARZ2` + + `V0UGFja2FnZU1hbmFnZXIAC2dldFJvb3RWaWV3ABNnZXRSb290V2luZG93SW5zZXRzAAdn` + + `ZXRSdW5lAAlnZXRTdHJpbmcAEGdldFN5c3RlbVNlcnZpY2UAGmdldFN5c3RlbVdpbmRvd0` + + `luc2V0Qm90dG9tABhnZXRTeXN0ZW1XaW5kb3dJbnNldExlZnQAGWdldFN5c3RlbVdpbmRv` + + `d0luc2V0UmlnaHQAF2dldFN5c3RlbVdpbmRvd0luc2V0VG9wAAlnZXRUbXBkaXIACGdldF` + + `dpZHRoAAlnZXRXaW5kb3cADmdldFdpbmRvd1Rva2VuABxnZXRXaW5kb3dWaXNpYmxlRGlz` + + `cGxheUZyYW1lABBnb05hdGl2ZUFjdGl2aXR5AAZoZWlnaHQADGhpZGVLZXlib2FyZAAXaG` + + `lkZVNvZnRJbnB1dEZyb21XaW5kb3cADGlucHV0X21ldGhvZAANaW5zZXRzQ2hhbmdlZAAO` + + `a2V5Ym9hcmREZWxldGUADWtleWJvYXJkVHlwZWQABGxlZnQABmxlbmd0aAAEbG9hZAALbG` + + `9hZExpYnJhcnkAEmxvYWRMaWJyYXJ5IGZhaWxlZAAnbG9hZExpYnJhcnk6IG5vIG1hbmlm` + + `ZXN0IG1ldGFkYXRhIGZvdW5kAAltVGV4dEVkaXQACG1ldGFEYXRhAARuYW1lAAhvbGRTdG` + + `F0ZQAQb25BY3Rpdml0eVJlc3VsdAAIb25DcmVhdGUADm9uTGF5b3V0Q2hhbmdlAA1vblRl` + + `eHRDaGFuZ2VkAAhwdXRFeHRyYQAMcmVxdWVzdEZvY3VzAANydW4ADXJ1bk9uVWlUaHJlYW` + + `QADXNldEltZU9wdGlvbnMADHNldElucHV0VHlwZQAPc2V0TGF5b3V0UGFyYW1zAAdzZXRU` + + `ZXh0AAdzZXRUeXBlAA1zZXRWaXNpYmlsaXR5AApzZXR1cEVudHJ5AAxzaG93RmlsZU9wZW` + + `4ADHNob3dGaWxlU2F2ZQAMc2hvd0tleWJvYXJkAA1zaG93U29mdElucHV0AAVzcGxpdAAW` + + `c3RhcnRBY3Rpdml0eUZvclJlc3VsdAALc3ViU2VxdWVuY2UABnRoaXMkMAAGdGhpcyQxAA` + + `h0b1N0cmluZwADdG9wACJ1bmtub3duIGtleWJvYXJkIHR5cGUsIHVzZSBkZWZhdWx0AAx1` + + `cGRhdGVMYXlvdXQAEHZhbCRrZXlib2FyZFR5cGUABXZhbHVlAAV3aWR0aAABfABPAgAABw` + + `4AUgAHdxACDll5lpd4tJaWl6WWAm0sIEsCdB0AfAEABw4AfwAHDrQA1QEBAAcOANgBCQAA` + + `AAAAAAAAAAcOWgDqAQEABw4A/AEBAAcOAPgBBAAAAAAHDgDtAQQAAAAABw4BEg8BHxO0An` + + `sdAN4BAQAHDgDhAQAHHeG0xFuWtQIU4AAvAAcOOE4tABoBAAcOABoCAAAHDgAaAQAHDgAa` + + `AgAABw4AGgAHDgAaAgAABw4AqgEDAAAABx2HNAJ7LCAegwB0AAcOWgDBAQAHDkujTEt/An` + + `sdh0seAN4BAAcOAiKGAIUBAQAHDloAmQECAAAHDloASwEABw5aAHgABw6HtIiMAIkBAQAH` + + `HXjheESWAncd4Vq0ajwAnQECAAAHDnjhWrdaWqUCex0ATwEABw4CIoYANAAHDgCFAgMAAA` + + `AHDgIMaAJ5HTxsSwDQAQEABw48PD20jAA5AAcOwwIOLAJ2HYeFTB5atbT/0AACGQG8ARpO` + + `AhoCWQQAnQEeAhsBvAEcARcBAhkBvAEaSwIZAbwBGl4CGQG8ARpCAhkBvAEaYAZEkAAIBA` + + `AEAQQCBAIEAQACAQEEkCABkCA3gIAEzBc4AewXAAEBAQaQIDmAgATsGToBiBoAAQEBB5Ag` + + `O4CABLAaPAHMGgABAQMIkCA9gIAE6Bo+AYQbAQGYGwEBrBsAAQEBCZAgQYCABNwcQgH4HA` + + `cCEggKGgEaARoBGgEaARoBChECAQJDgYAEpB4BiCDIHgGIIOAeAYgg+B4BiCCQHwGIIKgf` + + `AYggwB8GggIABQjYHwQIrCABggIAAYICAAGCAgABAsggBALMIQEI8CEBCIwiAQioIksAxC` + + `IBAJwjAQDcJAEA6CUIAIwmBwSwJgEB+CYHAMQnEQAAAAAAAAABAAAAAAAAAAEAAAC/AAAA` + + `cAAAAAIAAAAuAAAAbAMAAAMAAAA9AAAAJAQAAAQAAAATAAAAAAcAAAUAAABmAAAAmAcAAA` + + `YAAAAGAAAAyAoAAAMQAAAGAAAAiAsAAAEgAAAiAAAAzAsAAAYgAAAFAAAAoBQAAAEQAAAg` + + `AAAA+BQAAAIgAAC/AAAAGhYAAAMgAAAiAAAADCMAAAQgAAAHAAAAeiQAAAUgAAABAAAAry` + + `QAAAAgAAAGAAAAviQAAAAQAAABAAAApCUAAA==` + `` diff --git a/cmd/fyne_demo/tutorials/animation.go b/cmd/fyne_demo/tutorials/animation.go index 29da0d43a2..465b36476c 100644 --- a/cmd/fyne_demo/tutorials/animation.go +++ b/cmd/fyne_demo/tutorials/animation.go @@ -84,7 +84,7 @@ func makeAnimationCurveItem(label string, curve fyne.AnimationCurve, yOff float3 text.Alignment = fyne.TextAlignCenter text.Resize(fyne.NewSize(380, 30)) text.Move(fyne.NewPos(0, yOff)) - box = canvas.NewRectangle(theme.ForegroundColor()) + box = newThemedBox() box.Resize(fyne.NewSize(30, 30)) box.Move(fyne.NewPos(0, yOff)) @@ -98,3 +98,46 @@ func makeAnimationCurveItem(label string, curve fyne.AnimationCurve, yOff float3 anim.RepeatCount = 1 return } + +// themedBox is a simple box that change its background color according +// to the selected theme +type themedBox struct { + widget.BaseWidget +} + +func newThemedBox() *themedBox { + b := &themedBox{} + b.ExtendBaseWidget(b) + return b +} + +func (b *themedBox) CreateRenderer() fyne.WidgetRenderer { + b.ExtendBaseWidget(b) + bg := canvas.NewRectangle(theme.ForegroundColor()) + return &themedBoxRenderer{bg: bg, objects: []fyne.CanvasObject{bg}} +} + +type themedBoxRenderer struct { + bg *canvas.Rectangle + objects []fyne.CanvasObject +} + +func (r *themedBoxRenderer) Destroy() { +} + +func (r *themedBoxRenderer) Layout(size fyne.Size) { + r.bg.Resize(size) +} + +func (r *themedBoxRenderer) MinSize() fyne.Size { + return r.bg.MinSize() +} + +func (r *themedBoxRenderer) Objects() []fyne.CanvasObject { + return r.objects +} + +func (r *themedBoxRenderer) Refresh() { + r.bg.FillColor = theme.ForegroundColor() + r.bg.Refresh() +} diff --git a/cmd/fyne_demo/tutorials/dialog.go b/cmd/fyne_demo/tutorials/dialog.go index a829d0148a..1c86704905 100644 --- a/cmd/fyne_demo/tutorials/dialog.go +++ b/cmd/fyne_demo/tutorials/dialog.go @@ -47,13 +47,14 @@ func dialogScreen(win fyne.Window) fyne.CanvasObject { }), widget.NewButton("File Open With Filter (.jpg or .png)", func() { fd := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) { - if err == nil && reader == nil { - return - } if err != nil { dialog.ShowError(err, win) return } + if reader == nil { + log.Println("Cancelled") + return + } imageOpened(reader) }, win) @@ -66,6 +67,10 @@ func dialogScreen(win fyne.Window) fyne.CanvasObject { dialog.ShowError(err, win) return } + if writer == nil { + log.Println("Cancelled") + return + } fileSaved(writer, win) }, win) @@ -77,6 +82,7 @@ func dialogScreen(win fyne.Window) fyne.CanvasObject { return } if list == nil { + log.Println("Cancelled") return } @@ -142,11 +148,6 @@ func imageOpened(f fyne.URIReadCloser) { } func fileSaved(f fyne.URIWriteCloser, w fyne.Window) { - if f == nil { - log.Println("Cancelled") - return - } - defer f.Close() _, err := f.Write([]byte("Written by Fyne demo\n")) if err != nil { diff --git a/container/apptabs.go b/container/apptabs.go index ccebc08dd4..49bfa84671 100644 --- a/container/apptabs.go +++ b/container/apptabs.go @@ -18,10 +18,11 @@ import ( type AppTabs struct { widget.BaseWidget - Items []*TabItem - OnChanged func(tab *TabItem) - current int - tabLocation TabLocation + Items []*TabItem + OnChanged func(tab *TabItem) + current int + tabLocation TabLocation + isTransitioning bool } // TabItem represents a single view in a AppTabs. @@ -89,7 +90,7 @@ func (c *AppTabs) CreateRenderer() fyne.WidgetRenderer { c.BaseWidget.ExtendBaseWidget(c) r := &appTabsRenderer{line: canvas.NewRectangle(theme.ShadowColor()), underline: canvas.NewRectangle(theme.PrimaryColor()), container: c} - r.updateTabs() + r.updateTabs(false) return r } @@ -158,8 +159,10 @@ func (c *AppTabs) SelectTabIndex(index int) { if index < 0 || index >= len(c.Items) || c.current == index { return } + c.isTransitioning = true c.current = index c.Refresh() + c.isTransitioning = false if c.OnChanged != nil { c.OnChanged(c.Items[c.current]) @@ -211,13 +214,25 @@ func (r *appTabsRenderer) Destroy() { func (r *appTabsRenderer) Layout(size fyne.Size) { tabBarMinSize := r.tabBar.MinSize() + if fyne.CurrentDevice().IsMobile() { + cells := len(r.tabBar.Objects) + if cells == 0 { + cells = 1 + } + + if fyne.IsVertical(fyne.CurrentDevice().Orientation()) { + r.tabBar.Layout = layout.NewGridLayoutWithColumns(cells) + } else { + r.tabBar.Layout = layout.NewGridLayoutWithRows(cells) + } + } var tabBarPos fyne.Position var tabBarSize fyne.Size var linePos fyne.Position var lineSize fyne.Size var childPos fyne.Position var childSize fyne.Size - switch r.adaptedLocation() { + switch r.adaptedLocation(r.container.tabLocation) { case TabLocationTop: buttonHeight := tabBarMinSize.Height tabBarPos = fyne.NewPos(0, 0) @@ -265,7 +280,7 @@ func (r *appTabsRenderer) Layout(size fyne.Size) { child.Move(childPos) child.Resize(childSize) } - r.moveSelection() + r.moveSelection(r.container.isTransitioning) } func (r *appTabsRenderer) MinSize() fyne.Size { @@ -299,7 +314,7 @@ func (r *appTabsRenderer) Refresh() { r.line.Refresh() r.underline.FillColor = theme.PrimaryColor() - if r.updateTabs() { + if r.updateTabs(r.container.isTransitioning) { r.Layout(r.container.Size()) } else { current := r.container.current @@ -323,17 +338,29 @@ func (r *appTabsRenderer) Refresh() { button.Refresh() } } - r.moveSelection() + r.moveSelection(r.container.isTransitioning) canvas.Refresh(r.container) } -func (r *appTabsRenderer) adaptedLocation() TabLocation { - tabLocation := r.container.tabLocation - if fyne.CurrentDevice().IsMobile() && (tabLocation == TabLocationLeading || tabLocation == TabLocationTrailing) { - return TabLocationBottom +func (r *appTabsRenderer) adaptedLocation(l TabLocation) TabLocation { + // Mobile has limited screen space, so don't put app tab bar on long edges + if d := fyne.CurrentDevice(); d.IsMobile() { + if o := d.Orientation(); fyne.IsVertical(o) { + if l == TabLocationLeading { + return TabLocationTop + } else if l == TabLocationTrailing { + return TabLocationBottom + } + } else { + if l == TabLocationTop { + return TabLocationLeading + } else if l == TabLocationBottom { + return TabLocationTrailing + } + } } - return r.container.tabLocation + return l } func (r *appTabsRenderer) buildButton(item *TabItem, iconPos buttonIconPosition) *tabButton { @@ -352,7 +379,12 @@ func (r *appTabsRenderer) buildTabBar(buttons []fyne.CanvasObject) *fyne.Contain if cells == 0 { cells = 1 } - lay = layout.NewGridLayout(cells) + + if fyne.IsVertical(fyne.CurrentDevice().Orientation()) { + lay = layout.NewGridLayoutWithColumns(cells) + } else { + lay = layout.NewGridLayoutWithRows(cells) + } } else if r.container.tabLocation == TabLocationLeading || r.container.tabLocation == TabLocationTrailing { lay = layout.NewVBoxLayout() } else { @@ -362,7 +394,7 @@ func (r *appTabsRenderer) buildTabBar(buttons []fyne.CanvasObject) *fyne.Contain return fyne.NewContainerWithLayout(lay, buttons...) } -func (r *appTabsRenderer) moveSelection() { +func (r *appTabsRenderer) moveSelection(withAnimation bool) { if r.container.current < 0 { r.underline.Hide() return @@ -371,7 +403,7 @@ func (r *appTabsRenderer) moveSelection() { var underlinePos fyne.Position var underlineSize fyne.Size - switch r.adaptedLocation() { + switch r.adaptedLocation(r.container.tabLocation) { case TabLocationTop: underlinePos = fyne.NewPos(selected.Position().X, r.tabBar.MinSize().Height) underlineSize = fyne.NewSize(selected.Size().Width, theme.Padding()) @@ -393,6 +425,13 @@ func (r *appTabsRenderer) moveSelection() { return } + if !withAnimation && r.animation == nil { + r.underline.Move(underlinePos) + r.underline.Resize(underlineSize) + canvas.Refresh(r.underline) + return + } + if r.animation != nil { r.animation.Stop() } @@ -439,7 +478,7 @@ func (r *appTabsRenderer) tabsInSync() bool { return true } -func (r *appTabsRenderer) updateTabs() bool { +func (r *appTabsRenderer) updateTabs(withAnimation bool) bool { if r.tabsInSync() { return false } @@ -468,7 +507,7 @@ func (r *appTabsRenderer) updateTabs() bool { } r.tabBar = r.buildTabBar(buttons) r.objects = objects - r.moveSelection() + r.moveSelection(withAnimation) return true } diff --git a/container/apptabs_mobile_test.go b/container/apptabs_mobile_test.go index 8a7533d5cb..cb3e6534b9 100644 --- a/container/apptabs_mobile_test.go +++ b/container/apptabs_mobile_test.go @@ -232,25 +232,25 @@ func TestTabContainer_Layout(t *testing.T) { name: "bottom: tab with icon only", item: container.NewTabItemWithIcon("", theme.InfoIcon(), canvas.NewCircle(theme.BackgroundColor())), location: container.TabLocationBottom, - want: "apptabs/mobile/layout_bottom_ico.xml", + want: "apptabs/mobile/layout_bottom_icon.xml", }, { name: "leading: tab with icon and text", item: container.NewTabItemWithIcon("Text1", theme.CancelIcon(), canvas.NewCircle(theme.BackgroundColor())), location: container.TabLocationLeading, - want: "apptabs/mobile/layout_bottom_icon_and_text.xml", + want: "apptabs/mobile/layout_top_icon_and_text.xml", }, { name: "leading: tab with text only", item: container.NewTabItem("Text2", canvas.NewCircle(theme.BackgroundColor())), location: container.TabLocationLeading, - want: "apptabs/mobile/layout_bottom_text.xml", + want: "apptabs/mobile/layout_top_text.xml", }, { name: "leading: tab with icon only", item: container.NewTabItemWithIcon("", theme.InfoIcon(), canvas.NewCircle(theme.BackgroundColor())), location: container.TabLocationLeading, - want: "apptabs/mobile/layout_bottom_icon.xml", + want: "apptabs/mobile/layout_top_icon.xml", }, { name: "trailing: tab with icon and text", @@ -301,7 +301,7 @@ func TestTabContainer_SetTabLocation(t *testing.T) { tabs.SetTabLocation(container.TabLocationLeading) w.Resize(tabs.MinSize()) - test.AssertRendersToMarkup(t, "apptabs/mobile/tab_location_bottom.xml", c, "leading is the same as bottom on mobile") + test.AssertRendersToMarkup(t, "apptabs/mobile/tab_location_top.xml", c, "leading is the same as top on mobile") tabs.SetTabLocation(container.TabLocationBottom) w.Resize(tabs.MinSize()) diff --git a/container/layouts.go b/container/layouts.go index 6bd7034172..72be1ed4bf 100644 --- a/container/layouts.go +++ b/container/layouts.go @@ -52,7 +52,7 @@ func NewGridWithColumns(cols int, objects ...fyne.CanvasObject) *fyne.Container } // NewGridWithRows creates a new container with the specified objects and using the grid layout with -// a specified number of columns. The number of columns will depend on how many children are in the container. +// a specified number of rows. The number of columns will depend on how many children are in the container. // // Since: 1.4 func NewGridWithRows(rows int, objects ...fyne.CanvasObject) *fyne.Container { diff --git a/container/testdata/apptabs/mobile/layout_bottom_ico.xml b/container/testdata/apptabs/mobile/layout_bottom_ico.xml deleted file mode 100644 index 9bfe21d353..0000000000 --- a/container/testdata/apptabs/mobile/layout_bottom_ico.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/data/binding/bindlists_test.go b/data/binding/bindlists_test.go index 4aadc8d51a..1a0fa9c7bb 100644 --- a/data/binding/bindlists_test.go +++ b/data/binding/bindlists_test.go @@ -160,3 +160,43 @@ func TestFloatList_Set(t *testing.T) { assert.Nil(t, err) assert.Equal(t, 5.3, v) } + +func TestFloatList_NotifyOnlyOnceWhenChange(t *testing.T) { + f := NewFloatList() + triggered := 0 + f.AddListener(NewDataListener(func() { + triggered++ + })) + waitForItems() + assert.Equal(t, 1, triggered) + + triggered = 0 + f.Set([]float64{55, 77}) + waitForItems() + assert.Equal(t, 1, triggered) + + triggered = 0 + f.SetValue(0, 5) + waitForItems() + assert.Zero(t, triggered) + + triggered = 0 + f.Set([]float64{101, 98}) + waitForItems() + assert.Zero(t, triggered) + + triggered = 0 + f.Append(88) + waitForItems() + assert.Equal(t, 1, triggered) + + triggered = 0 + f.Prepend(23) + waitForItems() + assert.Equal(t, 1, triggered) + + triggered = 0 + f.Set([]float64{32}) + waitForItems() + assert.Equal(t, 1, triggered) +} diff --git a/data/binding/listbinding.go b/data/binding/listbinding.go index dfcffc937c..bedfc30df5 100644 --- a/data/binding/listbinding.go +++ b/data/binding/listbinding.go @@ -30,12 +30,8 @@ func (b *listBase) Length() int { func (b *listBase) appendItem(i DataItem) { b.items = append(b.items, i) - - b.trigger() } func (b *listBase) deleteItem(i int) { b.items = append(b.items[:i], b.items[i+1:]...) - - b.trigger() } diff --git a/dialog/file_mobile.go b/dialog/file_mobile.go index 6e31497cb2..e453bdf377 100644 --- a/dialog/file_mobile.go +++ b/dialog/file_mobile.go @@ -36,7 +36,7 @@ func fileOpenOSOverride(f *FileDialog) bool { } func fileSaveOSOverride(f *FileDialog) bool { - gomobile.ShowFileSavePicker(f.callback.(func(fyne.URIWriteCloser, error)), f.filter) + gomobile.ShowFileSavePicker(f.callback.(func(fyne.URIWriteCloser, error)), f.filter, f.initialFileName) return true } diff --git a/go.mod b/go.mod index 7648888d0f..4e500510a3 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ require ( github.com/akavel/rsrc v0.8.0 // indirect github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 github.com/fsnotify/fsnotify v1.4.9 - github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f + github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 - github.com/godbus/dbus/v5 v5.0.3 + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb + github.com/godbus/dbus/v5 v5.0.4 github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526 github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca diff --git a/go.sum b/go.sum index 74eb47698a..a074a968ef 100644 --- a/go.sum +++ b/go.sum @@ -10,14 +10,14 @@ github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8 github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f h1:rguJ/t99j/6zRSFzsBKlsmmyl+vOvCeTJ+2uTBvuXFI= -github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= +github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a h1:3TAJhl8vXyli0tooKB0vd6gLCyBdWL4QEYbDoJpHEZk= +github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY= github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 h1:QrUfZrT8n72FUuiABt4tbu8PwDnOPAbnj3Mql1UhdRI= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8= github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw= github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526 h1:NfuKjkj/Xc2z1xZIj+EmNCm5p1nKJPyw3F4E20usXvg= diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go index b3d41b20b3..b37c5872e3 100644 --- a/internal/driver/glfw/window.go +++ b/internal/driver/glfw/window.go @@ -825,7 +825,7 @@ func (w *window) waitForDoubleTap(co fyne.CanvasObject, ev *fyne.PointEvent) { } func (w *window) mouseScrolled(viewport *glfw.Window, xoff float64, yoff float64) { - co, _, _ := w.findObjectAtPositionMatching(w.canvas, w.mousePos, func(object fyne.CanvasObject) bool { + co, pos, _ := w.findObjectAtPositionMatching(w.canvas, w.mousePos, func(object fyne.CanvasObject) bool { _, ok := object.(fyne.Scrollable) return ok }) @@ -838,6 +838,8 @@ func (w *window) mouseScrolled(viewport *glfw.Window, xoff float64, yoff float64 } ev := &fyne.ScrollEvent{} ev.Scrolled = fyne.NewDelta(float32(xoff)*scrollSpeed, float32(yoff)*scrollSpeed) + ev.Position = pos + ev.AbsolutePosition = w.mousePos wid.Scrolled(ev) } } @@ -1120,7 +1122,7 @@ func (w *window) keyPressed(_ *glfw.Window, key glfw.Key, scancode int, action g } } - if shortcut == nil && keyDesktopModifier != 0 && keyDesktopModifier != desktop.ShiftModifier { + if shortcut == nil && keyDesktopModifier != 0 && !isKeyModifier(keyName) && keyDesktopModifier != desktop.ShiftModifier { shortcut = &desktop.CustomShortcut{ KeyName: keyName, Modifier: keyDesktopModifier, @@ -1417,3 +1419,10 @@ func (d *gLDriver) CreateSplashWindow() fyne.Window { func (d *gLDriver) AllWindows() []fyne.Window { return d.windows } + +func isKeyModifier(keyName fyne.KeyName) bool { + return keyName == desktop.KeyShiftLeft || keyName == desktop.KeyShiftRight || + keyName == desktop.KeyControlLeft || keyName == desktop.KeyControlRight || + keyName == desktop.KeyAltLeft || keyName == desktop.KeyAltRight || + keyName == desktop.KeySuperLeft || keyName == desktop.KeySuperRight +} diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go index bcabd91ac9..934b03a557 100644 --- a/internal/driver/glfw/window_test.go +++ b/internal/driver/glfw/window_test.go @@ -534,6 +534,22 @@ func TestWindow_HoverableOnDragging(t *testing.T) { assert.NotNil(t, dh.popMouseOutEvent()) } +func TestWindow_Scrolled(t *testing.T) { + w := createWindow("Test").(*window) + o := &scrollable{Rectangle: canvas.NewRectangle(color.White)} + o.SetMinSize(fyne.NewSize(100, 100)) + w.SetContent(o) + + w.mousePos = fyne.NewPos(50, 60) + w.mouseScrolled(w.viewport, 10, 10) + w.waitForEvents() + + if e, _ := o.popScrollEvent().(*fyne.ScrollEvent); assert.NotNil(t, e, "scroll event") { + assert.Equal(t, fyne.NewPos(50, 60), e.AbsolutePosition) + assert.Equal(t, fyne.NewPos(46, 56), e.Position) + } +} + func TestWindow_Tapped(t *testing.T) { w := createWindow("Test").(*window) rect := canvas.NewRectangle(color.White) @@ -933,6 +949,29 @@ func TestWindow_Focus(t *testing.T) { assert.Equal(t, "ef", e2.Text) } +func TestWindow_CaptureTypedShortcut(t *testing.T) { + w := createWindow("Test").(*window) + content := &typedShortcutable{} + content.SetMinSize(fyne.NewSize(10, 10)) + w.SetContent(content) + repaintWindow(w) + + w.mouseMoved(w.viewport, 5, 5) + w.mouseClicked(w.viewport, glfw.MouseButton1, glfw.Press, 0) + + w.keyPressed(nil, glfw.KeyLeftControl, 0, glfw.Action(glfw.Press), glfw.ModControl) + w.keyPressed(nil, glfw.KeyLeftShift, 0, glfw.Action(glfw.Press), glfw.ModControl) + w.keyPressed(nil, glfw.KeyF, 0, glfw.Action(glfw.Press), glfw.ModControl) + w.keyPressed(nil, glfw.KeyLeftShift, 0, glfw.Action(glfw.Press), glfw.ModControl) + w.keyPressed(nil, glfw.KeyLeftControl, 0, glfw.Action(glfw.Release), glfw.ModControl) + w.keyPressed(nil, glfw.KeyF, 0, glfw.Action(glfw.Release), glfw.ModControl) + + w.waitForEvents() + + assert.Equal(t, 1, len(content.capturedShortcuts)) + assert.Equal(t, "CustomDesktop:Control+F", content.capturedShortcuts[0].ShortcutName()) +} + func TestWindow_ManualFocus(t *testing.T) { w := createWindow("Test").(*window) content := &focusable{} @@ -1273,6 +1312,31 @@ func (f *focusable) Disabled() bool { return f.disabled } +type typedShortcutable struct { + focusable + capturedShortcuts []fyne.Shortcut +} + +func (ts *typedShortcutable) TypedShortcut(s fyne.Shortcut) { + ts.capturedShortcuts = append(ts.capturedShortcuts, s) +} + +var _ fyne.Draggable = (*draggable)(nil) + +type scrollable struct { + *canvas.Rectangle + events []interface{} +} + +func (s *scrollable) Scrolled(e *fyne.ScrollEvent) { + s.events = append(s.events, e) +} + +func (s *scrollable) popScrollEvent() (e interface{}) { + e, s.events = pop(s.events) + return +} + // // Test helper // diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go index 1824df2525..1506a1670e 100644 --- a/internal/driver/gomobile/canvas.go +++ b/internal/driver/gomobile/canvas.go @@ -253,7 +253,11 @@ func (c *mobileCanvas) focusManager() *app.FocusManager { } func (c *mobileCanvas) handleKeyboard(obj fyne.Focusable) { - if keyb, ok := obj.(mobile.Keyboardable); ok { + isDisabled := false + if disWid, ok := obj.(fyne.Disableable); ok { + isDisabled = disWid.Disabled() + } + if keyb, ok := obj.(mobile.Keyboardable); ok && !isDisabled { showVirtualKeyboard(keyb.Keyboard()) } else { hideVirtualKeyboard() diff --git a/internal/driver/gomobile/file.go b/internal/driver/gomobile/file.go index ea12251caa..7ea6af7b6d 100644 --- a/internal/driver/gomobile/file.go +++ b/internal/driver/gomobile/file.go @@ -102,11 +102,11 @@ func fileWriterForURI(u fyne.URI) (fyne.URIWriteCloser, error) { } type hasSavePicker interface { - ShowFileSavePicker(func(string, func()), *app.FileFilter) + ShowFileSavePicker(func(string, func()), *app.FileFilter, string) } // ShowFileSavePicker loads the native file save dialog and returns the chosen file path via the callback func. -func ShowFileSavePicker(callback func(fyne.URIWriteCloser, error), filter storage.FileFilter) { +func ShowFileSavePicker(callback func(fyne.URIWriteCloser, error), filter storage.FileFilter, filename string) { drv := fyne.CurrentApp().Driver().(*mobileDriver) if a, ok := drv.app.(hasSavePicker); ok { a.ShowFileSavePicker(func(uri string, closer func()) { @@ -119,6 +119,6 @@ func ShowFileSavePicker(callback func(fyne.URIWriteCloser, error), filter storag f.(*fileSave).done = closer } callback(f, err) - }, mobileFilter(filter)) + }, mobileFilter(filter), filename) } } diff --git a/internal/driver/util_test.go b/internal/driver/util_test.go index b9a006e539..9407de0763 100644 --- a/internal/driver/util_test.go +++ b/internal/driver/util_test.go @@ -439,23 +439,23 @@ type objectTree struct { size fyne.Size } -func (o objectTree) Size() fyne.Size { +func (o *objectTree) Size() fyne.Size { return o.size } -func (o objectTree) Resize(size fyne.Size) { +func (o *objectTree) Resize(size fyne.Size) { o.size = size } -func (o objectTree) Position() fyne.Position { +func (o *objectTree) Position() fyne.Position { return o.pos } -func (o objectTree) Move(position fyne.Position) { +func (o *objectTree) Move(position fyne.Position) { o.pos = position } -func (o objectTree) MinSize() fyne.Size { +func (o *objectTree) MinSize() fyne.Size { return o.size } @@ -463,18 +463,18 @@ func (o objectTree) Visible() bool { return !o.hidden } -func (o objectTree) Show() { +func (o *objectTree) Show() { o.hidden = false } -func (o objectTree) Hide() { +func (o *objectTree) Hide() { o.hidden = true } -func (o objectTree) Refresh() { +func (o *objectTree) Refresh() { } -func (o objectTree) CreateRenderer() fyne.WidgetRenderer { +func (o *objectTree) CreateRenderer() fyne.WidgetRenderer { r := &objectTreeRenderer{} r.SetObjects(o.children) return r diff --git a/layout/gridlayout.go b/layout/gridlayout.go index 32e4e69443..1a68779ca9 100644 --- a/layout/gridlayout.go +++ b/layout/gridlayout.go @@ -31,7 +31,7 @@ func NewGridLayoutWithColumns(cols int) fyne.Layout { return &gridLayout{Cols: cols} } -// NewGridLayoutWithRows returns a new grid layout that specifies a row count that creates new columns as required. +// NewGridLayoutWithRows returns a new grid layout that specifies a row count that creates new rows as required. func NewGridLayoutWithRows(rows int) fyne.Layout { return &gridLayout{Cols: rows, vertical: true} } diff --git a/theme/font/LICENSE.txt b/theme/font/LICENSE.txt index 75b52484ea..72fcf5fd06 100644 --- a/theme/font/LICENSE.txt +++ b/theme/font/LICENSE.txt @@ -1,202 +1,38 @@ +—————————————————————————————- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +—————————————————————————————- - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION +The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. - 1. Definitions. +DEFINITIONS +“Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. +“Reserved Font Name” refers to any names specified as such after the copyright statement(s). - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. +“Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. +“Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. +“Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. +1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). +2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. +3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. +5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. +TERMINATION +This license becomes null and void if any of the above conditions are not met. - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. \ No newline at end of file diff --git a/theme/icons.go b/theme/icons.go index 927913845c..91db076f15 100644 --- a/theme/icons.go +++ b/theme/icons.go @@ -1014,12 +1014,12 @@ func ZoomOutIcon() fyne.Resource { return safeIconLookup(IconNameViewZoomOut) } -// VisibilityIcon returns a resource containing the standard visibity icon for the current theme +// VisibilityIcon returns a resource containing the standard visibility icon for the current theme func VisibilityIcon() fyne.Resource { return safeIconLookup(IconNameVisibility) } -// VisibilityOffIcon returns a resource containing the standard visibity off icon for the current theme +// VisibilityOffIcon returns a resource containing the standard visibility off icon for the current theme func VisibilityOffIcon() fyne.Resource { return safeIconLookup(IconNameVisibilityOff) } diff --git a/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java b/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java index 16df340d56..afe446940d 100644 --- a/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java +++ b/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java @@ -149,11 +149,11 @@ void doShowFileOpen(String mimes) { startActivityForResult(Intent.createChooser(intent, "Open File"), FILE_OPEN_CODE); } - static void showFileSave(String mimes) { - goNativeActivity.doShowFileSave(mimes); + static void showFileSave(String mimes, String filename) { + goNativeActivity.doShowFileSave(mimes, filename); } - void doShowFileSave(String mimes) { + void doShowFileSave(String mimes, String filename) { Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); if (mimes.contains("|") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { intent.setType("*/*"); @@ -161,6 +161,7 @@ void doShowFileSave(String mimes) { } else { intent.setType(mimes); } + intent.putExtra(Intent.EXTRA_TITLE, filename); intent.addCategory(Intent.CATEGORY_OPENABLE); startActivityForResult(Intent.createChooser(intent, "Save File"), FILE_SAVE_CODE); } diff --git a/vendor/github.com/fyne-io/mobile/app/android.c b/vendor/github.com/fyne-io/mobile/app/android.c index 0c1ed9e6e6..3ba916cfef 100644 --- a/vendor/github.com/fyne-io/mobile/app/android.c +++ b/vendor/github.com/fyne-io/mobile/app/android.c @@ -83,7 +83,7 @@ void ANativeActivity_onCreate(ANativeActivity *activity, void* savedState, size_ show_keyboard_method = find_static_method(env, current_class, "showKeyboard", "(I)V"); hide_keyboard_method = find_static_method(env, current_class, "hideKeyboard", "()V"); show_file_open_method = find_static_method(env, current_class, "showFileOpen", "(Ljava/lang/String;)V"); - show_file_save_method = find_static_method(env, current_class, "showFileSave", "(Ljava/lang/String;)V"); + show_file_save_method = find_static_method(env, current_class, "showFileSave", "(Ljava/lang/String;Ljava/lang/String;)V"); setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz)); @@ -241,13 +241,15 @@ void showFileOpen(JNIEnv* env, char* mimes) { ); } -void showFileSave(JNIEnv* env, char* mimes) { +void showFileSave(JNIEnv* env, char* mimes, char* filename) { jstring mimesJString = (*env)->NewStringUTF(env, mimes); + jstring filenameJString = (*env)->NewStringUTF(env, filename); (*env)->CallStaticVoidMethod( env, current_class, show_file_save_method, - mimesJString + mimesJString, + filenameJString ); } diff --git a/vendor/github.com/fyne-io/mobile/app/android.go b/vendor/github.com/fyne-io/mobile/app/android.go index 7fc65902c2..7143b1fee1 100644 --- a/vendor/github.com/fyne-io/mobile/app/android.go +++ b/vendor/github.com/fyne-io/mobile/app/android.go @@ -45,7 +45,7 @@ int32_t getKeyRune(JNIEnv* env, AInputEvent* e); void showKeyboard(JNIEnv* env, int keyboardType); void hideKeyboard(JNIEnv* env); void showFileOpen(JNIEnv* env, char* mimes); -void showFileSave(JNIEnv* env, char* mimes); +void showFileSave(JNIEnv* env, char* mimes, char* filename); void Java_org_golang_app_GoNativeActivity_filePickerReturned(JNIEnv *env, jclass clazz, jstring str); */ @@ -383,16 +383,18 @@ func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter) } } -func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter) { +func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter, filename string) { fileCallback = callback mimes := mimeStringFromFilter(filter) mimeStr := C.CString(mimes) defer C.free(unsafe.Pointer(mimeStr)) + filenameStr := C.CString(filename) + defer C.free(unsafe.Pointer(filenameStr)) save := func(vm, jniEnv, ctx uintptr) error { env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer - C.showFileSave(env, mimeStr) + C.showFileSave(env, mimeStr, filenameStr) return nil } diff --git a/vendor/github.com/fyne-io/mobile/app/app.go b/vendor/github.com/fyne-io/mobile/app/app.go index b1ca18b278..b020c544a8 100644 --- a/vendor/github.com/fyne-io/mobile/app/app.go +++ b/vendor/github.com/fyne-io/mobile/app/app.go @@ -55,7 +55,7 @@ type App interface { ShowVirtualKeyboard(KeyboardType) HideVirtualKeyboard() ShowFileOpenPicker(func(string, func()), *FileFilter) - ShowFileSavePicker(func(string, func()), *FileFilter) + ShowFileSavePicker(func(string, func()), *FileFilter, string) } type FileFilter struct { @@ -150,8 +150,8 @@ func (a *app) HideVirtualKeyboard() { func (a *app) ShowFileOpenPicker(callback func(string, func()), filter *FileFilter) { driverShowFileOpenPicker(callback, filter) } -func (a *app) ShowFileSavePicker(callback func(string, func()), filter *FileFilter) { - driverShowFileSavePicker(callback, filter) +func (a *app) ShowFileSavePicker(callback func(string, func()), filter *FileFilter, filename string) { + driverShowFileSavePicker(callback, filter, filename) } type stopPumping struct{} diff --git a/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go b/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go index 0e1a240ef2..99368918c0 100644 --- a/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go +++ b/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go @@ -240,7 +240,7 @@ func driverShowFileOpenPicker(func(string, func()), *FileFilter) { } // driverShowFileSavePicker does nothing on desktop -func driverShowFileSavePicker(func(string, func()), *FileFilter) { +func driverShowFileSavePicker(func(string, func()), *FileFilter, string) { } // convRune marks the Carbon/Cocoa private-range unicode rune representing diff --git a/vendor/github.com/fyne-io/mobile/app/darwin_ios.go b/vendor/github.com/fyne-io/mobile/app/darwin_ios.go index dc30a97438..7477f6f2b4 100644 --- a/vendor/github.com/fyne-io/mobile/app/darwin_ios.go +++ b/vendor/github.com/fyne-io/mobile/app/darwin_ios.go @@ -306,7 +306,7 @@ func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter) C.showFileOpenPicker(mimeStr, extStr) } -func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter) { +func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter, filename string) { fileCallback = callback mimeStr, extStr := cStringsForFilter(filter) diff --git a/vendor/github.com/fyne-io/mobile/app/shiny.go b/vendor/github.com/fyne-io/mobile/app/shiny.go index b7c1926a43..d29da5d62b 100644 --- a/vendor/github.com/fyne-io/mobile/app/shiny.go +++ b/vendor/github.com/fyne-io/mobile/app/shiny.go @@ -27,5 +27,5 @@ func driverShowFileOpenPicker(func(string, func()), *FileFilter) { } // driverShowFileSavePicker does nothing on desktop -func driverShowFileSavePicker(func(string, func()), *FileFilter) { +func driverShowFileSavePicker(func(string, func()), *FileFilter, string) { } diff --git a/vendor/github.com/fyne-io/mobile/app/x11.go b/vendor/github.com/fyne-io/mobile/app/x11.go index edc3977af1..7b112f0969 100644 --- a/vendor/github.com/fyne-io/mobile/app/x11.go +++ b/vendor/github.com/fyne-io/mobile/app/x11.go @@ -137,5 +137,5 @@ func driverShowFileOpenPicker(func(string, func()), *FileFilter) { } // driverShowFileSavePicker does nothing on desktop -func driverShowFileSavePicker(func(string, func()), *FileFilter) { +func driverShowFileSavePicker(func(string, func()), *FileFilter, string) { } diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-client-protocol.h similarity index 100% rename from vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.h rename to vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-client-protocol.h diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go index 22ff0280b2..db5def5f11 100644 --- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go +++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go @@ -7,4 +7,4 @@ package glfw // generate` on this package. This exists to invalidate the build cache (see // https://github.com/go-gl/glfw/issues/269), which is unaffected by C source // inputs. -const upstreamTreeSHA = "4490c2c270a92046291b021c15e33340289b33db" +const upstreamTreeSHA = "35599f5c137b48f8395629e686000729426bf966" diff --git a/vendor/github.com/godbus/dbus/v5/.travis.yml b/vendor/github.com/godbus/dbus/v5/.travis.yml deleted file mode 100644 index dd67672048..0000000000 --- a/vendor/github.com/godbus/dbus/v5/.travis.yml +++ /dev/null @@ -1,50 +0,0 @@ -dist: bionic -language: go -go_import_path: github.com/godbus/dbus - -go: - - 1.11.x - - 1.12.x - - 1.13.x - - tip - -matrix: - fast_finish: true - allow_failures: - - go: tip - -addons: - apt: - packages: - - dbus - - dbus-x11 - -before_install: - - export GO111MODULE=on - -script: - - go test -v -race -mod=readonly ./... # Run all the tests with the race detector enabled - - go vet ./... # go vet is the official Go static analyzer - -jobs: - include: - # The build matrix doesn't cover build stages, so manually expand - # the jobs with anchors - - &multiarch - stage: "Multiarch Test" - go: 1.11.x - env: TARGETS="386 arm arm64 ppc64le" - before_install: - - docker run --rm --privileged multiarch/qemu-user-static --reset -p yes - script: - - | - set -e - for target in $TARGETS; do - printf "\e[1mRunning test suite under ${target}.\e[0m\n" - GOARCH="$target" go test -v ./... - printf "\n\n" - done - - <<: *multiarch - go: 1.12.x - - <<: *multiarch - go: 1.13.x diff --git a/vendor/github.com/godbus/dbus/v5/README.markdown b/vendor/github.com/godbus/dbus/v5/README.markdown index fd29648752..1fb2eacaa1 100644 --- a/vendor/github.com/godbus/dbus/v5/README.markdown +++ b/vendor/github.com/godbus/dbus/v5/README.markdown @@ -1,4 +1,4 @@ -[![Build Status](https://travis-ci.org/godbus/dbus.svg?branch=master)](https://travis-ci.org/godbus/dbus) +![Build Status](https://github.com/godbus/dbus/workflows/Go/badge.svg) dbus ---- @@ -32,6 +32,8 @@ gives a short overview over the basic usage. #### Projects using godbus - [notify](https://github.com/esiqveland/notify) provides desktop notifications over dbus into a library. - [go-bluetooth](https://github.com/muka/go-bluetooth) provides a bluetooth client over bluez dbus API. +- [playerbm](https://github.com/altdesktop/playerbm) a bookmark utility for media players. +- [iwd](https://github.com/shibumi/iwd) go bindings for the internet wireless daemon "iwd". Please note that the API is considered unstable for now and may change without further notice. diff --git a/vendor/github.com/godbus/dbus/v5/auth.go b/vendor/github.com/godbus/dbus/v5/auth.go index 31abac629d..283487a0e3 100644 --- a/vendor/github.com/godbus/dbus/v5/auth.go +++ b/vendor/github.com/godbus/dbus/v5/auth.go @@ -37,7 +37,7 @@ const ( // Auth defines the behaviour of an authentication mechanism. type Auth interface { - // Return the name of the mechnism, the argument to the first AUTH command + // Return the name of the mechanism, the argument to the first AUTH command // and the next status. FirstData() (name, resp []byte, status AuthStatus) diff --git a/vendor/github.com/godbus/dbus/v5/call.go b/vendor/github.com/godbus/dbus/v5/call.go index 2cb189012e..b06b063580 100644 --- a/vendor/github.com/godbus/dbus/v5/call.go +++ b/vendor/github.com/godbus/dbus/v5/call.go @@ -24,6 +24,15 @@ type Call struct { // Holds the response once the call is done. Body []interface{} + // ResponseSequence stores the sequence number of the DBus message containing + // the call response (or error). This can be compared to the sequence number + // of other call responses and signals on this connection to determine their + // relative ordering on the underlying DBus connection. + // For errors, ResponseSequence is populated only if the error came from a + // DBusMessage that was received or if there was an error receiving. In case of + // failure to make the call, ResponseSequence will be NoSequence. + ResponseSequence Sequence + // tracks context and canceler ctx context.Context ctxCanceler context.CancelFunc diff --git a/vendor/github.com/godbus/dbus/v5/conn.go b/vendor/github.com/godbus/dbus/v5/conn.go index b55bc99c85..29fe018ad8 100644 --- a/vendor/github.com/godbus/dbus/v5/conn.go +++ b/vendor/github.com/godbus/dbus/v5/conn.go @@ -45,6 +45,7 @@ type Conn struct { serialGen SerialGenerator inInt Interceptor outInt Interceptor + auth []Auth names *nameTracker calls *callTracker @@ -59,7 +60,8 @@ type Conn struct { func SessionBus() (conn *Conn, err error) { sessionBusLck.Lock() defer sessionBusLck.Unlock() - if sessionBus != nil { + if sessionBus != nil && + sessionBus.Connected() { return sessionBus, nil } defer func() { @@ -67,19 +69,7 @@ func SessionBus() (conn *Conn, err error) { sessionBus = conn } }() - conn, err = SessionBusPrivate() - if err != nil { - return - } - if err = conn.Auth(nil); err != nil { - conn.Close() - conn = nil - return - } - if err = conn.Hello(); err != nil { - conn.Close() - conn = nil - } + conn, err = ConnectSessionBus() return } @@ -116,7 +106,8 @@ func SessionBusPrivateHandler(handler Handler, signalHandler SignalHandler) (*Co func SystemBus() (conn *Conn, err error) { systemBusLck.Lock() defer systemBusLck.Unlock() - if systemBus != nil { + if systemBus != nil && + systemBus.Connected() { return systemBus, nil } defer func() { @@ -124,20 +115,42 @@ func SystemBus() (conn *Conn, err error) { systemBus = conn } }() - conn, err = SystemBusPrivate() + conn, err = ConnectSystemBus() + return +} + +// ConnectSessionBus connects to the session bus. +func ConnectSessionBus(opts ...ConnOption) (*Conn, error) { + address, err := getSessionBusAddress() if err != nil { - return + return nil, err } - if err = conn.Auth(nil); err != nil { - conn.Close() - conn = nil - return + return Connect(address, opts...) +} + +// ConnectSystemBus connects to the system bus. +func ConnectSystemBus(opts ...ConnOption) (*Conn, error) { + return Connect(getSystemBusPlatformAddress(), opts...) +} + +// Connect connects to the given address. +// +// Returned connection is ready to use and doesn't require calling +// Auth and Hello methods to make it usable. +func Connect(address string, opts ...ConnOption) (*Conn, error) { + conn, err := Dial(address, opts...) + if err != nil { + return nil, err + } + if err = conn.Auth(conn.auth); err != nil { + _ = conn.Close() + return nil, err } if err = conn.Hello(); err != nil { - conn.Close() - conn = nil + _ = conn.Close() + return nil, err } - return + return conn, nil } // SystemBusPrivate returns a new private connection to the system bus. @@ -197,6 +210,14 @@ func WithSerialGenerator(gen SerialGenerator) ConnOption { } } +// WithAuth sets authentication methods for the auth conversation. +func WithAuth(methods ...Auth) ConnOption { + return func(conn *Conn) error { + conn.auth = methods + return nil + } +} + // Interceptor intercepts incoming and outgoing messages. type Interceptor func(msg *Message) @@ -309,6 +330,11 @@ func (conn *Conn) Context() context.Context { return conn.ctx } +// Connected returns whether conn is connected +func (conn *Conn) Connected() bool { + return conn.ctx.Err() == nil +} + // Eavesdrop causes conn to send all incoming messages to the given channel // without further processing. Method replies, errors and signals will not be // sent to the appropriate channels and method calls will not be handled. If nil @@ -342,8 +368,9 @@ func (conn *Conn) Hello() error { } // inWorker runs in an own goroutine, reading incoming messages from the -// transport and dispatching them appropiately. +// transport and dispatching them appropriately. func (conn *Conn) inWorker() { + sequenceGen := newSequenceGenerator() for { msg, err := conn.ReadMessage() if err != nil { @@ -352,7 +379,7 @@ func (conn *Conn) inWorker() { // anything but to shut down all stuff and returns errors to all // pending replies. conn.Close() - conn.calls.finalizeAllWithError(err) + conn.calls.finalizeAllWithError(sequenceGen, err) return } // invalid messages are ignored @@ -381,13 +408,14 @@ func (conn *Conn) inWorker() { if conn.inInt != nil { conn.inInt(msg) } + sequence := sequenceGen.next() switch msg.Type { case TypeError: - conn.serialGen.RetireSerial(conn.calls.handleDBusError(msg)) + conn.serialGen.RetireSerial(conn.calls.handleDBusError(sequence, msg)) case TypeMethodReply: - conn.serialGen.RetireSerial(conn.calls.handleReply(msg)) + conn.serialGen.RetireSerial(conn.calls.handleReply(sequence, msg)) case TypeSignal: - conn.handleSignal(msg) + conn.handleSignal(sequence, msg) case TypeMethodCall: go conn.handleCall(msg) } @@ -395,7 +423,7 @@ func (conn *Conn) inWorker() { } } -func (conn *Conn) handleSignal(msg *Message) { +func (conn *Conn) handleSignal(sequence Sequence, msg *Message) { iface := msg.Headers[FieldInterface].value.(string) member := msg.Headers[FieldMember].value.(string) // as per http://dbus.freedesktop.org/doc/dbus-specification.html , @@ -421,10 +449,11 @@ func (conn *Conn) handleSignal(msg *Message) { } } signal := &Signal{ - Sender: sender, - Path: msg.Headers[FieldPath].value.(ObjectPath), - Name: iface + "." + member, - Body: msg.Body, + Sender: sender, + Path: msg.Headers[FieldPath].value.(ObjectPath), + Name: iface + "." + member, + Body: msg.Body, + Sequence: sequence, } conn.signalHandler.DeliverSignal(iface, member, signal) } @@ -442,6 +471,9 @@ func (conn *Conn) Object(dest string, path ObjectPath) BusObject { } func (conn *Conn) sendMessageAndIfClosed(msg *Message, ifClosed func()) { + if msg.serial == 0 { + msg.serial = conn.getSerial() + } if conn.outInt != nil { conn.outInt(msg) } @@ -473,16 +505,16 @@ func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call { if ctx == nil { panic("nil context") } + if ch == nil { + ch = make(chan *Call, 1) + } else if cap(ch) == 0 { + panic("dbus: unbuffered channel passed to (*Conn).Send") + } var call *Call ctx, canceler := context.WithCancel(ctx) msg.serial = conn.getSerial() if msg.Type == TypeMethodCall && msg.Flags&FlagNoReplyExpected == 0 { - if ch == nil { - ch = make(chan *Call, 5) - } else if cap(ch) == 0 { - panic("dbus: unbuffered channel passed to (*Conn).Send") - } call = new(Call) call.Destination, _ = msg.Headers[FieldDestination].value.(string) call.Path, _ = msg.Headers[FieldPath].value.(ObjectPath) @@ -504,7 +536,8 @@ func (conn *Conn) send(ctx context.Context, msg *Message, ch chan *Call) *Call { }) } else { canceler() - call = &Call{Err: nil} + call = &Call{Err: nil, Done: ch} + ch <- call conn.sendMessageAndIfClosed(msg, func() { call = &Call{Err: ErrClosed} }) @@ -529,7 +562,6 @@ func (conn *Conn) sendError(err error, dest string, serial uint32) { } msg := new(Message) msg.Type = TypeError - msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) if dest != "" { msg.Headers[FieldDestination] = MakeVariant(dest) @@ -548,7 +580,6 @@ func (conn *Conn) sendError(err error, dest string, serial uint32) { func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { msg := new(Message) msg.Type = TypeMethodReply - msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) if dest != "" { msg.Headers[FieldDestination] = MakeVariant(dest) @@ -564,8 +595,14 @@ func (conn *Conn) sendReply(dest string, serial uint32, values ...interface{}) { // AddMatchSignal registers the given match rule to receive broadcast // signals based on their contents. func (conn *Conn) AddMatchSignal(options ...MatchOption) error { + return conn.AddMatchSignalContext(context.Background(), options...) +} + +// AddMatchSignalContext acts like AddMatchSignal but takes a context. +func (conn *Conn) AddMatchSignalContext(ctx context.Context, options ...MatchOption) error { options = append([]MatchOption{withMatchType("signal")}, options...) - return conn.busObj.Call( + return conn.busObj.CallWithContext( + ctx, "org.freedesktop.DBus.AddMatch", 0, formatMatchOptions(options), ).Store() @@ -573,8 +610,14 @@ func (conn *Conn) AddMatchSignal(options ...MatchOption) error { // RemoveMatchSignal removes the first rule that matches previously registered with AddMatchSignal. func (conn *Conn) RemoveMatchSignal(options ...MatchOption) error { + return conn.RemoveMatchSignalContext(context.Background(), options...) +} + +// RemoveMatchSignalContext acts like RemoveMatchSignal but takes a context. +func (conn *Conn) RemoveMatchSignalContext(ctx context.Context, options ...MatchOption) error { options = append([]MatchOption{withMatchType("signal")}, options...) - return conn.busObj.Call( + return conn.busObj.CallWithContext( + ctx, "org.freedesktop.DBus.RemoveMatch", 0, formatMatchOptions(options), ).Store() @@ -639,10 +682,11 @@ func (e Error) Error() string { // Signal represents a D-Bus message of type Signal. The name member is given in // "interface.member" notation, e.g. org.freedesktop.D-Bus.NameLost. type Signal struct { - Sender string - Path ObjectPath - Name string - Body []interface{} + Sender string + Path ObjectPath + Name string + Body []interface{} + Sequence Sequence } // transport is a D-Bus transport. @@ -825,25 +869,25 @@ func (tracker *callTracker) track(sn uint32, call *Call) { tracker.lck.Unlock() } -func (tracker *callTracker) handleReply(msg *Message) uint32 { +func (tracker *callTracker) handleReply(sequence Sequence, msg *Message) uint32 { serial := msg.Headers[FieldReplySerial].value.(uint32) tracker.lck.RLock() _, ok := tracker.calls[serial] tracker.lck.RUnlock() if ok { - tracker.finalizeWithBody(serial, msg.Body) + tracker.finalizeWithBody(serial, sequence, msg.Body) } return serial } -func (tracker *callTracker) handleDBusError(msg *Message) uint32 { +func (tracker *callTracker) handleDBusError(sequence Sequence, msg *Message) uint32 { serial := msg.Headers[FieldReplySerial].value.(uint32) tracker.lck.RLock() _, ok := tracker.calls[serial] tracker.lck.RUnlock() if ok { name, _ := msg.Headers[FieldErrorName].value.(string) - tracker.finalizeWithError(serial, Error{name, msg.Body}) + tracker.finalizeWithError(serial, sequence, Error{name, msg.Body}) } return serial } @@ -856,7 +900,7 @@ func (tracker *callTracker) handleSendError(msg *Message, err error) { _, ok := tracker.calls[msg.serial] tracker.lck.RUnlock() if ok { - tracker.finalizeWithError(msg.serial, err) + tracker.finalizeWithError(msg.serial, NoSequence, err) } } @@ -871,7 +915,7 @@ func (tracker *callTracker) finalize(sn uint32) { } } -func (tracker *callTracker) finalizeWithBody(sn uint32, body []interface{}) { +func (tracker *callTracker) finalizeWithBody(sn uint32, sequence Sequence, body []interface{}) { tracker.lck.Lock() c, ok := tracker.calls[sn] if ok { @@ -880,11 +924,12 @@ func (tracker *callTracker) finalizeWithBody(sn uint32, body []interface{}) { tracker.lck.Unlock() if ok { c.Body = body + c.ResponseSequence = sequence c.done() } } -func (tracker *callTracker) finalizeWithError(sn uint32, err error) { +func (tracker *callTracker) finalizeWithError(sn uint32, sequence Sequence, err error) { tracker.lck.Lock() c, ok := tracker.calls[sn] if ok { @@ -893,11 +938,12 @@ func (tracker *callTracker) finalizeWithError(sn uint32, err error) { tracker.lck.Unlock() if ok { c.Err = err + c.ResponseSequence = sequence c.done() } } -func (tracker *callTracker) finalizeAllWithError(err error) { +func (tracker *callTracker) finalizeAllWithError(sequenceGen *sequenceGenerator, err error) { tracker.lck.Lock() closedCalls := make([]*Call, 0, len(tracker.calls)) for sn := range tracker.calls { @@ -907,6 +953,7 @@ func (tracker *callTracker) finalizeAllWithError(err error) { tracker.lck.Unlock() for _, call := range closedCalls { call.Err = err + call.ResponseSequence = sequenceGen.next() call.done() } } diff --git a/vendor/github.com/godbus/dbus/v5/dbus.go b/vendor/github.com/godbus/dbus/v5/dbus.go index 428923d266..ddf3b7afde 100644 --- a/vendor/github.com/godbus/dbus/v5/dbus.go +++ b/vendor/github.com/godbus/dbus/v5/dbus.go @@ -28,6 +28,7 @@ var ( interfaceType = reflect.TypeOf((*interface{})(nil)).Elem() unixFDType = reflect.TypeOf(UnixFD(0)) unixFDIndexType = reflect.TypeOf(UnixFDIndex(0)) + errType = reflect.TypeOf((*error)(nil)).Elem() ) // An InvalidTypeError signals that a value which cannot be represented in the @@ -63,6 +64,9 @@ func storeInterfaces(src, dest interface{}) error { func store(dest, src reflect.Value) error { if dest.Kind() == reflect.Ptr { + if dest.IsNil() { + dest.Set(reflect.New(dest.Type().Elem())) + } return store(dest.Elem(), src) } switch src.Kind() { diff --git a/vendor/github.com/godbus/dbus/v5/default_handler.go b/vendor/github.com/godbus/dbus/v5/default_handler.go index 6d8bf32f9f..13132c6b47 100644 --- a/vendor/github.com/godbus/dbus/v5/default_handler.go +++ b/vendor/github.com/godbus/dbus/v5/default_handler.go @@ -126,14 +126,28 @@ func (m exportedMethod) Call(args ...interface{}) ([]interface{}, error) { } ret := m.Value.Call(params) - - err := ret[t.NumOut()-1].Interface().(*Error) - ret = ret[:t.NumOut()-1] + var err error + nilErr := false // The reflection will find almost-nils, let's only pass back clean ones! + if t.NumOut() > 0 { + if e, ok := ret[t.NumOut()-1].Interface().(*Error); ok { // godbus *Error + nilErr = ret[t.NumOut()-1].IsNil() + ret = ret[:t.NumOut()-1] + err = e + } else if ret[t.NumOut()-1].Type().Implements(errType) { // Go error + i := ret[t.NumOut()-1].Interface() + if i == nil { + nilErr = ret[t.NumOut()-1].IsNil() + } else { + err = i.(error) + } + ret = ret[:t.NumOut()-1] + } + } out := make([]interface{}, len(ret)) for i, val := range ret { out[i] = val.Interface() } - if err == nil { + if nilErr || err == nil { //concrete type to interface nil is a special case return out, nil } diff --git a/vendor/github.com/godbus/dbus/v5/export.go b/vendor/github.com/godbus/dbus/v5/export.go index c277ab1426..2447b51d46 100644 --- a/vendor/github.com/godbus/dbus/v5/export.go +++ b/vendor/github.com/godbus/dbus/v5/export.go @@ -69,6 +69,22 @@ func getMethods(in interface{}, mapping map[string]string) map[string]reflect.Va return methods } +func getAllMethods(in interface{}, mapping map[string]string) map[string]reflect.Value { + if in == nil { + return nil + } + methods := make(map[string]reflect.Value) + val := reflect.ValueOf(in) + typ := val.Type() + for i := 0; i < typ.NumMethod(); i++ { + methtype := typ.Method(i) + method := val.Method(i) + // map names while building table + methods[computeMethodName(methtype.Name, mapping)] = method + } + return methods +} + func standardMethodArgumentDecode(m Method, sender string, msg *Message, body []interface{}) ([]interface{}, error) { pointers := make([]interface{}, m.NumArguments()) decode := make([]interface{}, 0, len(body)) @@ -159,7 +175,6 @@ func (conn *Conn) handleCall(msg *Message) { if msg.Flags&FlagNoReplyExpected == 0 { reply := new(Message) reply.Type = TypeMethodReply - reply.serial = conn.getSerial() reply.Headers = make(map[HeaderField]Variant) if hasSender { reply.Headers[FieldDestination] = msg.Headers[FieldSender] @@ -195,7 +210,6 @@ func (conn *Conn) Emit(path ObjectPath, name string, values ...interface{}) erro } msg := new(Message) msg.Type = TypeSignal - msg.serial = conn.getSerial() msg.Headers = make(map[HeaderField]Variant) msg.Headers[FieldInterface] = MakeVariant(iface) msg.Headers[FieldMember] = MakeVariant(member) @@ -247,6 +261,18 @@ func (conn *Conn) Export(v interface{}, path ObjectPath, iface string) error { return conn.ExportWithMap(v, nil, path, iface) } +// ExportAll registers all exported methods defined by the given object on +// the message bus. +// +// Unlike Export there is no requirement to have the last parameter as type +// *Error. If you want to be able to return error then you can append an error +// type parameter to your method signature. If the error returned is not nil, +// it is sent back to the caller as an error. Otherwise, a method reply is +// sent with the other return values as its body. +func (conn *Conn) ExportAll(v interface{}, path ObjectPath, iface string) error { + return conn.export(getAllMethods(v, nil), path, iface, false) +} + // ExportWithMap works exactly like Export but provides the ability to remap // method names (e.g. export a lower-case method). // @@ -299,19 +325,22 @@ func (conn *Conn) ExportSubtreeMethodTable(methods map[string]interface{}, path } func (conn *Conn) exportMethodTable(methods map[string]interface{}, path ObjectPath, iface string, includeSubtree bool) error { - out := make(map[string]reflect.Value) - for name, method := range methods { - rval := reflect.ValueOf(method) - if rval.Kind() != reflect.Func { - continue - } - t := rval.Type() - // only track valid methods must return *Error as last arg - if t.NumOut() == 0 || - t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) { - continue + var out map[string]reflect.Value + if methods != nil { + out = make(map[string]reflect.Value) + for name, method := range methods { + rval := reflect.ValueOf(method) + if rval.Kind() != reflect.Func { + continue + } + t := rval.Type() + // only track valid methods must return *Error as last arg + if t.NumOut() == 0 || + t.Out(t.NumOut()-1) != reflect.TypeOf(&ErrMsgInvalidArg) { + continue + } + out[name] = rval } - out[name] = rval } return conn.export(out, path, iface, includeSubtree) } @@ -327,12 +356,12 @@ func (conn *Conn) unexport(h *defaultHandler, path ObjectPath, iface string) err return nil } -// exportWithMap is the worker function for all exports/registrations. +// export is the worker function for all exports/registrations. func (conn *Conn) export(methods map[string]reflect.Value, path ObjectPath, iface string, includeSubtree bool) error { h, ok := conn.handler.(*defaultHandler) if !ok { return fmt.Errorf( - `dbus: export only allowed on the default hander handler have %T"`, + `dbus: export only allowed on the default handler. Received: %T"`, conn.handler) } diff --git a/vendor/github.com/godbus/dbus/v5/match.go b/vendor/github.com/godbus/dbus/v5/match.go index 086ee336a9..5a607e53e4 100644 --- a/vendor/github.com/godbus/dbus/v5/match.go +++ b/vendor/github.com/godbus/dbus/v5/match.go @@ -1,6 +1,7 @@ package dbus import ( + "strconv" "strings" ) @@ -60,3 +61,29 @@ func WithMatchPathNamespace(namespace ObjectPath) MatchOption { func WithMatchDestination(destination string) MatchOption { return WithMatchOption("destination", destination) } + +// WithMatchArg sets argN match option, range of N is 0 to 63. +func WithMatchArg(argIdx int, value string) MatchOption { + if argIdx < 0 || argIdx > 63 { + panic("range of argument index is 0 to 63") + } + return WithMatchOption("arg"+strconv.Itoa(argIdx), value) +} + +// WithMatchArgPath sets argN path match option, range of N is 0 to 63. +func WithMatchArgPath(argIdx int, path string) MatchOption { + if argIdx < 0 || argIdx > 63 { + panic("range of argument index is 0 to 63") + } + return WithMatchOption("arg"+strconv.Itoa(argIdx)+"path", path) +} + +// WithMatchArg0Namespace sets arg0namespace match option. +func WithMatchArg0Namespace(arg0Namespace string) MatchOption { + return WithMatchOption("arg0namespace", arg0Namespace) +} + +// WithMatchEavesdrop sets eavesdrop match option. +func WithMatchEavesdrop(eavesdrop bool) MatchOption { + return WithMatchOption("eavesdrop", strconv.FormatBool(eavesdrop)) +} diff --git a/vendor/github.com/godbus/dbus/v5/object.go b/vendor/github.com/godbus/dbus/v5/object.go index 8acd7fc8b1..664abb7fba 100644 --- a/vendor/github.com/godbus/dbus/v5/object.go +++ b/vendor/github.com/godbus/dbus/v5/object.go @@ -16,6 +16,7 @@ type BusObject interface { AddMatchSignal(iface, member string, options ...MatchOption) *Call RemoveMatchSignal(iface, member string, options ...MatchOption) *Call GetProperty(p string) (Variant, error) + StoreProperty(p string, value interface{}) error SetProperty(p string, v interface{}) error Destination() string Path() ObjectPath @@ -109,7 +110,6 @@ func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch method = method[i+1:] msg := new(Message) msg.Type = TypeMethodCall - msg.serial = o.conn.getSerial() msg.Flags = flags & (FlagNoAutoStart | FlagNoReplyExpected) msg.Headers = make(map[HeaderField]Variant) msg.Headers[FieldPath] = MakeVariant(o.path) @@ -122,68 +122,31 @@ func (o *Object) createCall(ctx context.Context, method string, flags Flags, ch if len(args) > 0 { msg.Headers[FieldSignature] = MakeVariant(SignatureOf(args...)) } - if msg.Flags&FlagNoReplyExpected == 0 { - if ch == nil { - ch = make(chan *Call, 1) - } else if cap(ch) == 0 { - panic("dbus: unbuffered channel passed to (*Object).Go") - } - ctx, cancel := context.WithCancel(ctx) - call := &Call{ - Destination: o.dest, - Path: o.path, - Method: method, - Args: args, - Done: ch, - ctxCanceler: cancel, - ctx: ctx, - } - o.conn.calls.track(msg.serial, call) - o.conn.sendMessageAndIfClosed(msg, func() { - o.conn.calls.handleSendError(msg, ErrClosed) - cancel() - }) - go func() { - <-ctx.Done() - o.conn.calls.handleSendError(msg, ctx.Err()) - }() - - return call - } - done := make(chan *Call, 1) - call := &Call{ - Err: nil, - Done: done, - } - defer func() { - call.Done <- call - close(done) - }() - o.conn.sendMessageAndIfClosed(msg, func() { - call.Err = ErrClosed - }) - return call + return o.conn.SendWithContext(ctx, msg, ch) } // GetProperty calls org.freedesktop.DBus.Properties.Get on the given // object. The property name must be given in interface.member notation. func (o *Object) GetProperty(p string) (Variant, error) { + var result Variant + err := o.StoreProperty(p, &result) + return result, err +} + +// StoreProperty calls org.freedesktop.DBus.Properties.Get on the given +// object. The property name must be given in interface.member notation. +// It stores the returned property into the provided value. +func (o *Object) StoreProperty(p string, value interface{}) error { idx := strings.LastIndex(p, ".") if idx == -1 || idx+1 == len(p) { - return Variant{}, errors.New("dbus: invalid property " + p) + return errors.New("dbus: invalid property " + p) } iface := p[:idx] prop := p[idx+1:] - result := Variant{} - err := o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop).Store(&result) - - if err != nil { - return Variant{}, err - } - - return result, nil + return o.Call("org.freedesktop.DBus.Properties.Get", 0, iface, prop). + Store(value) } // SetProperty calls org.freedesktop.DBus.Properties.Set on the given diff --git a/vendor/github.com/godbus/dbus/v5/sequence.go b/vendor/github.com/godbus/dbus/v5/sequence.go new file mode 100644 index 0000000000..89435d3933 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/sequence.go @@ -0,0 +1,24 @@ +package dbus + +// Sequence represents the value of a monotonically increasing counter. +type Sequence uint64 + +const ( + // NoSequence indicates the absence of a sequence value. + NoSequence Sequence = 0 +) + +// sequenceGenerator represents a monotonically increasing counter. +type sequenceGenerator struct { + nextSequence Sequence +} + +func (generator *sequenceGenerator) next() Sequence { + result := generator.nextSequence + generator.nextSequence++ + return result +} + +func newSequenceGenerator() *sequenceGenerator { + return &sequenceGenerator{nextSequence: 1} +} diff --git a/vendor/github.com/godbus/dbus/v5/sequential_handler.go b/vendor/github.com/godbus/dbus/v5/sequential_handler.go new file mode 100644 index 0000000000..ef2fcdba17 --- /dev/null +++ b/vendor/github.com/godbus/dbus/v5/sequential_handler.go @@ -0,0 +1,125 @@ +package dbus + +import ( + "sync" +) + +// NewSequentialSignalHandler returns an instance of a new +// signal handler that guarantees sequential processing of signals. It is a +// guarantee of this signal handler that signals will be written to +// channels in the order they are received on the DBus connection. +func NewSequentialSignalHandler() SignalHandler { + return &sequentialSignalHandler{} +} + +type sequentialSignalHandler struct { + mu sync.RWMutex + closed bool + signals []*sequentialSignalChannelData +} + +func (sh *sequentialSignalHandler) DeliverSignal(intf, name string, signal *Signal) { + sh.mu.RLock() + defer sh.mu.RUnlock() + if sh.closed { + return + } + for _, scd := range sh.signals { + scd.deliver(signal) + } +} + +func (sh *sequentialSignalHandler) Terminate() { + sh.mu.Lock() + defer sh.mu.Unlock() + if sh.closed { + return + } + + for _, scd := range sh.signals { + scd.close() + close(scd.ch) + } + sh.closed = true + sh.signals = nil +} + +func (sh *sequentialSignalHandler) AddSignal(ch chan<- *Signal) { + sh.mu.Lock() + defer sh.mu.Unlock() + if sh.closed { + return + } + sh.signals = append(sh.signals, newSequentialSignalChannelData(ch)) +} + +func (sh *sequentialSignalHandler) RemoveSignal(ch chan<- *Signal) { + sh.mu.Lock() + defer sh.mu.Unlock() + if sh.closed { + return + } + for i := len(sh.signals) - 1; i >= 0; i-- { + if ch == sh.signals[i].ch { + sh.signals[i].close() + copy(sh.signals[i:], sh.signals[i+1:]) + sh.signals[len(sh.signals)-1] = nil + sh.signals = sh.signals[:len(sh.signals)-1] + } + } +} + +type sequentialSignalChannelData struct { + ch chan<- *Signal + in chan *Signal + done chan struct{} +} + +func newSequentialSignalChannelData(ch chan<- *Signal) *sequentialSignalChannelData { + scd := &sequentialSignalChannelData{ + ch: ch, + in: make(chan *Signal), + done: make(chan struct{}), + } + go scd.bufferSignals() + return scd +} + +func (scd *sequentialSignalChannelData) bufferSignals() { + defer close(scd.done) + + // Ensure that signals are delivered to scd.ch in the same + // order they are received from scd.in. + var queue []*Signal + for { + if len(queue) == 0 { + signal, ok := <- scd.in + if !ok { + return + } + queue = append(queue, signal) + } + select { + case scd.ch <- queue[0]: + copy(queue, queue[1:]) + queue[len(queue)-1] = nil + queue = queue[:len(queue)-1] + case signal, ok := <-scd.in: + if !ok { + return + } + queue = append(queue, signal) + } + } +} + +func (scd *sequentialSignalChannelData) deliver(signal *Signal) { + scd.in <- signal +} + +func (scd *sequentialSignalChannelData) close() { + close(scd.in) + // Ensure that bufferSignals() has exited and won't attempt + // any future sends on scd.ch + <-scd.done +} diff --git a/vendor/github.com/godbus/dbus/v5/sig.go b/vendor/github.com/godbus/dbus/v5/sig.go index c1b809202c..2d326cebc0 100644 --- a/vendor/github.com/godbus/dbus/v5/sig.go +++ b/vendor/github.com/godbus/dbus/v5/sig.go @@ -137,7 +137,7 @@ func ParseSignatureMust(s string) Signature { return sig } -// Empty retruns whether the signature is the empty signature. +// Empty returns whether the signature is the empty signature. func (s Signature) Empty() bool { return s.str == "" } diff --git a/vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go b/vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go index 0fc5b92739..1b5ed2089d 100644 --- a/vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go +++ b/vendor/github.com/godbus/dbus/v5/transport_unixcred_freebsd.go @@ -10,6 +10,7 @@ package dbus /* const int sizeofPtr = sizeof(void*); #define _WANT_UCRED +#include #include */ import "C" diff --git a/vendor/github.com/godbus/dbus/v5/variant.go b/vendor/github.com/godbus/dbus/v5/variant.go index 5b51828c82..f1e81f3ede 100644 --- a/vendor/github.com/godbus/dbus/v5/variant.go +++ b/vendor/github.com/godbus/dbus/v5/variant.go @@ -142,3 +142,9 @@ func (v Variant) String() string { func (v Variant) Value() interface{} { return v.value } + +// Store converts the variant into a native go type using the same +// mechanism as the "Store" function. +func (v Variant) Store(value interface{}) error { + return storeInterfaces(v.value, value) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 96f646bd03..f0cea6fed1 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -10,7 +10,7 @@ github.com/davecgh/go-spew/spew github.com/fredbi/uri # github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify -# github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f +# github.com/fyne-io/mobile v0.1.3-0.20210412090810-650a3139866a github.com/fyne-io/mobile/app github.com/fyne-io/mobile/app/internal/callfn github.com/fyne-io/mobile/event/key @@ -25,7 +25,7 @@ github.com/fyne-io/mobile/internal/mobileinit # github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 github.com/go-gl/gl/v3.1/gles2 github.com/go-gl/gl/v3.2-core/gl -# github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48 +# github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb github.com/go-gl/glfw/v3.3/glfw github.com/go-gl/glfw/v3.3/glfw/glfw/deps github.com/go-gl/glfw/v3.3/glfw/glfw/deps/glad @@ -33,7 +33,7 @@ github.com/go-gl/glfw/v3.3/glfw/glfw/deps/mingw github.com/go-gl/glfw/v3.3/glfw/glfw/deps/vs2008 github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW github.com/go-gl/glfw/v3.3/glfw/glfw/src -# github.com/godbus/dbus/v5 v5.0.3 +# github.com/godbus/dbus/v5 v5.0.4 github.com/godbus/dbus/v5 # github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff github.com/goki/freetype diff --git a/widget/button.go b/widget/button.go index 07e390287e..a41169bd01 100644 --- a/widget/button.go +++ b/widget/button.go @@ -66,7 +66,6 @@ type Button struct { hovered bool tapAnim *fyne.Animation - tapBG *canvas.Rectangle } // NewButton creates a new button widget with the set label and tap handler @@ -99,10 +98,12 @@ func (b *Button) CreateRenderer() fyne.WidgetRenderer { text.TextStyle.Bold = true background := canvas.NewRectangle(theme.ButtonColor()) - b.tapBG = canvas.NewRectangle(color.Transparent) + tapBG := canvas.NewRectangle(color.Transparent) + b.tapAnim = newButtonTapAnimation(tapBG, b) + b.tapAnim.Curve = fyne.AnimationEaseOut objects := []fyne.CanvasObject{ background, - b.tapBG, + tapBG, text, } shadowLevel := widget.ButtonLevel @@ -112,6 +113,7 @@ func (b *Button) CreateRenderer() fyne.WidgetRenderer { r := &buttonRenderer{ ShadowingRenderer: widget.NewShadowingRenderer(objects, shadowLevel), background: background, + tapBG: tapBG, button: b, label: text, layout: layout.NewHBoxLayout(), @@ -177,17 +179,10 @@ func (b *Button) Tapped(*fyne.PointEvent) { } func (b *Button) tapAnimation() { - if b.tapBG == nil { // not rendered yet? (tests) - return - } - if b.tapAnim == nil { - b.tapAnim = newButtonTapAnimation(b.tapBG, b) - b.tapAnim.Curve = fyne.AnimationEaseOut - } else { - b.tapAnim.Stop() + return } - + b.tapAnim.Stop() b.tapAnim.Start() } @@ -197,6 +192,7 @@ type buttonRenderer struct { icon *canvas.Image label *canvas.Text background *canvas.Rectangle + tapBG *canvas.Rectangle button *Button layout fyne.Layout } @@ -351,7 +347,7 @@ func (r *buttonRenderer) updateIconAndText() { if r.icon == nil { r.icon = canvas.NewImageFromResource(r.button.Icon) r.icon.FillMode = canvas.ImageFillContain - r.SetObjects([]fyne.CanvasObject{r.background, r.button.tapBG, r.label, r.icon}) + r.SetObjects([]fyne.CanvasObject{r.background, r.tapBG, r.label, r.icon}) } if r.button.Disabled() { r.icon.Resource = theme.NewDisabledResource(r.button.Icon) diff --git a/widget/button_internal_test.go b/widget/button_internal_test.go index e37caf4e2a..6573462bb1 100644 --- a/widget/button_internal_test.go +++ b/widget/button_internal_test.go @@ -5,6 +5,7 @@ import ( "testing" "fyne.io/fyne/v2" + "fyne.io/fyne/v2/internal/cache" "fyne.io/fyne/v2/internal/widget" "fyne.io/fyne/v2/test" "fyne.io/fyne/v2/theme" @@ -157,3 +158,33 @@ func TestButtonRenderer_ApplyTheme(t *testing.T) { assert.NotEqual(t, textSize, customTextSize) } + +func TestButtonRenderer_TapAnimation(t *testing.T) { + test.NewApp() + defer test.NewApp() + + button := NewButton("Hi", func() {}) + w := test.NewWindow(button) + defer w.Close() + w.Resize(fyne.NewSize(50, 50).Add(fyne.NewSize(10, 10))) + button.Resize(fyne.NewSize(50, 50)) + + test.ApplyTheme(t, test.NewTheme()) + button.Refresh() + + render1 := test.WidgetRenderer(button).(*buttonRenderer) + test.Tap(button) + button.tapAnim.Tick(0.5) + test.AssertImageMatches(t, "button/tap_animation.png", w.Canvas().Capture()) + + cache.DestroyRenderer(button) + button.Refresh() + + render2 := test.WidgetRenderer(button).(*buttonRenderer) + + assert.NotEqual(t, render1, render2) + + test.Tap(button) + button.tapAnim.Tick(0.5) + test.AssertImageMatches(t, "button/tap_animation.png", w.Canvas().Capture()) +} diff --git a/widget/entry.go b/widget/entry.go index 9b838a9d24..8e58391b35 100644 --- a/widget/entry.go +++ b/widget/entry.go @@ -1546,6 +1546,10 @@ func (r *entryContentRenderer) moveCursor() { } func (r *entryContentRenderer) updateScrollDirections() { + if r.content.scroll == nil { // not scrolling + return + } + switch r.content.entry.Wrapping { case fyne.TextWrapOff: r.content.scroll.Direction = widget.ScrollNone diff --git a/widget/form.go b/widget/form.go index c6b69660ac..489daf7ed7 100644 --- a/widget/form.go +++ b/widget/form.go @@ -67,7 +67,7 @@ func (f *Form) AppendItem(item *FormItem) { f.Items = append(f.Items, item) if f.itemGrid != nil { f.itemGrid.Add(f.createLabel(item.Text)) - f.itemGrid.Add(item.Widget) + f.itemGrid.Add(f.createInput(item)) f.setUpValidation(item.Widget, len(f.Items)-1) } diff --git a/widget/form_test.go b/widget/form_test.go index 5e037296ce..9537fa4f53 100644 --- a/widget/form_test.go +++ b/widget/form_test.go @@ -232,3 +232,30 @@ func TestForm_EntryValidation_FirstTypeValid(t *testing.T) { test.AssertImageMatches(t, "form/validation_entry_first_type_invalid.png", w.Canvas().Capture()) } + +func TestForm_HintsRendered(t *testing.T) { + app := test.NewApp() + defer test.NewApp() + app.Settings().SetTheme(theme.LightTheme()) + + f := NewForm() + + fi1 := NewFormItem("Form Item 1", NewEntry()) + fi1.HintText = "HT1" + f.AppendItem(fi1) + + fi2 := NewFormItem("Form Item 2", NewEntry()) + fi2.HintText = "HT2" + + f.AppendItem(fi2) + + fi3 := NewFormItem("Form Item 3", NewEntry()) + fi3.HintText = "HT3" + + f.AppendItem(fi3) + + w := test.NewWindow(f) + defer w.Close() + + test.AssertImageMatches(t, "form/hints_rendered.png", w.Canvas().Capture()) +} diff --git a/widget/icon.go b/widget/icon.go index b2c6dd7e72..df70b20fd4 100644 --- a/widget/icon.go +++ b/widget/icon.go @@ -9,6 +9,8 @@ import ( type iconRenderer struct { widget.BaseRenderer + raster *canvas.Image + image *Icon } @@ -26,25 +28,16 @@ func (i *iconRenderer) Layout(size fyne.Size) { } func (i *iconRenderer) Refresh() { - if i.image.Resource != i.image.cachedRes { - i.image.propertyLock.RLock() - i.updateObjects() - i.image.cachedRes = i.image.Resource - i.image.propertyLock.RUnlock() + if i.image.Resource == i.image.cachedRes { + return } - i.Layout(i.image.Size()) - canvas.Refresh(i.image.super()) -} + i.image.propertyLock.RLock() + i.raster.Resource = i.image.Resource + i.image.cachedRes = i.image.Resource + i.image.propertyLock.RUnlock() -func (i *iconRenderer) updateObjects() { - var objects []fyne.CanvasObject - if i.image.Resource != nil { - raster := canvas.NewImageFromResource(i.image.Resource) - raster.FillMode = canvas.ImageFillContain - objects = append(objects, raster) - } - i.SetObjects(objects) + canvas.Refresh(i.image.super()) } // Icon widget is a basic image component that load's its resource to match the theme. @@ -58,7 +51,6 @@ type Icon struct { // SetResource updates the resource rendered in this icon widget func (i *Icon) SetResource(res fyne.Resource) { i.Resource = res - i.cachedRes = nil i.Refresh() } @@ -73,8 +65,12 @@ func (i *Icon) CreateRenderer() fyne.WidgetRenderer { i.ExtendBaseWidget(i) i.propertyLock.RLock() defer i.propertyLock.RUnlock() - r := &iconRenderer{image: i} - r.updateObjects() + + img := canvas.NewImageFromResource(i.Resource) + img.FillMode = canvas.ImageFillContain + r := &iconRenderer{image: i, raster: img} + r.SetObjects([]fyne.CanvasObject{img}) + i.cachedRes = i.Resource return r } diff --git a/widget/icon_internal_test.go b/widget/icon_internal_test.go index fd88d9ab12..07733d66fe 100644 --- a/widget/icon_internal_test.go +++ b/widget/icon_internal_test.go @@ -26,7 +26,8 @@ func TestIcon_Nil(t *testing.T) { icon := NewIcon(nil) render := test.WidgetRenderer(icon) - assert.Equal(t, 0, len(render.Objects())) + assert.Equal(t, 1, len(render.Objects())) + assert.Nil(t, render.Objects()[0].(*canvas.Image).Resource) } func TestIcon_MinSize(t *testing.T) { diff --git a/widget/label.go b/widget/label.go index ccc6ada36b..f748af3a22 100644 --- a/widget/label.go +++ b/widget/label.go @@ -55,18 +55,7 @@ func NewLabelWithStyle(text string, alignment fyne.TextAlign, style fyne.TextSty func (l *Label) Bind(data binding.String) { l.Unbind() l.textSource = data - l.textListener = binding.NewDataListener(func() { - val, err := l.textSource.Get() - if err != nil { - fyne.LogError("Error getting current data value", err) - return - } - - l.Text = val - if cache.IsRendered(l) { - l.Refresh() - } - }) + l.createListener() data.AddListener(l.textListener) } @@ -109,15 +98,38 @@ func (l *Label) SetText(text string) { // // Since: 2.0 func (l *Label) Unbind() { - if l.textSource == nil || l.textListener == nil { + src := l.textSource + if src == nil { return } - l.textSource.RemoveListener(l.textListener) - l.textListener = nil + src.RemoveListener(l.textListener) l.textSource = nil } +func (l *Label) createListener() { + if l.textListener != nil { + return + } + + l.textListener = binding.NewDataListener(func() { + src := l.textSource + if src == nil { + return + } + val, err := src.Get() + if err != nil { + fyne.LogError("Error getting current data value", err) + return + } + + l.Text = val + if cache.IsRendered(l) { + l.Refresh() + } + }) +} + // textAlign tells the rendering textProvider our alignment func (l *Label) textAlign() fyne.TextAlign { return l.Alignment diff --git a/widget/select.go b/widget/select.go index 37d0ebbd3c..bc5e0e6973 100644 --- a/widget/select.go +++ b/widget/select.go @@ -24,7 +24,6 @@ type Select struct { hovered bool popUp *PopUpMenu tapAnim *fyne.Animation - tapBG *canvas.Rectangle } var _ fyne.Widget = (*Select)(nil) @@ -66,8 +65,10 @@ func (s *Select) CreateRenderer() fyne.WidgetRenderer { background := &canvas.Rectangle{} line := canvas.NewRectangle(theme.ShadowColor()) - s.tapBG = canvas.NewRectangle(color.Transparent) - objects := []fyne.CanvasObject{background, line, s.tapBG, txtProv, icon} + tapBG := canvas.NewRectangle(color.Transparent) + s.tapAnim = newButtonTapAnimation(tapBG, s) + s.tapAnim.Curve = fyne.AnimationEaseOut + objects := []fyne.CanvasObject{background, line, tapBG, txtProv, icon} r := &selectRenderer{icon, txtProv, background, line, objects, s} background.FillColor, line.FillColor = r.bgLineColor() r.updateIcon() @@ -224,24 +225,19 @@ func (s *Select) object() fyne.Widget { return nil } -func (s *Select) optionTapped(text string) { - s.SetSelected(text) - s.popUp = nil -} - func (s *Select) popUpPos() fyne.Position { buttonPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(s.super()) return buttonPos.Add(fyne.NewPos(0, s.Size().Height-theme.InputBorderSize())) } func (s *Select) showPopUp() { - var items []*fyne.MenuItem - for _, option := range s.Options { - text := option // capture - item := fyne.NewMenuItem(option, func() { - s.optionTapped(text) + items := make([]*fyne.MenuItem, len(s.Options)) + for i := range s.Options { + text := s.Options[i] // capture + items[i] = fyne.NewMenuItem(text, func() { + s.updateSelected(text) + s.popUp = nil }) - items = append(items, item) } c := fyne.CurrentApp().Driver().CanvasForObject(s.super()) @@ -251,17 +247,10 @@ func (s *Select) showPopUp() { } func (s *Select) tapAnimation() { - if s.tapBG == nil { // not rendered yet? (tests) - return - } - if s.tapAnim == nil { - s.tapAnim = newButtonTapAnimation(s.tapBG, s) - s.tapAnim.Curve = fyne.AnimationEaseOut - } else { - s.tapAnim.Stop() + return } - + s.tapAnim.Stop() s.tapAnim.Start() } diff --git a/widget/select_entry.go b/widget/select_entry.go index 92c878f93f..7df4a43393 100644 --- a/widget/select_entry.go +++ b/widget/select_entry.go @@ -59,14 +59,25 @@ func (e *SelectEntry) MinSize() fyne.Size { min := e.Entry.MinSize() if e.dropDown != nil { + padding := fyne.NewSize(4*theme.Padding(), 0) for _, item := range e.dropDown.Items { - itemMin := fyne.MeasureText(item.Label, theme.TextSize(), fyne.TextStyle{}).Add(fyne.NewSize(4*theme.Padding(), 0)) + itemMin := fyne.MeasureText(item.Label, theme.TextSize(), fyne.TextStyle{}).Add(padding) min = min.Max(itemMin) } } return min } +// Move changes the relative position of the select entry. +// +// Implements: fyne.Widget +func (e *SelectEntry) Move(pos fyne.Position) { + e.Entry.Move(pos) + if e.popUp != nil { + e.popUp.Move(e.popUpPos()) + } +} + // Resize changes the size of the select entry. // // Implements: fyne.Widget @@ -80,10 +91,10 @@ func (e *SelectEntry) Resize(size fyne.Size) { // SetOptions sets the options the user might select from. func (e *SelectEntry) SetOptions(options []string) { e.options = options - var items []*fyne.MenuItem - for _, option := range options { + items := make([]*fyne.MenuItem, len(options)) + for i, option := range options { option := option // capture - items = append(items, fyne.NewMenuItem(option, func() { e.SetText(option) })) + items[i] = fyne.NewMenuItem(option, func() { e.SetText(option) }) } e.dropDown = fyne.NewMenu("", items...) @@ -95,15 +106,17 @@ func (e *SelectEntry) SetOptions(options []string) { } } +func (e *SelectEntry) popUpPos() fyne.Position { + entryPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(e.super()) + return entryPos.Add(fyne.NewPos(0, e.Size().Height-theme.InputBorderSize())) +} + func (e *SelectEntry) setupDropDown() *Button { dropDownButton := NewButton("", func() { c := fyne.CurrentApp().Driver().CanvasForObject(e.super()) - entryPos := fyne.CurrentApp().Driver().AbsolutePositionForObject(e.super()) - popUpPos := entryPos.Add(fyne.NewPos(0, e.Size().Height-theme.InputBorderSize())) - e.popUp = NewPopUpMenu(e.dropDown, c) - e.popUp.ShowAtPosition(popUpPos) + e.popUp.ShowAtPosition(e.popUpPos()) e.popUp.Resize(fyne.NewSize(e.Size().Width, e.popUp.MinSize().Height)) }) dropDownButton.Importance = LowImportance diff --git a/widget/select_entry_test.go b/widget/select_entry_internal_test.go similarity index 85% rename from widget/select_entry_test.go rename to widget/select_entry_internal_test.go index 9d84f44adf..627ab5e971 100644 --- a/widget/select_entry_test.go +++ b/widget/select_entry_internal_test.go @@ -1,4 +1,4 @@ -package widget_test +package widget import ( "testing" @@ -6,7 +6,6 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/test" "fyne.io/fyne/v2/theme" - "fyne.io/fyne/v2/widget" "github.com/stretchr/testify/assert" ) @@ -16,7 +15,7 @@ func TestSelectEntry_Disableable(t *testing.T) { defer test.NewApp() options := []string{"A", "B", "C"} - e := widget.NewSelectEntry(options) + e := NewSelectEntry(options) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -54,7 +53,7 @@ func TestSelectEntry_DropDown(t *testing.T) { defer test.NewApp() options := []string{"A", "B", "C"} - e := widget.NewSelectEntry(options) + e := NewSelectEntry(options) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -81,12 +80,43 @@ func TestSelectEntry_DropDown(t *testing.T) { assert.Equal(t, "C", e.Text) } +func TestSelectEntry_DropDownMove(t *testing.T) { + test.NewApp() + defer test.NewApp() + + e := NewSelectEntry([]string{"one"}) + w := test.NewWindow(e) + defer w.Close() + entrySize := e.MinSize() + w.Resize(entrySize.Add(fyne.NewSize(100, 100))) + e.Resize(entrySize) + + // open the popup + test.Tap(e.ActionItem.(fyne.Tappable)) + + // first movement + e.Move(fyne.NewPos(10, 10)) + assert.Equal(t, fyne.NewPos(10, 10), e.Entry.Position()) + assert.Equal(t, + fyne.NewPos(10, 10+entrySize.Height-theme.InputBorderSize()), + e.popUp.Position(), + ) + + // second movement + e.Move(fyne.NewPos(30, 27)) + assert.Equal(t, fyne.NewPos(30, 27), e.Entry.Position()) + assert.Equal(t, + fyne.NewPos(30, 27+entrySize.Height-theme.InputBorderSize()), + e.popUp.Position(), + ) +} + func TestSelectEntry_DropDownResize(t *testing.T) { test.NewApp() defer test.NewApp() options := []string{"A", "B", "C"} - e := widget.NewSelectEntry(options) + e := NewSelectEntry(options) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -114,7 +144,7 @@ func TestSelectEntry_MinSize(t *testing.T) { largeOptions := []string{"Large Option A", "Larger Option B", "Very Large Option C"} largeOptionsMinWidth := optionsMinSize(largeOptions).Width - labelHeight := widget.NewLabel("W").MinSize().Height + labelHeight := NewLabel("W").MinSize().Height tests := map[string]struct { placeholder string @@ -150,7 +180,7 @@ func TestSelectEntry_MinSize(t *testing.T) { } for name, tt := range tests { t.Run(name, func(t *testing.T) { - e := widget.NewSelectEntry(tt.options) + e := NewSelectEntry(tt.options) e.PlaceHolder = tt.placeholder e.Text = tt.value assert.Equal(t, tt.want, e.MinSize()) @@ -162,7 +192,7 @@ func TestSelectEntry_SetOptions(t *testing.T) { test.NewApp() defer test.NewApp() - e := widget.NewSelectEntry([]string{"A", "B", "C"}) + e := NewSelectEntry([]string{"A", "B", "C"}) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -184,7 +214,7 @@ func TestSelectEntry_SetOptions_Empty(t *testing.T) { test.NewApp() defer test.NewApp() - e := widget.NewSelectEntry([]string{}) + e := NewSelectEntry([]string{}) w := test.NewWindow(e) defer w.Close() w.Resize(fyne.NewSize(150, 200)) @@ -203,13 +233,13 @@ func dropDownIconWidth() float32 { } func emptyTextWidth() float32 { - return widget.NewLabel("M").MinSize().Width + return NewLabel("M").MinSize().Width } func optionsMinSize(options []string) fyne.Size { - var labels []*widget.Label + var labels []*Label for _, option := range options { - labels = append(labels, widget.NewLabel(option)) + labels = append(labels, NewLabel(option)) } minWidth := float32(0) minHeight := float32(0) diff --git a/widget/select_internal_test.go b/widget/select_internal_test.go new file mode 100644 index 0000000000..90c86835c7 --- /dev/null +++ b/widget/select_internal_test.go @@ -0,0 +1,42 @@ +package widget + +import ( + "testing" + + "fyne.io/fyne/v2" + "fyne.io/fyne/v2/internal/cache" + "fyne.io/fyne/v2/test" + "github.com/stretchr/testify/assert" +) + +func TestSelectRenderer_TapAnimation(t *testing.T) { + test.NewApp() + defer test.NewApp() + + sel := NewSelect([]string{"one"}, func(s string) {}) + w := test.NewWindow(sel) + defer w.Close() + w.Resize(sel.MinSize().Add(fyne.NewSize(10, 10))) + sel.Resize(sel.MinSize()) + + test.ApplyTheme(t, test.NewTheme()) + sel.Refresh() + + render1 := test.WidgetRenderer(sel).(*selectRenderer) + test.Tap(sel) + sel.popUp.Hide() + sel.tapAnim.Tick(0.5) + test.AssertImageMatches(t, "select/tap_animation.png", w.Canvas().Capture()) + + cache.DestroyRenderer(sel) + sel.Refresh() + + render2 := test.WidgetRenderer(sel).(*selectRenderer) + + assert.NotEqual(t, render1, render2) + + test.Tap(sel) + sel.popUp.Hide() + sel.tapAnim.Tick(0.5) + test.AssertImageMatches(t, "select/tap_animation.png", w.Canvas().Capture()) +} diff --git a/widget/testdata/button/tap_animation.png b/widget/testdata/button/tap_animation.png new file mode 100644 index 0000000000..8f04fc5c90 Binary files /dev/null and b/widget/testdata/button/tap_animation.png differ diff --git a/widget/testdata/form/hints_rendered.png b/widget/testdata/form/hints_rendered.png new file mode 100644 index 0000000000..a62a2137d2 Binary files /dev/null and b/widget/testdata/form/hints_rendered.png differ diff --git a/widget/testdata/icon/layout_empty.xml b/widget/testdata/icon/layout_empty.xml index fee0aa6145..3a6aac5ba4 100644 --- a/widget/testdata/icon/layout_empty.xml +++ b/widget/testdata/icon/layout_empty.xml @@ -2,6 +2,7 @@ + diff --git a/widget/testdata/select/tap_animation.png b/widget/testdata/select/tap_animation.png new file mode 100644 index 0000000000..ac78da2709 Binary files /dev/null and b/widget/testdata/select/tap_animation.png differ diff --git a/widget/textgrid.go b/widget/textgrid.go index 3788ee775f..c5f5becb8f 100644 --- a/widget/textgrid.go +++ b/widget/textgrid.go @@ -6,6 +6,8 @@ import ( "math" "strings" + "fyne.io/fyne/v2/internal/cache" + "fyne.io/fyne/v2" "fyne.io/fyne/v2/canvas" "fyne.io/fyne/v2/theme" @@ -296,8 +298,8 @@ func (t *TextGrid) ensureCells(row, col int) { } func (t *TextGrid) refreshCell(row, col int) { - // TODO trigger single cell refresh - t.Refresh() + r := cache.Renderer(t).(*textGridRenderer) + r.refreshCell(row, col) } // NewTextGrid creates a new empty TextGrid widget. @@ -331,6 +333,19 @@ func (t *textGridRenderer) appendTextCell(str rune) { t.objects = append(t.objects, bg, text) } +func (t *textGridRenderer) refreshCell(row, col int) { + pos := row*t.cols + col + if pos*2+1 >= len(t.objects) { + return + } + + text := t.objects[pos*2+1].(*canvas.Text) + rect := t.objects[pos*2].(*canvas.Rectangle) + + canvas.Refresh(text) + canvas.Refresh(rect) +} + func (t *textGridRenderer) setCellRune(str rune, pos int, style, rowStyle TextGridStyle) { if str == 0 { str = ' ' @@ -343,11 +358,9 @@ func (t *textGridRenderer) setCellRune(str rune, pos int, style, rowStyle TextGr } else if rowStyle != nil && rowStyle.TextColor() != nil { fg = rowStyle.TextColor() } - if (text.Text == "" || str != []rune(text.Text)[0]) || fg != text.Color { - text.Text = string(str) - text.Color = fg - canvas.Refresh(text) - } + text.Text = string(str) + text.Color = fg + canvas.Refresh(text) rect := t.objects[pos*2].(*canvas.Rectangle) bg := color.Color(color.Transparent) @@ -356,10 +369,8 @@ func (t *textGridRenderer) setCellRune(str rune, pos int, style, rowStyle TextGr } else if rowStyle != nil && rowStyle.BackgroundColor() != nil { bg = rowStyle.BackgroundColor() } - if bg != rect.FillColor { - rect.FillColor = bg - canvas.Refresh(rect) - } + rect.FillColor = bg + canvas.Refresh(rect) } func (t *textGridRenderer) addCellsIfRequired() {