diff --git a/CHANGELOG.md b/CHANGELOG.md
index fcc4714e2b..50ad3e8977 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,35 @@
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.2 - 1 April 2021
+
+### Changed
+
+* Text can now be copied from a disable Entry using keyboard shortcuts
+
+### Fixed
+
+* Slider offset position could be incorrect for mobile apps
+* Correct error in example code
+* When graphics init fails then don't try to continue running (#1593)
+* Don't show global settings on mobile in fyne_demo as it's not supported (#2062)
+* Empty selection would render small rectangle in Entry
+* Do not show validation state for disabled Entry
+* dialog.ShowFileSave did not support mobile (#2076)
+* Fix issue that storage could not write to files on iOS and Android
+* mobile app could crash in some focus calls
+* Duplicate symbol error when compiling for Android with NDK 23 (#2064)
+* Add internet permission by default for Android apps (#1715)
+* Child and Parent support in storage were missing for mobile appps
+* Various crashes with Entry and multiline selections (including #1989)
+* Slider calls OnChanged for each value between steps (#1748)
+* fyne command doesn't remove temporary binary from src (#1910)
+* Advanced Color picker on mobile keeps updating values forever after sliding (#2075)
+* exec.Command and widget.Button combination not working (#1857)
+* After clicking a link on macOS, click everywhere in the app will be linked (#2112)
+* Text selection - Shift+Tab bug (#1787)
+
+
## 2.0.1 - 4 March 2021
### Changed
diff --git a/README.md b/README.md
index 552a88a870..27908ea92e 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-
+
diff --git a/app/app.go b/app/app.go
index dbc338b07b..b8c85ff3fe 100644
--- a/app/app.go
+++ b/app/app.go
@@ -90,6 +90,9 @@ func (app *fyneApp) Storage() fyne.Storage {
}
func (app *fyneApp) Preferences() fyne.Preferences {
+ if app.uniqueID == "" {
+ fyne.LogError("Preferences API requires a unique ID, use app.NewWithID()", nil)
+ }
return app.prefs
}
diff --git a/app/app_mobile_and.go b/app/app_mobile_and.go
index e68aa82a23..29d9657e38 100644
--- a/app/app_mobile_and.go
+++ b/app/app_mobile_and.go
@@ -16,6 +16,7 @@ import (
"log"
"net/url"
"os"
+ "path/filepath"
"unsafe"
mobileApp "github.com/fyne-io/mobile/app"
@@ -54,5 +55,5 @@ func rootConfigDir() string {
return "/data/data" // probably won't work, but we can't make a better guess
}
- return filesDir
+ return filepath.Join(filesDir, "fyne")
}
diff --git a/app/preferences_android.go b/app/preferences_android.go
index 7118f5f698..663abd28bb 100644
--- a/app/preferences_android.go
+++ b/app/preferences_android.go
@@ -7,12 +7,12 @@ import "path/filepath"
// storagePath returns the location of the settings storage
func (p *preferences) storagePath() string {
// we have no global storage, use app global instead - rootConfigDir looks up in app_mobile_and.go
- return filepath.Join(rootConfigDir(), "storage", "preferences.json")
+ return filepath.Join(p.app.storageRoot(), "preferences.json")
}
// storageRoot returns the location of the app storage
func (a *fyneApp) storageRoot() string {
- return filepath.Join(rootConfigDir(), a.uniqueID)
+ return rootConfigDir() // we are in a sandbox, so no app ID added to this path
}
func (p *preferences) watch() {
diff --git a/canvas/image.go b/canvas/image.go
index 36334ba372..53d4e6c9b2 100644
--- a/canvas/image.go
+++ b/canvas/image.go
@@ -71,6 +71,10 @@ func (i *Image) Alpha() float64 {
// Resize on an image will scale the content or reposition it according to FillMode.
// It will normally cause a Refresh to ensure the pixels are recalculated.
func (i *Image) Resize(s fyne.Size) {
+ if s == i.Size() {
+ return
+ }
+
i.baseObject.Resize(s)
Refresh(i)
diff --git a/canvas/raster.go b/canvas/raster.go
index 50972cb48d..9f809034e8 100644
--- a/canvas/raster.go
+++ b/canvas/raster.go
@@ -34,6 +34,10 @@ func (r *Raster) Alpha() float64 {
// Resize on a raster image causes the new size to be set and then calls Refresh.
// This causes the underlying data to be recalculated and a new output to be drawn.
func (r *Raster) Resize(s fyne.Size) {
+ if s == r.Size() {
+ return
+ }
+
r.baseObject.Resize(s)
Refresh(r)
}
diff --git a/cmd/fyne/commands/package.go b/cmd/fyne/commands/package.go
index ed29ed1227..86556640fa 100644
--- a/cmd/fyne/commands/package.go
+++ b/cmd/fyne/commands/package.go
@@ -3,6 +3,7 @@ package commands
import (
"flag"
"fmt"
+ "log"
"strconv"
"strings"
@@ -105,6 +106,7 @@ 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()
}
switch p.os {
@@ -123,6 +125,13 @@ func (p *packager) doPackage() error {
}
}
+func (p *packager) removeBuild() {
+ err := os.Remove(p.exe)
+ if err != nil {
+ log.Println("Unable to remove temporary build file", p.exe)
+ }
+}
+
func (p *packager) validate() error {
if p.os == "" {
p.os = targetOS()
diff --git a/cmd/fyne/internal/mobile/dex.go b/cmd/fyne/internal/mobile/dex.go
index f26a9eac7f..c8a8df6669 100644
--- a/cmd/fyne/internal/mobile/dex.go
+++ b/cmd/fyne/internal/mobile/dex.go
@@ -6,183 +6,191 @@
package mobile
-var dexStr = `ZGV4CjAzNQAClnuWucRCmShMpICNQdPrq7aZEIeogCKMJAAAcAAAAHhWNBIAAAAAAAAAAL` +
- `wjAAC3AAAAcAAAAC0AAABMAwAAOwAAAAAEAAASAAAAxAYAAGMAAABUBwAABgAAAGwKAABg` +
- `GQAALAsAAAIVAAAEFQAACRUAABEVAAAlFQAAPBUAAEwVAABSFQAAaRUAAGwVAABxFQAAdx` +
- `UAAHwVAACCFQAAhRUAAIkVAACOFQAAkhUAAJcVAACcFQAAuhUAANsVAAD2FQAAEBYAADMW` +
- `AABYFgAAcRYAAIQWAACgFgAAtRYAAMsWAADkFgAAABcAABQXAABJFwAAaRcAAJUXAACqFw` +
- `AA0RcAAOgXAAAFGAAANBgAAE8YAAB6GAAAnxgAAL8YAADPGAAA6RgAAAAZAAAfGQAAMxkA` +
- `AEkZAABdGQAAcRkAAIgZAACtGQAA0hkAAPcZAAAeGgAAQxoAAGYaAAB8GgAAhxoAAJAaAA` +
- `CqGgAArRoAALEaAAC2GgAAvRoAAMMaAADHGgAAzBoAANMaAADfGgAA5BoAAOcaAADrGgAA` +
- `8BoAAAUbAAAJGwAAFRsAACEbAAAtGwAAORsAAEUbAABRGwAAXhsAAGsbAAB7GwAAhRsAAK` +
- `AbAAC4GwAAyhsAAOAbAAAFHAAALxwAAFEcAAByHAAAixwAAJ4cAACsHAAAthwAAMUcAADV` +
- `HAAA5RwAAPUcAAD4HAAAAB0AACMdAAA3HQAARR0AAEodAABbHQAAbB0AAHkdAACHHQAAkB` +
- `0AAJ4dAACpHQAAtB0AAMcdAADUHQAA6R0AAPIdAAD9HQAADx4AACseAABFHgAAYB4AAHke` +
- `AACEHgAAjh4AAJkeAACpHgAAxx4AANkeAADhHgAA7x4AAAgfAAAWHwAAJR8AADUfAABEHw` +
- `AASh8AAFIfAABYHwAAZR8AAHkfAACiHwAArR8AALcfAAC9HwAAxx8AANkfAADjHwAA8x8A` +
- `AAIgAAAMIAAAGiAAAB8gAAAuIAAAPSAAAEsgAABcIAAAZSAAAG4gAAB9IAAAiSAAAJcgAA` +
- `ClIAAAtCAAALsgAADTIAAA4CAAAOggAADwIAAA+iAAAP8gAAAjIQAAMSEAAEMhAABKIQAA` +
- `USEAAAgAAAATAAAAFAAAABUAAAAWAAAAFwAAABgAAAAZAAAAGgAAABsAAAAcAAAAHQAAAB` +
- `4AAAAfAAAAIAAAACEAAAAiAAAAIwAAACQAAAAlAAAAJgAAACcAAAAoAAAAKQAAACoAAAAr` +
- `AAAALAAAAC0AAAAuAAAALwAAADAAAAAxAAAAMgAAADMAAAA0AAAANQAAADYAAAA3AAAAOA` +
- `AAADkAAAA6AAAAOwAAAEAAAABKAAAATQAAAAgAAAAAAAAAAAAAAAkAAAAAAAAA+BMAAAoA` +
- `AAAAAAAAABQAAAsAAAAAAAAADBQAAAwAAAAAAAAAFBQAAA0AAAACAAAAAAAAAA0AAAAEAA` +
- `AAAAAAAA4AAAAEAAAAIBQAABIAAAAEAAAAKBQAABAAAAAEAAAAMBQAABIAAAAEAAAAOBQA` +
- `ABEAAAAFAAAAQBQAAA0AAAAGAAAAAAAAAA0AAAAIAAAAAAAAAA0AAAALAAAAAAAAAA4AAA` +
- `AQAAAAIBQAAA0AAAASAAAAAAAAAA4AAAASAAAAIBQAAA0AAAAUAAAAAAAAAA0AAAAVAAAA` +
- `AAAAABAAAAAXAAAASBQAABIAAAAXAAAAUBQAAA0AAAAbAAAAAAAAAA8AAAAcAAAA+BMAAB` +
- `AAAAAfAAAAMBQAAA0AAAAhAAAAAAAAABAAAAAhAAAAMBQAABAAAAAhAAAASBQAABIAAAAh` +
- `AAAAWBQAAA0AAAApAAAAAAAAAEAAAAAqAAAAAAAAAEEAAAAqAAAAIBQAAEIAAAAqAAAA+B` +
- `MAAEMAAAAqAAAAYBQAAEQAAAAqAAAAbBQAAEUAAAAqAAAAeBQAAEYAAAAqAAAAgBQAAEUA` +
- `AAAqAAAAiBQAAEUAAAAqAAAAkBQAAEUAAAAqAAAAmBQAAEUAAAAqAAAA8BMAAEUAAAAqAA` +
- `AA6BMAAEgAAAAqAAAAoBQAAEkAAAAqAAAAuBQAAEUAAAAqAAAAwBQAAEUAAAAqAAAAyBQA` +
- `AEcAAAAqAAAA0BQAAEUAAAAqAAAA4BMAAEUAAAAqAAAAMBQAAEUAAAAqAAAA3BQAAEUAAA` +
- `AqAAAASBQAAEYAAAAqAAAA5BQAAEkAAAAqAAAAWBQAAEoAAAArAAAAAAAAAEwAAAArAAAA` +
- `7BQAAEwAAAArAAAA9BQAAEsAAAArAAAAyBQAAEsAAAArAAAA/BQAABAAAAAsAAAAMBQAAA` +
- `UACgCVAAAABwAAAI4AAAAHAAAAsAAAAAkAAAA+AAAAJAApAK0AAAAkAAAAswAAACUAKQCt` +
- `AAAAJgApAK0AAAAnACgArgAAACgAKQCtAAAAKQAAAAMAAAApAAAABAAAACkAAAAFAAAAKQ` +
- `AAADwAAAApAAAAPwAAACkAKQCGAAAAKQAXAJQAAAApACEAlwAAAAEAHgACAAAAAQAmAJkA` +
- `AAAEADAAAgAAAAQACQBWAAAABAAHAFgAAAAEAAgAZQAAAAQABQByAAAABAANAHMAAAAEAA` +
- `oAnAAAAAQACQCkAAAABgALAHAAAAAHAB4AAgAAAAcAAACHAAAABwAAALUAAAAIABkArwAA` +
- `AAoAGgB7AAAADgADAGkAAAAOAAQAaQAAABAAAQBuAAAAEAAPAJAAAAASACkAWQAAABIAAA` +
- `B1AAAAEgAQAHgAAAASABMAeQAAABIAAACCAAAAEgAOAIQAAAASACUAhQAAABQAEAB0AAAA` +
- `FQAAAH0AAAAVAAAAfgAAABUAAAB/AAAAFQAAAIAAAAAWADYAiQAAABYANwCpAAAAFwAjAA` +
- `IAAAAXACgAWgAAABcAHgBjAAAAFwA1AJ0AAAAXAB8AoAAAABcAHwChAAAAFwAsAKIAAAAX` +
- `AC0AowAAABcAHwClAAAAGAAgAAIAAAAbABkAbwAAABwAAACPAAAAHAAXAKwAAAAcABkArw` +
- `AAAB8AHgACAAAAIQA4AGQAAAAhADkAagAAACEAAACPAAAAIQA6AKoAAAAiADAAkQAAACQA` +
- `MwACAAAAJAAeAJ4AAAAlADIAAgAAACUAHgCeAAAAJgAyAAIAAAAmACoAmgAAACcAMQACAA` +
- `AAJwAnAFsAAAAnAC4AYgAAACcALgCbAAAAKAAyAAIAAAAoAB4AngAAACkAHgACAAAAKQAU` +
- `AE8AAAApABUAUAAAACkAGwBRAAAAKQAcAFIAAAApAB0AUwAAACkANABUAAAAKQArAFcAAA` +
- `ApAB4AZgAAACkAMABnAAAAKQAfAGgAAAApADAAbAAAACkAEQBtAAAAKQAWAHEAAAApAAYA` +
- `dgAAACkADAB3AAAAKQACAHoAAAApABgAfAAAACkAGQCBAAAAKQASAIMAAAApAB4AiAAAAC` +
- `kAIQCLAAAAKQAeAIwAAAApADAAjQAAACkAHgCQAAAAKQAiAJgAAAApACYAmQAAACkALwCf` +
- `AAAAKQAeAKYAAAApADAApwAAACkAHwCoAAAAKQAkAKsAAAApAB4AsgAAACQAAAAAAAAAHw` +
- `AAAOATAAAHAAAAkBMAAOAiAAAAAAAAJQAAAAAAAAAfAAAA4BMAAAcAAACgEwAA9CIAAAAA` +
- `AAAmAAAAAAAAAB8AAADoEwAABwAAALATAAAFIwAAAAAAACcAAAAAAAAAHwAAAPATAAAHAA` +
- `AAwBMAABYjAAAAAAAAKAAAAAAAAAAfAAAA4BMAAAcAAADQEwAALyMAAAAAAAApAAAAAQAA` +
- `AAEAAAAAAAAABwAAAAAAAABAIwAA0yIAAAIAAACnIgAAriIAAAIAAAC3IgAAriIAAAIAAA` +
- `C+IgAAriIAAAIAAADFIgAAriIAAAIAAADMIgAAriIAAAMAAwABAAAAVCEAAAgAAABbAQQA` +
- `WQIFAHAQMAAAAA4ABgABAAMAAABbIQAAeAAAABUBAEASYhIEFACQAAgAUlMFACsDZQAAAB` +
- `oCBgAaA7EAcSAQADIAVFIEAHEQQwACAAwCbiAmABIAVFEEAHEQQwABAAwBbiAnAAEAVFAE` +
- `ABoBAABxIEYAEABUUAQAcRBDAAAADAAaAQAAbiApABAAVFAEAHEQQwAAAAwAbiAqAEAAVF` +
- `AEAHEQQwAAAAwAbhAkAAAAVFAEAHEQQwAAAAwAbhAlAAAAVFAEABoBigBuIFMAEAAMAB8A` +
- `FgBUUQQAcRBDAAEADAFuMCEAEAQOAAEhKKwUAJIACAABISinAAAAAQMAAAAAAAoAAABdAA` +
- `AAXwAAAAIAAgABAAAAdiEAAAYAAABbAQYAcBAwAAAADgADAAEAAgAAAHwhAAAMAAAAVCAG` +
- `AHEQQwAAAAwAEwEIAG4gKgAQAA4AAgACAAEAAACCIQAABgAAAFsBBwBwEDAAAAAOAAsACg` +
- `ABAAAAiSEAAAYAAABUEAcAbhBiAAAADgACAAIAAQAAAJkhAAAGAAAAWwEIAHAQMAAAAA4A` +
- `AgACAAAAAACgIQAAAQAAAA4AAAAFAAUAAAAAAKchAAABAAAADgAAAAgABQADAAAAsSEAAF` +
- `AAAAByEC0ABAAKAFQxCABUEQkAcRBFAAEADAFuEDMAAQAKATcQLQBUMAgAVAAJAFQxCABU` +
- `EQkAcRBFAAEADAFuEDMAAQAKAXIQLQAEAAoCcjAuABQCDAFyEC8AAQAMAXEgSAAQAFQwCA` +
- `BUAAkAchAvAAQADAFxIEYAEAAOAHIQLQAEAAoAVDEIAFQRCQBxEEUAAQAMAW4QMwABAAoB` +
- `NRDk/yjiAgACAAEAAADFIQAABgAAAFsBCQBwEDAAAAAOAAUAAQADAAAAzCEAAE4AAAAS41` +
- `RACQAiARcAcQBHAAAADAJwICIAIQBxIEQAEABUQAkAcRBDAAAADAATAQgAbiAqABAAVEAJ` +
- `AHEQQwAAAAwAFAGQAAgAbiAnABAAIgAYAHAwKwAwA1RBCQBxEEMAAQAMAW4gKAABAFRBCQ` +
- `BUQgkAcRBDAAIADAJuMEkAIQBUQAkAcRBDAAAADAAiAScAcCA8AEEAbiAjABAADgACAAEA` +
- `AQAAANshAAAKAAAAcBAAAAEAGgAAAFsQEQBpAQ8ADgACAAEAAAAAAOMhAAADAAAAVBAQAB` +
- `EAAAACAAIAAAAAAOkhAAADAAAAWwEQABEBAAACAAEAAAAAAPAhAAADAAAAVBARABEAAAAC` +
- `AAIAAAAAAPYhAAADAAAAWwERABEBAAABAAAAAAAAAP0hAAADAAAAYgAPABEAAAACAAIAAg` +
- `AAAAIiAAAEAAAAcCBZABAADgAHAAMAAwABAAkiAAAZAAAAEvBxEBMABAAMAW4wEgBRBgoB` +
- `OQEDAA8AARAo/g0BGgIGABoDawBxMBEAMgEo9Q0BKPMAAAEAAAAHAAEAAQIPFx0OAAABAA` +
- `AAAQAAABoiAAAGAAAAYgAPAG4QSgAAAA4ABAABAAMAAQAgIgAAMwAAAG4QUQADAAwAbhBQ` +
- `AAMADAFuEAYAAQAMARMCgABuMAoAEAIMAFQBAAA5AQoAGgAGABoBkwBxIBAAEAAOAFQAAA` +
- `AaAVwAbiAPABAADABxEDUAAAAo9A0AGgEGABoCkgBxMBEAIQAo6wAAAAAAACkAAQABAR0q` +
- `AgABAAIAAAAxIgAACQAAACIAKABwIEAAEABuIF0AAQAOAAAAAgABAAIAAAA6IgAABgAAAG` +
- `IADwBuIEsAEAAOAAIAAQACAAAAQiIAAAYAAABiAA8AbiBMABAADgAEAAEAAwAAAEkiAAAk` +
- `AAAAGgCKAG4gUwADAAwAHwAWABQBAgACAW4gTgATAAwBbhAWAAEADAFuEBkAAQAMARICbj` +
- `AgABACIgAlAHAgOAAwAG4gXQADAA4ABgACAAMAAABSIgAAVwAAABITIgAEABoBXQBwIAIA` +
- `EAAaAWEAbiAyAFEACgE4ARwAYAEDABMCFQA0IRYAIgAEABoBXgBwIAIAEABuIAQAMAAaAT` +
- `0AcSAFABAADABuMGEABAMOABoBtgBuIDEAFQAKATgBHgBgAQMAEwITADQhGAAaAQEAbiAJ` +
- `ABAAGgFgABoCTgBuIDQAJQAMAm4wCAAQAhoBXwBuIAMAEAAo024gCQBQABoBXwBuIAMAEA` +
- `AoygAAAwACAAMAAABmIgAACQAAACIAJABwMDYAEAJuIF0AAQAOAAAAAgABAAEAAABvIgAA` +
- `CQAAAG4QTwABAAwAbhAsAAAADAARAAAABQAEAAIAAAB0IgAAGQAAABIQMgIDAA4AEvAyAw` +
- `gAGgAAAHAgTQABACj3bhAHAAQADABuEA4AAAAMAHAgTQABACjrAAAEAAIAAgAAAIYiAAAd` +
- `AAAAcBBaAAIAbyABADIAcBBeAAIAFAACAAIBbiBOAAIADABuEBYAAAAMACIBJgBwIDoAIQ` +
- `BuIBQAEAAOAAAABwABAAUAAQCSIgAAYAAAAG4QVQAGAAwAbhAbAAAADABuEBcAAAAMADkA` +
- `AwAOAG4QHwAAAAoBbhAcAAAACgJuEB0AAAAKA24QHgAAAAoAcFBXABYyKOwNACIABwBwEA` +
- `sAAABuEFUABgAMAW4QGwABAAwBbiAaAAEAFAECAAIBbiBOABYADAFuEBYAAQAMAVICAgBu` +
- `EBUAAQAKA24QDAAAAAoEsUNSBAIAsUNSBAEAbhAYAAEACgFuEA0AAAAKBbFRUgABAJEAAQ` +
- `BwUFcAJkMorwAAAAAiAAEAAQEeIywLAAAAAAAAAAAAAAAAAAA4CwAAAAAAAAAAAAAAAAAA` +
- `RAsAAAAAAAAAAAAAAAAAAFALAAAAAAAAAAAAAAAAAABcCwAAAAAAAAAAAAAAAAAAAQAAAC` +
- `AAAAABAAAAEQAAAAEAAAANAAAAAgAAAAAAAAADAAAAAAAAAAAAAAACAAAAIQAhAAMAAAAh` +
- `ACEAIwAAAAEAAAAAAAAAAgAAAAQAHAABAAAAIQAAAAIAAAAhACwAAgAAAAIAAAABAAAAKQ` +
- `AAAAIAAAApABcAAgAAACkAIQAEAAAAAAAAAAAAAAADAAAAAAAAAAQAAAABAAAAAwAAAAIA` +
- `AAAEAAAAAQAAAAcAAAABAAAACgAAAAEAAAAMAAAACQAAABIAAAAAAAAAAAAAAAAAAAAAAA` +
- `AAAgAAABIAEwABAAAAEwAAAAEAAAAcAAAABAAAABwAAAAAAAAAAQAAACgAAAACAAAAKQAA` +
- `AAIAAAALAAAAAgAAABIAAAABAAAAHwAAAAMqLyoABjxpbml0PgASREVGQVVMVF9JTlBVVF` +
- `9UWVBFABVERUZBVUxUX0tFWUJPQVJEX0NPREUADkZJTEVfT1BFTl9DT0RFAARGeW5lABVH` +
- `b05hdGl2ZUFjdGl2aXR5LmphdmEAAUkAA0lJSQAESUlJSQADSUxMAARJTExMAAFMAAJMSQ` +
- `ADTElJAAJMTAADTExJAANMTEwAHExhbmRyb2lkL2FwcC9OYXRpdmVBY3Rpdml0eTsAH0xh` +
- `bmRyb2lkL2NvbnRlbnQvQ29tcG9uZW50TmFtZTsAGUxhbmRyb2lkL2NvbnRlbnQvQ29udG` +
- `V4dDsAGExhbmRyb2lkL2NvbnRlbnQvSW50ZW50OwAhTGFuZHJvaWQvY29udGVudC9wbS9B` +
- `Y3Rpdml0eUluZm87ACNMYW5kcm9pZC9jb250ZW50L3BtL1BhY2thZ2VNYW5hZ2VyOwAXTG` +
- `FuZHJvaWQvZ3JhcGhpY3MvUmVjdDsAEUxhbmRyb2lkL25ldC9Vcmk7ABpMYW5kcm9pZC9v` +
- `cy9CdWlsZCRWRVJTSU9OOwATTGFuZHJvaWQvb3MvQnVuZGxlOwAUTGFuZHJvaWQvb3MvSU` +
- `JpbmRlcjsAF0xhbmRyb2lkL3RleHQvRWRpdGFibGU7ABpMYW5kcm9pZC90ZXh0L1RleHRX` +
- `YXRjaGVyOwASTGFuZHJvaWQvdXRpbC9Mb2c7ADNMYW5kcm9pZC92aWV3L0tleUNoYXJhY3` +
- `Rlck1hcCRVbmF2YWlsYWJsZUV4Y2VwdGlvbjsAHkxhbmRyb2lkL3ZpZXcvS2V5Q2hhcmFj` +
- `dGVyTWFwOwAqTGFuZHJvaWQvdmlldy9WaWV3JE9uTGF5b3V0Q2hhbmdlTGlzdGVuZXI7AB` +
- `NMYW5kcm9pZC92aWV3L1ZpZXc7ACVMYW5kcm9pZC92aWV3L1ZpZXdHcm91cCRMYXlvdXRQ` +
- `YXJhbXM7ABVMYW5kcm9pZC92aWV3L1dpbmRvdzsAG0xhbmRyb2lkL3ZpZXcvV2luZG93SW` +
- `5zZXRzOwAtTGFuZHJvaWQvdmlldy9pbnB1dG1ldGhvZC9JbnB1dE1ldGhvZE1hbmFnZXI7` +
- `ABlMYW5kcm9pZC93aWRnZXQvRWRpdFRleHQ7AClMYW5kcm9pZC93aWRnZXQvRnJhbWVMYX` +
- `lvdXQkTGF5b3V0UGFyYW1zOwAjTGRhbHZpay9hbm5vdGF0aW9uL0VuY2xvc2luZ01ldGhv` +
- `ZDsAHkxkYWx2aWsvYW5ub3RhdGlvbi9Jbm5lckNsYXNzOwAOTGphdmEvaW8vRmlsZTsAGE` +
- `xqYXZhL2xhbmcvQ2hhclNlcXVlbmNlOwAVTGphdmEvbGFuZy9FeGNlcHRpb247AB1MamF2` +
- `YS9sYW5nL05vU3VjaE1ldGhvZEVycm9yOwASTGphdmEvbGFuZy9PYmplY3Q7ABRMamF2YS` +
- `9sYW5nL1J1bm5hYmxlOwASTGphdmEvbGFuZy9TdHJpbmc7ABJMamF2YS9sYW5nL1N5c3Rl` +
- `bTsAFUxqYXZhL2xhbmcvVGhyb3dhYmxlOwAjTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQW` +
- `N0aXZpdHkkMTsAI0xvcmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2aXR5JDI7ACNMb3Jn` +
- `L2dvbGFuZy9hcHAvR29OYXRpdmVBY3Rpdml0eSQzOwAlTG9yZy9nb2xhbmcvYXBwL0dvTm` +
- `F0aXZlQWN0aXZpdHkkNCQxOwAjTG9yZy9nb2xhbmcvYXBwL0dvTmF0aXZlQWN0aXZpdHkk` +
- `NDsAIUxvcmcvZ29sYW5nL2FwcC9Hb05hdGl2ZUFjdGl2aXR5OwAUTlVNQkVSX0tFWUJPQV` +
- `JEX0NPREUACU9wZW4gRmlsZQAHU0RLX0lOVAAYU0lOR0xFTElORV9LRVlCT0FSRF9DT0RF` +
- `AAFWAAJWSQADVklJAAVWSUlJSQAEVklJTAACVkwAA1ZMSQAFVkxJSUkAClZMSUlJSUlJSU` +
- `kAA1ZMTAABWgACWkwAA1pMSQATW0xqYXZhL2xhbmcvU3RyaW5nOwACXHwACmFjY2VzcyQw` +
- `MDAACmFjY2VzcyQwMDIACmFjY2VzcyQxMDAACmFjY2VzcyQxMDIACmFjY2VzcyQyMDAACm` +
- `FjY2VzcyQzMDAAC2FjY2Vzc0ZsYWdzAAthZGRDYXRlZ29yeQAOYWRkQ29udGVudFZpZXcA` +
- `CGFkZEZsYWdzABlhZGRPbkxheW91dENoYW5nZUxpc3RlbmVyABZhZGRUZXh0Q2hhbmdlZE` +
- `xpc3RlbmVyABBhZnRlclRleHRDaGFuZ2VkABRhbmRyb2lkLmFwcC5saWJfbmFtZQAjYW5k` +
- `cm9pZC5pbnRlbnQuYWN0aW9uLk9QRU5fRE9DVU1FTlQAKGFuZHJvaWQuaW50ZW50LmFjdG` +
- `lvbi5PUEVOX0RPQ1VNRU5UX1RSRUUAIGFuZHJvaWQuaW50ZW50LmNhdGVnb3J5Lk9QRU5B` +
- `QkxFAB9hbmRyb2lkLmludGVudC5leHRyYS5NSU1FX1RZUEVTABdhcHBsaWNhdGlvbi94LW` +
- `RpcmVjdG9yeQARYmVmb3JlVGV4dENoYW5nZWQADGJyaW5nVG9Gcm9udAAIY29udGFpbnMA` +
- `DWNyZWF0ZUNob29zZXIADmRvSGlkZUtleWJvYXJkAA5kb1Nob3dGaWxlT3BlbgAOZG9TaG` +
- `93S2V5Ym9hcmQAAWUABmVxdWFscwAhZXhjZXB0aW9uIHJlYWRpbmcgS2V5Q2hhcmFjdGVy` +
- `TWFwABJmaWxlUGlja2VyUmV0dXJuZWQADGZpbmRWaWV3QnlJZAADZ2V0AA9nZXRBYnNvbH` +
- `V0ZVBhdGgAD2dldEFjdGl2aXR5SW5mbwALZ2V0Q2FjaGVEaXIADGdldENvbXBvbmVudAAH` +
- `Z2V0RGF0YQAMZ2V0RGVjb3JWaWV3AAlnZXRIZWlnaHQACWdldEludGVudAARZ2V0UGFja2` +
- `FnZU1hbmFnZXIAC2dldFJvb3RWaWV3ABNnZXRSb290V2luZG93SW5zZXRzAAdnZXRSdW5l` +
- `AAlnZXRTdHJpbmcAEGdldFN5c3RlbVNlcnZpY2UAGmdldFN5c3RlbVdpbmRvd0luc2V0Qm` +
- `90dG9tABhnZXRTeXN0ZW1XaW5kb3dJbnNldExlZnQAGWdldFN5c3RlbVdpbmRvd0luc2V0` +
- `UmlnaHQAF2dldFN5c3RlbVdpbmRvd0luc2V0VG9wAAlnZXRUbXBkaXIACGdldFdpZHRoAA` +
- `lnZXRXaW5kb3cADmdldFdpbmRvd1Rva2VuABxnZXRXaW5kb3dWaXNpYmxlRGlzcGxheUZy` +
- `YW1lABBnb05hdGl2ZUFjdGl2aXR5AAZoZWlnaHQADGhpZGVLZXlib2FyZAAXaGlkZVNvZn` +
- `RJbnB1dEZyb21XaW5kb3cADGlucHV0X21ldGhvZAANaW5zZXRzQ2hhbmdlZAAOa2V5Ym9h` +
- `cmREZWxldGUADWtleWJvYXJkVHlwZWQABGxlZnQABmxlbmd0aAAEbG9hZAALbG9hZExpYn` +
- `JhcnkAEmxvYWRMaWJyYXJ5IGZhaWxlZAAnbG9hZExpYnJhcnk6IG5vIG1hbmlmZXN0IG1l` +
- `dGFkYXRhIGZvdW5kAAltVGV4dEVkaXQACG1ldGFEYXRhAARuYW1lAAhvbGRTdGF0ZQAQb2` +
- `5BY3Rpdml0eVJlc3VsdAAIb25DcmVhdGUADm9uTGF5b3V0Q2hhbmdlAA1vblRleHRDaGFu` +
- `Z2VkAAhwdXRFeHRyYQAMcmVxdWVzdEZvY3VzAANydW4ADXJ1bk9uVWlUaHJlYWQADXNldE` +
- `ltZU9wdGlvbnMADHNldElucHV0VHlwZQAPc2V0TGF5b3V0UGFyYW1zAAdzZXRUZXh0AAdz` +
- `ZXRUeXBlAA1zZXRWaXNpYmlsaXR5AApzZXR1cEVudHJ5AAxzaG93RmlsZU9wZW4ADHNob3` +
- `dLZXlib2FyZAANc2hvd1NvZnRJbnB1dAAFc3BsaXQAFnN0YXJ0QWN0aXZpdHlGb3JSZXN1` +
- `bHQAC3N1YlNlcXVlbmNlAAZ0aGlzJDAABnRoaXMkMQAIdG9TdHJpbmcAA3RvcAAidW5rbm` +
- `93biBrZXlib2FyZCB0eXBlLCB1c2UgZGVmYXVsdAAMdXBkYXRlTGF5b3V0ABB2YWwka2V5` +
- `Ym9hcmRUeXBlAAV2YWx1ZQAFd2lkdGgAAXwATgIAAAcOAFEAB3cQAg5ZeZaXeLSWlpellg` +
- `JtLCBLAnQdAHsBAAcOAH4ABw60AMQBAQAHDgDHAQkAAAAAAAAAAAAHDloA2QEBAAcOAOsB` +
- `AQAHDgDnAQQAAAAABw4A3AEEAAAAAAcOARIPAR8TtAJ7HQDNAQEABw4A0AEABx3htMRblr` +
- `UCFOAALgAHDjhOLQAaAQAHDgAaAgAABw4AGgEABw4AGgIAAAcOABoABw4AGgIAAAcOAJkB` +
- `AwAAAAcdhzQCeywgHoMAcwAHDloAsAEABw5Lo0xLfwJ7HYdLHgDNAQAHDgIihgCEAQEABw` +
- `5aAEoBAAcOWgB3AAcOh7SIjACIAQEABx144XhElgJ3HeFatGo8AE4BAAcOAiKGADMABw4A` +
- `9AEDAAAABw4CDDsCeR08bEsAvwEBAAcOPDw9tIwAOAAHDsMCDiwCdh2HhUweWrW0/9AAAh` +
- `kBtAEaTAIaAlUEAJYBHgIZAbQBGkoCGQG0ARpcAhkBtAEaQQIZAbQBGl4FRJAACAQABAEE` +
- `AgQBAAIBAQSQIAGQIDaAgAToFjcBiBcAAQEBBpAgOICABIgZOQGkGQABAQEHkCA6gIAEzB` +
- `k7AegZAAEBAwiQIDyAgASEGj0BoBoBAbQaAQHIGgABAQEJkCBAgIAE+BtBAZQcBgIRBwoa` +
- `ARoBGgEaARoBChACAQJCgYAEwB0BiCDkHQGIIPwdAYgglB4BiCCsHgGIIMQeAYgg3B4Fgg` +
- `IABQj0HgQIyB8BggIAAYICAAGCAgABAuQfBALoIAEIjCEBCKghSgDEIQEAnCIBANwjCACA` +
- `JAcEpCQBAegkBgC0JREAAAAAAAAAAQAAAAAAAAABAAAAtwAAAHAAAAACAAAALQAAAEwDAA` +
- `ADAAAAOwAAAAAEAAAEAAAAEgAAAMQGAAAFAAAAYwAAAFQHAAAGAAAABgAAAGwKAAADEAAA` +
- `BQAAACwLAAABIAAAIAAAAGgLAAAGIAAABQAAAJATAAABEAAAIAAAAOATAAACIAAAtwAAAA` +
- `IVAAADIAAAIAAAAFQhAAAEIAAABgAAAKciAAAFIAAAAQAAANMiAAAAIAAABgAAAOAiAAAA` +
- `EAAAAQAAALwjAAA=` +
+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` +
``
diff --git a/cmd/fyne/internal/mobile/manifest.go b/cmd/fyne/internal/mobile/manifest.go
index 96884fd1a7..29b6a2f5bc 100644
--- a/cmd/fyne/internal/mobile/manifest.go
+++ b/cmd/fyne/internal/mobile/manifest.go
@@ -78,4 +78,5 @@ var manifestTmpl = template.Must(template.New("manifest").Parse(`
+
`))
diff --git a/cmd/fyne_demo/main.go b/cmd/fyne_demo/main.go
index 7e444e3600..a6fa02316c 100644
--- a/cmd/fyne_demo/main.go
+++ b/cmd/fyne_demo/main.go
@@ -80,9 +80,13 @@ func main() {
u, _ := url.Parse("https://github.com/sponsors/fyne-io")
_ = a.OpenURL(u)
}))
+ file := fyne.NewMenu("File", newItem)
+ if !fyne.CurrentDevice().IsMobile() {
+ file.Items = append(file.Items, fyne.NewMenuItemSeparator(), settingsItem)
+ }
mainMenu := fyne.NewMainMenu(
// a quit item will be appended to our first menu
- fyne.NewMenu("File", newItem, fyne.NewMenuItemSeparator(), settingsItem),
+ file,
fyne.NewMenu("Edit", cutItem, copyItem, pasteItem, fyne.NewMenuItemSeparator(), findItem),
helpMenu,
)
diff --git a/cmd/fyne_demo/tutorials/dialog.go b/cmd/fyne_demo/tutorials/dialog.go
index 54fd28d121..a829d0148a 100644
--- a/cmd/fyne_demo/tutorials/dialog.go
+++ b/cmd/fyne_demo/tutorials/dialog.go
@@ -67,7 +67,7 @@ func dialogScreen(win fyne.Window) fyne.CanvasObject {
return
}
- fileSaved(writer)
+ fileSaved(writer, win)
}, win)
}),
widget.NewButton("Folder Open", func() {
@@ -141,13 +141,18 @@ func imageOpened(f fyne.URIReadCloser) {
showImage(f)
}
-func fileSaved(f fyne.URIWriteCloser) {
+func fileSaved(f fyne.URIWriteCloser, w fyne.Window) {
if f == nil {
log.Println("Cancelled")
return
}
- log.Println("Save to...", f.URI())
+ defer f.Close()
+ _, err := f.Write([]byte("Written by Fyne demo\n"))
+ if err != nil {
+ dialog.ShowError(err, w)
+ }
+ log.Println("Saved to...", f.URI())
}
func loadImage(f fyne.URIReadCloser) *canvas.Image {
diff --git a/dialog/color_channel.go b/dialog/color_channel.go
index 313698c964..6ab7d311ae 100644
--- a/dialog/color_channel.go
+++ b/dialog/color_channel.go
@@ -2,6 +2,7 @@ package dialog
import (
"strconv"
+ "sync"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
@@ -124,25 +125,21 @@ func (r *colorChannelRenderer) updateObjects() {
}
type colorChannelEntry struct {
- widget.Entry
+ userChangeEntry
}
func newColorChannelEntry(c *colorChannel) *colorChannelEntry {
- e := &colorChannelEntry{
- Entry: widget.Entry{
- Text: "0",
- OnChanged: func(text string) {
- value, err := strconv.Atoi(text)
- if err != nil {
- fyne.LogError("Couldn't parse: "+text, err)
- } else {
- c.SetValue(value)
- }
- },
- // TODO add number min/max validator
- },
- }
+ e := &colorChannelEntry{}
+ e.Text = "0"
e.ExtendBaseWidget(e)
+ e.setOnChanged(func(text string) {
+ value, err := strconv.Atoi(text)
+ if err != nil {
+ fyne.LogError("Couldn't parse: "+text, err)
+ return
+ }
+ c.SetValue(value)
+ })
return e
}
@@ -152,3 +149,49 @@ func (e *colorChannelEntry) MinSize() fyne.Size {
min = min.Add(fyne.NewSize(theme.Padding()*6, theme.Padding()*4))
return min.Max(e.Entry.MinSize())
}
+
+type userChangeEntry struct {
+ widget.Entry
+
+ lock sync.RWMutex
+ userTyped bool
+}
+
+func newUserChangeEntry(text string) *userChangeEntry {
+ e := &userChangeEntry{}
+ e.Entry.Text = text
+ e.ExtendBaseWidget(e)
+ return e
+}
+
+func (e *userChangeEntry) setOnChanged(onChanged func(s string)) {
+ e.Entry.OnChanged = func(text string) {
+ e.lock.Lock()
+ userTyped := e.userTyped
+ if userTyped {
+ e.userTyped = false
+ }
+ e.lock.Unlock()
+ if !userTyped {
+ return
+ }
+ if onChanged != nil {
+ onChanged(text)
+ }
+ }
+ e.ExtendBaseWidget(e)
+}
+
+func (e *userChangeEntry) TypedRune(r rune) {
+ e.lock.Lock()
+ e.userTyped = true
+ e.lock.Unlock()
+ e.Entry.TypedRune(r)
+}
+
+func (e *userChangeEntry) TypedKey(ev *fyne.KeyEvent) {
+ e.lock.Lock()
+ e.userTyped = true
+ e.lock.Unlock()
+ e.Entry.TypedKey(ev)
+}
diff --git a/dialog/color_picker.go b/dialog/color_picker.go
index 835c9fadde..6c0a784078 100644
--- a/dialog/color_picker.go
+++ b/dialog/color_picker.go
@@ -162,17 +162,16 @@ func (p *colorAdvancedPicker) CreateRenderer() fyne.WidgetRenderer {
})
// Hex
- hex := &widget.Entry{
- OnChanged: func(text string) {
- c, err := stringToColor(text)
- if err != nil {
- fyne.LogError("Error parsing color: "+text, err)
- // TODO trigger entry invalid state
- } else {
- p.SetColor(c)
- }
- },
- }
+ hex := newUserChangeEntry("")
+ hex.setOnChanged(func(text string) {
+ c, err := stringToColor(text)
+ if err != nil {
+ fyne.LogError("Error parsing color: "+text, err)
+ // TODO trigger entry invalid state
+ } else {
+ p.SetColor(c)
+ }
+ })
contents := fyne.NewContainerWithLayout(layout.NewPaddedLayout(), container.NewVBox(
container.NewGridWithColumns(3,
@@ -264,7 +263,7 @@ type colorPickerRenderer struct {
wheel *colorWheel
preview *canvas.Rectangle
alphaChannel *colorChannel
- hex *widget.Entry
+ hex *userChangeEntry
contents fyne.CanvasObject
}
diff --git a/dialog/file_mobile.go b/dialog/file_mobile.go
index 8e9350f620..6e31497cb2 100644
--- a/dialog/file_mobile.go
+++ b/dialog/file_mobile.go
@@ -36,12 +36,7 @@ func fileOpenOSOverride(f *FileDialog) bool {
}
func fileSaveOSOverride(f *FileDialog) bool {
- ShowInformation("File Save", "File save not available on mobile", f.parent)
-
- callback := f.callback.(func(fyne.URIWriteCloser, error))
- if callback != nil {
- callback(nil, nil)
- }
+ gomobile.ShowFileSavePicker(f.callback.(func(fyne.URIWriteCloser, error)), f.filter)
return true
}
diff --git a/go.mod b/go.mod
index b7918fc3fb..7648888d0f 100644
--- a/go.mod
+++ b/go.mod
@@ -7,9 +7,9 @@ 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.2
+ github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7
- github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3
+ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48
github.com/godbus/dbus/v5 v5.0.3
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526
@@ -29,5 +29,3 @@ require (
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
gopkg.in/yaml.v2 v2.2.8 // indirect
)
-
-replace github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3 => github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d
diff --git a/go.sum b/go.sum
index 112f526ce0..74eb47698a 100644
--- a/go.sum
+++ b/go.sum
@@ -10,12 +10,12 @@ github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
-github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d h1:WfVxpuVm+5Gr3ipAoWrxV8lJFYkaBWoEwFRrWThWRSU=
-github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
-github.com/fyne-io/mobile v0.1.2 h1:0HaXDtOOwyOTn3Umi0uKVCOgJtfX73c6unC4U8i5VZU=
-github.com/fyne-io/mobile v0.1.2/go.mod h1:/kOrWrZB6sasLbEy2JIvr4arEzQTXBTZGb3Y96yWbHY=
+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/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/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
@@ -44,7 +44,6 @@ github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQO
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
-github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
@@ -52,14 +51,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
-golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
-golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k=
@@ -72,18 +69,15 @@ golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 h1:gVCS+QOncANNPlmlO1AhlU3oxs4V9z+gTtPwIk3p2N8=
golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
-golang.org/x/tools v0.0.0-20190808195139-e713427fea3f h1:lSQQYboXWc71s9tnZRRBiMcc9Uc1BPWj3Bzvdk8UQ0Y=
golang.org/x/tools v0.0.0-20190808195139-e713427fea3f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03 h1:XpToik3MpT5iW3iHgNwnh3a8QwugfomvxOlyDnaOils=
golang.org/x/tools v0.0.0-20200328031815-3db5fc6bac03/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/app/focus_manager.go b/internal/app/focus_manager.go
index 668aa12520..cd86b3ddb3 100644
--- a/internal/app/focus_manager.go
+++ b/internal/app/focus_manager.go
@@ -52,25 +52,18 @@ func (f *FocusManager) Focus(obj fyne.Focusable) bool {
return true
}
if dis, ok := obj.(fyne.Disableable); ok && dis.Disabled() {
- return true
+ type selectableText interface {
+ SelectedText() string
+ }
+ if _, isSelectableText := obj.(selectableText); !isSelectableText || fyne.CurrentDevice().IsMobile() {
+ return true
+ }
}
}
f.focus(obj)
return true
}
-// FocusBeforeAdded allows an object to be focused before it is added to the object tree.
-// This is typically used before a canvas is visible and should be used with care.
-func (f *FocusManager) FocusBeforeAdded(obj fyne.Focusable) {
- f.RLock()
- defer f.RUnlock()
- if dis, ok := obj.(fyne.Disableable); ok && dis.Disabled() {
- return
- }
-
- f.focus(obj)
-}
-
// Focused returns the currently focused object or nil if none.
func (f *FocusManager) Focused() fyne.Focusable {
f.RLock()
diff --git a/internal/driver/glfw/canvas.go b/internal/driver/glfw/canvas.go
index e2662b6637..ae6c305fe2 100644
--- a/internal/driver/glfw/canvas.go
+++ b/internal/driver/glfw/canvas.go
@@ -94,7 +94,7 @@ func (c *glCanvas) Focus(obj fyne.Focusable) {
}
}
- c.contentFocusMgr.FocusBeforeAdded(obj) // not found yet assume we are preparing the UI
+ fyne.LogError("Failed to focus object which is not part of the canvas’ content, menu or overlays.", nil)
}
func (c *glCanvas) Focused() fyne.Focusable {
diff --git a/internal/driver/glfw/canvas_test.go b/internal/driver/glfw/canvas_test.go
index 4b9dc6d013..7d97ab99ef 100644
--- a/internal/driver/glfw/canvas_test.go
+++ b/internal/driver/glfw/canvas_test.go
@@ -210,9 +210,19 @@ func TestGlCanvas_Focus_BeforeVisible(t *testing.T) {
e := widget.NewEntry()
c := w.Canvas().(*glCanvas)
c.Focus(e) // this crashed in the past
+}
+
+func TestGlCanvas_Focus_SetContent(t *testing.T) {
+ w := createWindow("Test")
+ w.SetPadded(false)
+ e := widget.NewEntry()
+ w.SetContent(e)
+ c := w.Canvas().(*glCanvas)
+ c.Focus(e)
+ assert.Equal(t, e, c.Focused())
w.SetContent(e)
- assert.Equal(t, e, c.Focused(), "Item set to focus before SetContent was not focused after")
+ assert.Equal(t, e, c.Focused())
}
func TestGlCanvas_FocusHandlingWhenAddingAndRemovingOverlays(t *testing.T) {
diff --git a/internal/driver/glfw/driver.go b/internal/driver/glfw/driver.go
index 4964b5c022..7eef21173b 100644
--- a/internal/driver/glfw/driver.go
+++ b/internal/driver/glfw/driver.go
@@ -3,6 +3,7 @@
package glfw
import (
+ "os"
"runtime"
"strconv"
"strings"
@@ -113,6 +114,16 @@ func (d *gLDriver) windowList() []fyne.Window {
return d.windows
}
+func (d *gLDriver) initFailed(msg string, err error) {
+ fyne.LogError(msg, err)
+
+ if running() {
+ d.Quit()
+ } else {
+ os.Exit(1)
+ }
+}
+
func goroutineID() int {
b := make([]byte, 64)
b = b[:runtime.Stack(b, false)]
diff --git a/internal/driver/glfw/window.go b/internal/driver/glfw/window.go
index 01e0ac4cca..b3d41b20b3 100644
--- a/internal/driver/glfw/window.go
+++ b/internal/driver/glfw/window.go
@@ -57,6 +57,7 @@ type window struct {
cursor desktop.Cursor
customCursor *glfw.Cursor
canvas *glCanvas
+ driver *gLDriver
title string
icon fyne.Resource
mainmenu *fyne.MainMenu
@@ -446,7 +447,7 @@ func (w *window) Close() {
func (w *window) ShowAndRun() {
w.Show()
- fyne.CurrentApp().Driver().Run()
+ w.driver.Run()
}
// Clipboard returns the system clipboard
@@ -701,6 +702,11 @@ func (w *window) mouseOut() {
}
func (w *window) mouseClicked(_ *glfw.Window, btn glfw.MouseButton, action glfw.Action, mods glfw.ModifierKey) {
+ if w.mousePos.IsZero() { // window may not be focused (darwin mostly) and so position callbacks not happening
+ xpos, ypos := w.viewport.GetCursorPos()
+ w.mousePos = fyne.NewPos(internal.UnscaleInt(w.canvas, int(xpos)), internal.UnscaleInt(w.canvas, int(ypos)))
+ }
+
co, pos, _ := w.findObjectAtPositionMatching(w.canvas, w.mousePos, func(object fyne.CanvasObject) bool {
switch object.(type) {
case fyne.Tappable, fyne.SecondaryTappable, fyne.DoubleTappable, fyne.Focusable, desktop.Mouseable, desktop.Hoverable:
@@ -1123,10 +1129,19 @@ func (w *window) keyPressed(_ *glfw.Window, key glfw.Key, scancode int, action g
if shortcut != nil {
if focused, ok := w.canvas.Focused().(fyne.Shortcutable); ok {
- w.queueEvent(func() { focused.TypedShortcut(shortcut) })
+ shouldRunShortcut := true
+ type selectableText interface {
+ fyne.Disableable
+ SelectedText() string
+ }
+ if selectableTextWid, ok := focused.(selectableText); ok && selectableTextWid.Disabled() {
+ shouldRunShortcut = shortcut.ShortcutName() == "Copy"
+ }
+ if shouldRunShortcut {
+ w.queueEvent(func() { focused.TypedShortcut(shortcut) })
+ }
return
}
-
w.queueEvent(func() { w.canvas.shortcut.TypedShortcut(shortcut) })
return
}
@@ -1174,6 +1189,7 @@ func (w *window) focused(_ *glfw.Window, isFocused bool) {
w.canvas.FocusGained()
} else {
w.canvas.FocusLost()
+ w.mousePos = fyne.Position{}
}
}
@@ -1265,7 +1281,7 @@ func (d *gLDriver) createWindow(title string, decorate bool) fyne.Window {
runOnMain(func() {
d.initGLFW()
- ret = &window{title: title, decorate: decorate}
+ ret = &window{title: title, decorate: decorate, driver: d}
// This channel will be closed when the window is closed.
ret.eventQueue = make(chan func(), 1024)
go ret.runEventQueue()
@@ -1308,7 +1324,7 @@ func (w *window) create() {
win, err := glfw.CreateWindow(pixWidth, pixHeight, w.title, nil, nil)
if err != nil {
- fyne.LogError("window creation error", err)
+ w.driver.initFailed("window creation error", err)
return
}
diff --git a/internal/driver/glfw/window_test.go b/internal/driver/glfw/window_test.go
index ed51481468..bcabd91ac9 100644
--- a/internal/driver/glfw/window_test.go
+++ b/internal/driver/glfw/window_test.go
@@ -992,6 +992,46 @@ func TestWindow_Clipboard(t *testing.T) {
cb.SetContent(cliboardContent)
}
+func TestWindow_ClipboardCopy_DisabledEntry(t *testing.T) {
+ w := createWindow("Test").(*window)
+ e := widget.NewEntry()
+ e.SetText("Testing")
+ e.Disable()
+ w.SetContent(e)
+ repaintWindow(w)
+
+ w.canvas.Focus(e)
+ e.DoubleTapped(nil)
+ assert.Equal(t, "Testing", e.SelectedText())
+
+ ctrlMod := glfw.ModControl
+ if runtime.GOOS == "darwin" {
+ ctrlMod = glfw.ModSuper
+ }
+ w.keyPressed(nil, glfw.KeyC, 0, glfw.Repeat, ctrlMod)
+ w.waitForEvents()
+
+ assert.Equal(t, "Testing", w.Clipboard().Content())
+
+ e.SetText("Testing2")
+ e.DoubleTapped(nil)
+ assert.Equal(t, "Testing2", e.SelectedText())
+
+ // any other shortcut should be forbidden (Cut)
+ w.keyPressed(nil, glfw.KeyX, 0, glfw.Repeat, ctrlMod)
+ w.waitForEvents()
+
+ assert.Equal(t, "Testing2", e.Text)
+ assert.Equal(t, "Testing", w.Clipboard().Content())
+
+ // any other shortcut should be forbidden (Paste)
+ w.keyPressed(nil, glfw.KeyV, 0, glfw.Repeat, ctrlMod)
+ w.waitForEvents()
+
+ assert.Equal(t, "Testing2", e.Text)
+ assert.Equal(t, "Testing", w.Clipboard().Content())
+}
+
func TestWindow_CloseInterception(t *testing.T) {
d := NewGLDriver()
w := d.CreateWindow("test").(*window)
diff --git a/internal/driver/gomobile/android.c b/internal/driver/gomobile/android.c
index 930a46dcf0..cee6f2ab39 100644
--- a/internal/driver/gomobile/android.c
+++ b/internal/driver/gomobile/android.c
@@ -144,6 +144,26 @@ void* openStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
return (*env)->NewGlobalRef(env, stream);
}
+void* saveStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
+ JNIEnv *env = (JNIEnv*)jni_env;
+ jobject resolver = getContentResolver(jni_env, ctx);
+
+ jclass resolverClass = (*env)->GetObjectClass(env, resolver);
+ jmethodID saveOutputStream = find_method(env, resolverClass, "openOutputStream", "(Landroid/net/Uri;Ljava/lang/String;)Ljava/io/OutputStream;");
+
+ jobject uri = parseURI(jni_env, ctx, uriCstr);
+ jstring modes = (*env)->NewStringUTF(env, "wt"); // truncate before write
+ jobject stream = (jobject)(*env)->CallObjectMethod(env, resolver, saveOutputStream, uri, modes);
+ jthrowable loadErr = (*env)->ExceptionOccurred(env);
+
+ if (loadErr != NULL) {
+ (*env)->ExceptionClear(env);
+ return NULL;
+ }
+
+ return (*env)->NewGlobalRef(env, stream);
+}
+
char* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* total) {
JNIEnv *env = (JNIEnv*)jni_env;
jclass streamClass = (*env)->GetObjectClass(env, stream);
@@ -162,6 +182,19 @@ char* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* t
return bytes;
}
+void writeStream(uintptr_t jni_env, uintptr_t ctx, void* stream, char* buf, int len) {
+ JNIEnv *env = (JNIEnv*)jni_env;
+ jclass streamClass = (*env)->GetObjectClass(env, stream);
+ jmethodID write = find_method(env, streamClass, "write", "([BII)V");
+
+ jbyteArray data = (*env)->NewByteArray(env, len);
+ (*env)->SetByteArrayRegion(env, data, 0, len, buf);
+
+ (*env)->CallVoidMethod(env, stream, write, data, 0, len);
+
+ free(buf);
+}
+
void closeStream(uintptr_t jni_env, uintptr_t ctx, void* stream) {
JNIEnv *env = (JNIEnv*)jni_env;
jclass streamClass = (*env)->GetObjectClass(env, stream);
@@ -183,7 +216,6 @@ bool hasPrefix(char* string, char* prefix) {
bool canListContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject resolver = getContentResolver(jni_env, ctx);
- jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
jobject uri = parseURI(jni_env, ctx, uriCstr);
jthrowable loadErr = (*env)->ExceptionOccurred(env);
@@ -243,10 +275,41 @@ bool canListURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
return false;
}
+char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
+ JNIEnv *env = (JNIEnv*)jni_env;
+ jobject resolver = getContentResolver(jni_env, ctx);
+ jobject uri = parseURI(jni_env, ctx, uriCstr);
+ jthrowable loadErr = (*env)->ExceptionOccurred(env);
+
+ if (loadErr != NULL) {
+ (*env)->ExceptionClear(env);
+ return "";
+ }
+
+ jclass stringClass = find_class(env, "java/lang/String");
+ jobjectArray project = (*env)->NewObjectArray(env, 1, stringClass, (*env)->NewStringUTF(env, "_display_name"));
+
+ jclass resolverClass = (*env)->GetObjectClass(env, resolver);
+ jmethodID query = find_method(env, resolverClass, "query", "(Landroid/net/Uri;[Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;)Landroid/database/Cursor;");
+
+ jobject cursor = (jobject)(*env)->CallObjectMethod(env, resolver, query, uri, project, NULL, NULL, NULL);
+ jclass cursorClass = (*env)->GetObjectClass(env, cursor);
+
+ jmethodID first = find_method(env, cursorClass, "moveToFirst", "()Z");
+ jmethodID get = find_method(env, cursorClass, "getString", "(I)Ljava/lang/String;");
+
+ if (((jboolean)(*env)->CallBooleanMethod(env, cursor, first)) == JNI_TRUE) {
+ jstring name = (jstring)(*env)->CallObjectMethod(env, cursor, get, 0);
+ char *fname = getString(jni_env, ctx, name);
+ return fname;
+ }
+
+ return NULL;
+}
+
char* listContentURI(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
JNIEnv *env = (JNIEnv*)jni_env;
jobject resolver = getContentResolver(jni_env, ctx);
- jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
jobject uri = parseURI(jni_env, ctx, uriCstr);
jthrowable loadErr = (*env)->ExceptionOccurred(env);
diff --git a/internal/driver/gomobile/canvas.go b/internal/driver/gomobile/canvas.go
index b62fe7e045..1824df2525 100644
--- a/internal/driver/gomobile/canvas.go
+++ b/internal/driver/gomobile/canvas.go
@@ -40,12 +40,13 @@ type mobileCanvas struct {
onTypedKey func(event *fyne.KeyEvent)
shortcut fyne.ShortcutHandler
- inited bool
- lastTapDown map[int]time.Time
- lastTapDownPos map[int]fyne.Position
- dragging fyne.Draggable
- refreshQueue chan fyne.CanvasObject
- minSizeCache map[fyne.CanvasObject]fyne.Size
+ inited bool
+ lastTapDown map[int]time.Time
+ lastTapDownPos map[int]fyne.Position
+ dragging fyne.Draggable
+ dragStart, dragOffset fyne.Position
+ refreshQueue chan fyne.CanvasObject
+ minSizeCache map[fyne.CanvasObject]fyne.Size
touchTapCount int
touchCancelFunc context.CancelFunc
@@ -82,7 +83,7 @@ func (c *mobileCanvas) Content() fyne.CanvasObject {
func (c *mobileCanvas) Focus(obj fyne.Focusable) {
focusMgr := c.focusManager()
- if focusMgr.Focus(obj) { // fast path – probably >99.9% of all cases
+ if focusMgr != nil && focusMgr.Focus(obj) { // fast path – probably >99.9% of all cases
c.handleKeyboard(obj)
return
}
@@ -101,15 +102,27 @@ func (c *mobileCanvas) Focus(obj fyne.Focusable) {
}
func (c *mobileCanvas) FocusNext() {
- c.focusManager().FocusNext()
+ mgr := c.focusManager()
+ if mgr == nil {
+ return
+ }
+ mgr.FocusNext()
}
func (c *mobileCanvas) FocusPrevious() {
- c.focusManager().FocusPrevious()
+ mgr := c.focusManager()
+ if mgr == nil {
+ return
+ }
+ mgr.FocusPrevious()
}
func (c *mobileCanvas) Focused() fyne.Focusable {
- return c.focusManager().Focused()
+ mgr := c.focusManager()
+ if mgr == nil {
+ return nil
+ }
+ return mgr.Focused()
}
func (c *mobileCanvas) InteractiveArea() (fyne.Position, fyne.Size) {
@@ -176,7 +189,11 @@ func (c *mobileCanvas) Size() fyne.Size {
}
func (c *mobileCanvas) Unfocus() {
- if c.focusManager().Focus(nil) {
+ mgr := c.focusManager()
+ if mgr == nil {
+ return
+ }
+ if mgr.Focus(nil) {
hideVirtualKeyboard()
}
}
@@ -389,8 +406,9 @@ func (c *mobileCanvas) tapDown(pos fyne.Position, tapID int) {
func (c *mobileCanvas) tapMove(pos fyne.Position, tapID int,
dragCallback func(fyne.Draggable, *fyne.DragEvent)) {
- deltaX := pos.X - c.lastTapDownPos[tapID].X
- deltaY := pos.Y - c.lastTapDownPos[tapID].Y
+ previousPos := c.lastTapDownPos[tapID]
+ deltaX := pos.X - previousPos.X
+ deltaY := pos.Y - previousPos.Y
if c.dragging == nil && (math.Abs(float64(deltaX)) < tapMoveThreshold && math.Abs(float64(deltaY)) < tapMoveThreshold) {
return
@@ -420,13 +438,16 @@ func (c *mobileCanvas) tapMove(pos fyne.Position, tapID int,
if c.dragging == nil {
if drag, ok := co.(fyne.Draggable); ok {
c.dragging = drag
+ c.dragOffset = previousPos.Subtract(objPos)
+ c.dragStart = co.Position()
} else {
return
}
}
ev := new(fyne.DragEvent)
- ev.Position = objPos
+ draggedObjDelta := c.dragStart.Subtract(c.dragging.(fyne.CanvasObject).Position())
+ ev.Position = pos.Subtract(c.dragOffset).Add(draggedObjDelta)
ev.Dragged = fyne.Delta{DX: deltaX, DY: deltaY}
dragCallback(c.dragging, ev)
diff --git a/internal/driver/gomobile/canvas_test.go b/internal/driver/gomobile/canvas_test.go
index 7929cfbe37..b49cff9a23 100644
--- a/internal/driver/gomobile/canvas_test.go
+++ b/internal/driver/gomobile/canvas_test.go
@@ -4,6 +4,7 @@ package gomobile
import (
"image/color"
+ "os"
"testing"
"time"
@@ -19,6 +20,14 @@ import (
"github.com/stretchr/testify/assert"
)
+func TestMain(m *testing.M) {
+ currentApp := fyne.CurrentApp()
+ fyne.SetCurrentApp(newTestMobileApp())
+ ret := m.Run()
+ fyne.SetCurrentApp(currentApp)
+ os.Exit(ret)
+}
+
func TestCanvas_ChildMinSizeChangeAffectsAncestorsUpToRoot(t *testing.T) {
c := NewCanvas().(*mobileCanvas)
leftObj1 := canvas.NewRectangle(color.Black)
@@ -170,12 +179,43 @@ func TestCanvas_Dragged(t *testing.T) {
assert.True(t, dragged)
assert.Equal(t, scroll, draggedObj)
- // TODO find a way to get the test driver to report as mobile
dragged = false
c.tapMove(fyne.NewPos(32, 5), 0, func(wid fyne.Draggable, ev *fyne.DragEvent) {
wid.Dragged(ev)
dragged = true
})
+ assert.True(t, dragged)
+ assert.Equal(t, fyne.NewPos(0, 5), scroll.Offset)
+}
+
+func TestCanvas_DraggingOutOfWidget(t *testing.T) {
+ c := NewCanvas().(*mobileCanvas)
+ slider := widget.NewSlider(0.0, 100.0)
+ c.SetContent(container.NewGridWithRows(2, slider, widget.NewLabel("Outside")))
+ c.resize(fyne.NewSize(100, 200))
+
+ assert.Zero(t, slider.Value)
+ lastValue := slider.Value
+
+ dragged := false
+ c.tapDown(fyne.NewPos(23, 13), 0)
+ c.tapMove(fyne.NewPos(30, 13), 0, func(wid fyne.Draggable, ev *fyne.DragEvent) {
+ assert.Equal(t, slider, wid)
+ wid.Dragged(ev)
+ dragged = true
+ })
+ assert.True(t, dragged)
+ assert.Greater(t, slider.Value, lastValue)
+ lastValue = slider.Value
+
+ dragged = false
+ c.tapMove(fyne.NewPos(40, 120), 0, func(wid fyne.Draggable, ev *fyne.DragEvent) {
+ assert.Equal(t, slider, wid)
+ wid.Dragged(ev)
+ dragged = true
+ })
+ assert.True(t, dragged)
+ assert.Greater(t, slider.Value, lastValue)
}
func TestCanvas_Tappable(t *testing.T) {
@@ -359,3 +399,19 @@ func simulateTap(c *mobileCanvas) {
}, func(wid fyne.Draggable) {
})
}
+
+type mobileApp struct {
+ fyne.App
+ driver fyne.Driver
+}
+
+func (a *mobileApp) Driver() fyne.Driver {
+ return a.driver
+}
+
+func newTestMobileApp() fyne.App {
+ return &mobileApp{
+ App: fyne.CurrentApp(),
+ driver: NewGoMobileDriver(),
+ }
+}
diff --git a/internal/driver/gomobile/file.go b/internal/driver/gomobile/file.go
index 51834c354c..ea12251caa 100644
--- a/internal/driver/gomobile/file.go
+++ b/internal/driver/gomobile/file.go
@@ -43,20 +43,20 @@ func mobileFilter(filter storage.FileFilter) *app.FileFilter {
return mobile
}
-type hasPicker interface {
+type hasOpenPicker interface {
ShowFileOpenPicker(func(string, func()), *app.FileFilter)
}
// ShowFileOpenPicker loads the native file open dialog and returns the chosen file path via the callback func.
func ShowFileOpenPicker(callback func(fyne.URIReadCloser, error), filter storage.FileFilter) {
drv := fyne.CurrentApp().Driver().(*mobileDriver)
- if a, ok := drv.app.(hasPicker); ok {
+ if a, ok := drv.app.(hasOpenPicker); ok {
a.ShowFileOpenPicker(func(uri string, closer func()) {
if uri == "" {
callback(nil, nil)
return
}
- f, err := fileReaderForURI(storage.NewURI(uri))
+ f, err := fileReaderForURI(nativeURI(uri))
if f != nil {
f.(*fileOpen).done = closer
}
@@ -69,7 +69,7 @@ func ShowFileOpenPicker(callback func(fyne.URIReadCloser, error), filter storage
func ShowFolderOpenPicker(callback func(fyne.ListableURI, error)) {
filter := storage.NewMimeTypeFileFilter([]string{"application/x-directory"})
drv := fyne.CurrentApp().Driver().(*mobileDriver)
- if a, ok := drv.app.(hasPicker); ok {
+ if a, ok := drv.app.(hasOpenPicker); ok {
a.ShowFileOpenPicker(func(uri string, _ func()) {
if uri == "" {
callback(nil, nil)
@@ -80,3 +80,45 @@ func ShowFolderOpenPicker(callback func(fyne.ListableURI, error)) {
}, mobileFilter(filter))
}
}
+
+type fileSave struct {
+ io.WriteCloser
+ uri fyne.URI
+ done func()
+}
+
+func (f *fileSave) URI() fyne.URI {
+ return f.uri
+}
+
+func fileWriterForURI(u fyne.URI) (fyne.URIWriteCloser, error) {
+ file := &fileSave{uri: u}
+ write, err := nativeFileSave(file)
+ if write == nil {
+ return nil, err
+ }
+ file.WriteCloser = write
+ return file, err
+}
+
+type hasSavePicker interface {
+ ShowFileSavePicker(func(string, func()), *app.FileFilter)
+}
+
+// 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) {
+ drv := fyne.CurrentApp().Driver().(*mobileDriver)
+ if a, ok := drv.app.(hasSavePicker); ok {
+ a.ShowFileSavePicker(func(uri string, closer func()) {
+ if uri == "" {
+ callback(nil, nil)
+ return
+ }
+ f, err := fileWriterForURI(storage.NewURI(uri))
+ if f != nil {
+ f.(*fileSave).done = closer
+ }
+ callback(f, err)
+ }, mobileFilter(filter))
+ }
+}
diff --git a/internal/driver/gomobile/file_android.go b/internal/driver/gomobile/file_android.go
index 5c69c8af45..4becae0f26 100644
--- a/internal/driver/gomobile/file_android.go
+++ b/internal/driver/gomobile/file_android.go
@@ -9,6 +9,8 @@ package gomobile
void* openStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr);
char* readStream(uintptr_t jni_env, uintptr_t ctx, void* stream, int len, int* total);
+void* saveStream(uintptr_t jni_env, uintptr_t ctx, char* uriCstr);
+void writeStream(uintptr_t jni_env, uintptr_t ctx, void* stream, char* data, int len);
void closeStream(uintptr_t jni_env, uintptr_t ctx, void* stream);
*/
import "C"
@@ -92,8 +94,52 @@ func nativeFileOpen(f *fileOpen) (io.ReadCloser, error) {
return stream, nil
}
+func saveStream(uri string) unsafe.Pointer {
+ uriStr := C.CString(uri)
+ defer C.free(unsafe.Pointer(uriStr))
+
+ var stream unsafe.Pointer
+ app.RunOnJVM(func(_, env, ctx uintptr) error {
+ streamPtr := C.saveStream(C.uintptr_t(env), C.uintptr_t(ctx), uriStr)
+ if streamPtr == C.NULL {
+ return os.ErrNotExist
+ }
+
+ stream = unsafe.Pointer(streamPtr)
+ return nil
+ })
+ return stream
+}
+
+func nativeFileSave(f *fileSave) (io.WriteCloser, error) {
+ if f.uri == nil || f.uri.String() == "" {
+ return nil, nil
+ }
+
+ ret := saveStream(f.uri.String())
+ if ret == nil {
+ return nil, errors.New("resource not found at URI")
+ }
+
+ stream := &javaStream{}
+ stream.stream = ret
+ return stream, nil
+}
+
+// Declare conformity to WriteCloser interface
+var _ io.WriteCloser = (*javaStream)(nil)
+
+func (s *javaStream) Write(p []byte) (int, error) {
+ err := app.RunOnJVM(func(_, env, ctx uintptr) error {
+ C.writeStream(C.uintptr_t(env), C.uintptr_t(ctx), s.stream, (*C.char)(C.CBytes(p)), C.int(len(p)))
+ return nil
+ })
+
+ return len(p), err
+}
+
func registerRepository(d *mobileDriver) {
- repo := &mobileFileRepo{driver: d}
+ repo := &mobileFileRepo{}
repository.Register("file", repo)
repository.Register("content", repo)
}
diff --git a/internal/driver/gomobile/file_desktop.go b/internal/driver/gomobile/file_desktop.go
index d8833575f2..ba84061029 100644
--- a/internal/driver/gomobile/file_desktop.go
+++ b/internal/driver/gomobile/file_desktop.go
@@ -14,6 +14,11 @@ func nativeFileOpen(*fileOpen) (io.ReadCloser, error) {
return nil, nil
}
+func nativeFileSave(*fileSave) (io.WriteCloser, error) {
+ // no-op as we use the internal FileRepository
+ return nil, nil
+}
+
func registerRepository(d *mobileDriver) {
repository.Register("file", intRepo.NewFileRepository())
}
diff --git a/internal/driver/gomobile/file_ios.go b/internal/driver/gomobile/file_ios.go
index 21cc47697b..528c7a5a79 100644
--- a/internal/driver/gomobile/file_ios.go
+++ b/internal/driver/gomobile/file_ios.go
@@ -10,6 +10,7 @@ package gomobile
void* iosParseUrl(const char* url);
const void* iosReadFromURL(void* url, int* len);
+const int iosWriteToURL(void* url, const void* bytes, int len);
*/
import "C"
import (
@@ -63,6 +64,31 @@ func (s *secureReadCloser) Close() error {
return nil
}
+type secureWriteCloser struct {
+ url unsafe.Pointer
+ closer func()
+
+ offset int
+}
+
+// Declare conformity to WriteCloser interface
+var _ io.WriteCloser = (*secureWriteCloser)(nil)
+
+func (s *secureWriteCloser) Write(p []byte) (int, error) {
+ count := int(C.iosWriteToURL(s.url, C.CBytes(p), C.int(len(p))))
+ s.offset += count
+
+ return count, nil
+}
+
+func (s *secureWriteCloser) Close() error {
+ if s.closer != nil {
+ s.closer()
+ }
+ s.url = nil
+ return nil
+}
+
func nativeFileOpen(f *fileOpen) (io.ReadCloser, error) {
if f.uri == nil || f.uri.String() == "" {
return nil, nil
@@ -77,7 +103,21 @@ func nativeFileOpen(f *fileOpen) (io.ReadCloser, error) {
return fileStruct, nil
}
+func nativeFileSave(f *fileSave) (io.WriteCloser, error) {
+ if f.uri == nil || f.uri.String() == "" {
+ return nil, nil
+ }
+
+ cStr := C.CString(f.uri.String())
+ defer C.free(unsafe.Pointer(cStr))
+
+ url := C.iosParseUrl(cStr)
+
+ fileStruct := &secureWriteCloser{url: url, closer: f.done}
+ return fileStruct, nil
+}
+
func registerRepository(d *mobileDriver) {
- repo := &mobileFileRepo{driver: d}
+ repo := &mobileFileRepo{}
repository.Register("file", repo)
}
diff --git a/internal/driver/gomobile/file_ios.m b/internal/driver/gomobile/file_ios.m
index 864e2093d4..613cf0a228 100644
--- a/internal/driver/gomobile/file_ios.m
+++ b/internal/driver/gomobile/file_ios.m
@@ -16,3 +16,14 @@
*len = data.length;
return data.bytes;
}
+
+const int iosWriteToURL(void* urlPtr, const void* bytes, int len) {
+ NSURL* url = (NSURL*)urlPtr;
+ NSData *data = [NSData dataWithBytes:bytes length:len];
+ BOOL ok = [data writeToURL:url atomically:YES];
+
+ if (!ok) {
+ return 0;
+ }
+ return data.length;
+}
diff --git a/internal/driver/gomobile/repository.go b/internal/driver/gomobile/repository.go
index 4b2d33f5a6..29fb537d76 100644
--- a/internal/driver/gomobile/repository.go
+++ b/internal/driver/gomobile/repository.go
@@ -1,5 +1,3 @@
-// +build ios android
-
package gomobile
import (
@@ -10,39 +8,66 @@ import (
// declare conformance with repository types
var _ repository.Repository = (*mobileFileRepo)(nil)
+var _ repository.HierarchicalRepository = (*mobileFileRepo)(nil)
var _ repository.ListableRepository = (*mobileFileRepo)(nil)
-
-// TODO add write support (not yet supported on mobile)
-// var _ repository.WritableRepository = (*mobileFileRepo)(nil)
+var _ repository.WritableRepository = (*mobileFileRepo)(nil)
type mobileFileRepo struct {
- driver *mobileDriver
-}
-
-func (m *mobileFileRepo) Exists(u fyne.URI) (bool, error) {
- return true, nil // TODO check a file exists
}
-func (m *mobileFileRepo) Reader(u fyne.URI) (fyne.URIReadCloser, error) {
- return fileReaderForURI(u)
+func (m *mobileFileRepo) CanList(u fyne.URI) (bool, error) {
+ return canListURI(u), nil
}
func (m *mobileFileRepo) CanRead(u fyne.URI) (bool, error) {
return true, nil // TODO check a file can be read
}
+func (m *mobileFileRepo) CanWrite(u fyne.URI) (bool, error) {
+ return true, nil // TODO check a file can be written
+}
+
+func (m *mobileFileRepo) Child(u fyne.URI, name string) (fyne.URI, error) {
+ if u == nil || u.Scheme() != "file" {
+ return nil, repository.ErrOperationNotSupported
+ }
+
+ return repository.GenericChild(u, name)
+}
+
+func (m *mobileFileRepo) CreateListable(u fyne.URI) error {
+ // TODO: implement this
+ return repository.ErrOperationNotSupported
+}
+
+func (m *mobileFileRepo) Delete(u fyne.URI) error {
+ // TODO: implement this
+ return repository.ErrOperationNotSupported
+}
+
func (m *mobileFileRepo) Destroy(string) {
}
-func (m *mobileFileRepo) CanList(u fyne.URI) (bool, error) {
- return canListURI(u), nil
+func (m *mobileFileRepo) Exists(u fyne.URI) (bool, error) {
+ return true, nil // TODO check a file exists
}
func (m *mobileFileRepo) List(u fyne.URI) ([]fyne.URI, error) {
return listURI(u)
}
-func (m *mobileFileRepo) CreateListable(u fyne.URI) error {
- // TODO: implement this
- return repository.ErrOperationNotSupported
+func (m *mobileFileRepo) Parent(u fyne.URI) (fyne.URI, error) {
+ if u == nil || u.Scheme() != "file" {
+ return nil, repository.ErrOperationNotSupported
+ }
+
+ return repository.GenericParent(u)
+}
+
+func (m *mobileFileRepo) Reader(u fyne.URI) (fyne.URIReadCloser, error) {
+ return fileReaderForURI(u)
+}
+
+func (m *mobileFileRepo) Writer(u fyne.URI) (fyne.URIWriteCloser, error) {
+ return fileWriterForURI(u)
}
diff --git a/internal/driver/gomobile/repository_test.go b/internal/driver/gomobile/repository_test.go
new file mode 100644
index 0000000000..99a7d7c129
--- /dev/null
+++ b/internal/driver/gomobile/repository_test.go
@@ -0,0 +1,40 @@
+package gomobile
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "fyne.io/fyne/v2/storage"
+ "fyne.io/fyne/v2/storage/repository"
+)
+
+func TestFileRepositoryChild(t *testing.T) {
+ f := &mobileFileRepo{}
+ repository.Register("file", f)
+
+ p, _ := storage.Child(storage.NewFileURI("/foo/bar"), "baz")
+ assert.Equal(t, "file:///foo/bar/baz", p.String())
+
+ p, _ = storage.Child(storage.NewFileURI("/foo/bar/"), "baz")
+ assert.Equal(t, "file:///foo/bar/baz", p.String())
+
+ p, err := storage.Child(storage.NewURI("content://thing"), "new")
+ assert.NotNil(t, err)
+ assert.Nil(t, p)
+}
+
+func TestFileRepositoryParent(t *testing.T) {
+ f := &mobileFileRepo{}
+ repository.Register("file", f)
+
+ p, _ := storage.Parent(storage.NewFileURI("/foo/bar"))
+ assert.Equal(t, "file:///foo", p.String())
+
+ p, _ = storage.Parent(storage.NewFileURI("/foo/bar/"))
+ assert.Equal(t, "file:///foo", p.String())
+
+ p, err := storage.Parent(storage.NewURI("content://thing"))
+ assert.NotNil(t, err)
+ assert.Nil(t, p)
+}
diff --git a/internal/driver/gomobile/uri.go b/internal/driver/gomobile/uri.go
new file mode 100644
index 0000000000..dadfc50006
--- /dev/null
+++ b/internal/driver/gomobile/uri.go
@@ -0,0 +1,12 @@
+// +build !android
+
+package gomobile
+
+import (
+ "fyne.io/fyne/v2"
+ "fyne.io/fyne/v2/storage"
+)
+
+func nativeURI(uri string) fyne.URI {
+ return storage.NewURI(uri)
+}
diff --git a/internal/driver/gomobile/uri_android.go b/internal/driver/gomobile/uri_android.go
new file mode 100644
index 0000000000..b1138747f4
--- /dev/null
+++ b/internal/driver/gomobile/uri_android.go
@@ -0,0 +1,63 @@
+// +build android
+
+package gomobile
+
+/*
+#cgo LDFLAGS: -landroid -llog
+
+#include
+
+char* contentURIGetFileName(uintptr_t jni_env, uintptr_t ctx, char* uriCstr);
+*/
+import "C"
+import (
+ "path/filepath"
+ "unsafe"
+
+ "fyne.io/fyne/v2"
+ "fyne.io/fyne/v2/storage"
+ "github.com/fyne-io/mobile/app"
+)
+
+type androidURI struct {
+ systemURI string
+ fyne.URI
+}
+
+// Override Name on android for content://
+func (a *androidURI) Name() string {
+ if a.Scheme() == "content" {
+ result := contentURIGetFileName(a.systemURI)
+ if result != "" {
+ return result
+ }
+ }
+ return a.URI.Name()
+}
+
+func (a *androidURI) Extension() string {
+ return filepath.Ext(a.Name())
+}
+
+func contentURIGetFileName(uri string) string {
+ uriStr := C.CString(uri)
+ defer C.free(unsafe.Pointer(uriStr))
+
+ var filename string
+ app.RunOnJVM(func(_, env, ctx uintptr) error {
+ fnamePtr := C.contentURIGetFileName(C.uintptr_t(env), C.uintptr_t(ctx), uriStr)
+ vPtr := unsafe.Pointer(fnamePtr)
+ if vPtr == C.NULL {
+ return nil
+ }
+ filename = C.GoString(fnamePtr)
+ C.free(vPtr)
+
+ return nil
+ })
+ return filename
+}
+
+func nativeURI(uri string) fyne.URI {
+ return &androidURI{URI: storage.NewURI(uri), systemURI: uri}
+}
diff --git a/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java b/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java
index 71e3d7cc76..16df340d56 100644
--- a/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java
+++ b/vendor/github.com/fyne-io/mobile/app/GoNativeActivity.java
@@ -26,6 +26,7 @@
public class GoNativeActivity extends NativeActivity {
private static GoNativeActivity goNativeActivity;
private static final int FILE_OPEN_CODE = 1;
+ private static final int FILE_SAVE_CODE = 2;
private static final int DEFAULT_INPUT_TYPE = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS |
InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD; // this is required to force samsung keyboards to not suggest
@@ -148,6 +149,21 @@ void doShowFileOpen(String mimes) {
startActivityForResult(Intent.createChooser(intent, "Open File"), FILE_OPEN_CODE);
}
+ static void showFileSave(String mimes) {
+ goNativeActivity.doShowFileSave(mimes);
+ }
+
+ void doShowFileSave(String mimes) {
+ Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
+ if (mimes.contains("|") && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ intent.setType("*/*");
+ intent.putExtra(Intent.EXTRA_MIME_TYPES, mimes.split("\\|"));
+ } else {
+ intent.setType(mimes);
+ }
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ startActivityForResult(Intent.createChooser(intent, "Save File"), FILE_SAVE_CODE);
+ }
static int getRune(int deviceId, int keyCode, int metaState) {
try {
int rune = KeyCharacterMap.load(deviceId).get(keyCode, metaState);
@@ -241,7 +257,7 @@ public void afterTextChanged(Editable s) {
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// unhandled request
- if (requestCode != FILE_OPEN_CODE) {
+ if (requestCode != FILE_OPEN_CODE && requestCode != FILE_SAVE_CODE) {
return;
}
diff --git a/vendor/github.com/fyne-io/mobile/app/android.c b/vendor/github.com/fyne-io/mobile/app/android.c
index 2ed86d5854..0c1ed9e6e6 100644
--- a/vendor/github.com/fyne-io/mobile/app/android.c
+++ b/vendor/github.com/fyne-io/mobile/app/android.c
@@ -51,6 +51,7 @@ static jmethodID key_rune_method;
static jmethodID show_keyboard_method;
static jmethodID hide_keyboard_method;
static jmethodID show_file_open_method;
+static jmethodID show_file_save_method;
jint JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv* env;
@@ -82,6 +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");
setCurrentContext(activity->vm, (*env)->NewGlobalRef(env, activity->clazz));
@@ -239,6 +241,16 @@ void showFileOpen(JNIEnv* env, char* mimes) {
);
}
+void showFileSave(JNIEnv* env, char* mimes) {
+ jstring mimesJString = (*env)->NewStringUTF(env, mimes);
+ (*env)->CallStaticVoidMethod(
+ env,
+ current_class,
+ show_file_save_method,
+ mimesJString
+ );
+}
+
void Java_org_golang_app_GoNativeActivity_filePickerReturned(JNIEnv *env, jclass clazz, jstring str) {
const char* cstr = (*env)->GetStringUTFChars(env, str, JNI_FALSE);
filePickerReturned((char*)cstr);
@@ -255,4 +267,4 @@ void Java_org_golang_app_GoNativeActivity_keyboardTyped(JNIEnv *env, jclass claz
void Java_org_golang_app_GoNativeActivity_keyboardDelete(JNIEnv *env, jclass clazz) {
keyboardDelete();
-}
\ No newline at end of file
+}
diff --git a/vendor/github.com/fyne-io/mobile/app/android.go b/vendor/github.com/fyne-io/mobile/app/android.go
index 742dfff54b..7fc65902c2 100644
--- a/vendor/github.com/fyne-io/mobile/app/android.go
+++ b/vendor/github.com/fyne-io/mobile/app/android.go
@@ -35,8 +35,8 @@ package app
#include
#include
-EGLDisplay display;
-EGLSurface surface;
+extern EGLDisplay display;
+extern EGLSurface surface;
char* createEGLSurface(ANativeWindow* window);
char* destroyEGLSurface();
@@ -45,6 +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 Java_org_golang_app_GoNativeActivity_filePickerReturned(JNIEnv *env, jclass clazz, jstring str);
*/
@@ -337,9 +338,7 @@ func insetsChanged(top, bottom, left, right int) {
screenInsetTop, screenInsetBottom, screenInsetLeft, screenInsetRight = top, bottom, left, right
}
-func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter) {
- fileCallback = callback
-
+func mimeStringFromFilter(filter *FileFilter) string {
mimes := "*/*"
if filter.MimeTypes != nil {
mimes = strings.Join(filter.MimeTypes, "|")
@@ -362,6 +361,13 @@ func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter)
}
mimes = strings.Join(mimeTypes, "|")
}
+ return mimes
+}
+
+func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter) {
+ fileCallback = callback
+
+ mimes := mimeStringFromFilter(filter)
mimeStr := C.CString(mimes)
defer C.free(unsafe.Pointer(mimeStr))
@@ -377,6 +383,24 @@ func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter)
}
}
+func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter) {
+ fileCallback = callback
+
+ mimes := mimeStringFromFilter(filter)
+ mimeStr := C.CString(mimes)
+ defer C.free(unsafe.Pointer(mimeStr))
+
+ save := func(vm, jniEnv, ctx uintptr) error {
+ env := (*C.JNIEnv)(unsafe.Pointer(jniEnv)) // not a Go heap pointer
+ C.showFileSave(env, mimeStr)
+ return nil
+ }
+
+ if err := mobileinit.RunOnJVM(save); err != nil {
+ log.Fatalf("app: %v", err)
+ }
+}
+
var mainUserFn func(App)
var DisplayMetrics struct {
diff --git a/vendor/github.com/fyne-io/mobile/app/app.go b/vendor/github.com/fyne-io/mobile/app/app.go
index 44eccb6995..b1ca18b278 100644
--- a/vendor/github.com/fyne-io/mobile/app/app.go
+++ b/vendor/github.com/fyne-io/mobile/app/app.go
@@ -55,6 +55,7 @@ type App interface {
ShowVirtualKeyboard(KeyboardType)
HideVirtualKeyboard()
ShowFileOpenPicker(func(string, func()), *FileFilter)
+ ShowFileSavePicker(func(string, func()), *FileFilter)
}
type FileFilter struct {
@@ -149,6 +150,9 @@ 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)
+}
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 54f8b2ad2c..0e1a240ef2 100644
--- a/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go
+++ b/vendor/github.com/fyne-io/mobile/app/darwin_desktop.go
@@ -239,6 +239,10 @@ func driverHideVirtualKeyboard() {
func driverShowFileOpenPicker(func(string, func()), *FileFilter) {
}
+// driverShowFileSavePicker does nothing on desktop
+func driverShowFileSavePicker(func(string, func()), *FileFilter) {
+}
+
// convRune marks the Carbon/Cocoa private-range unicode rune representing
// a non-unicode key event to -1, used for Rune in the key package.
//
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 eb65c66bc6..dc30a97438 100644
--- a/vendor/github.com/fyne-io/mobile/app/darwin_ios.go
+++ b/vendor/github.com/fyne-io/mobile/app/darwin_ios.go
@@ -28,6 +28,7 @@ void showKeyboard(int keyboardType);
void hideKeyboard();
void showFileOpenPicker(char* mimes, char *exts);
+void showFileSavePicker(char* mimes, char *exts);
void closeFileResource(void* urlPtr);
*/
import "C"
@@ -258,6 +259,19 @@ func (a *app) loop(ctx C.GLintptr) {
}
}
+func cStringsForFilter(filter *FileFilter) (*C.char, *C.char) {
+ mimes := strings.Join(filter.MimeTypes, "|")
+
+ // extensions must have the '.' removed for UTI lookups on iOS
+ extList := []string{}
+ for _, ext := range filter.Extensions {
+ extList = append(extList, ext[1:])
+ }
+ exts := strings.Join(extList, "|")
+
+ return C.CString(mimes), C.CString(exts)
+}
+
// driverShowVirtualKeyboard requests the driver to show a virtual keyboard for text input
func driverShowVirtualKeyboard(keyboard KeyboardType) {
C.showKeyboard(C.int(int32(keyboard)))
@@ -285,19 +299,19 @@ func filePickerReturned(str *C.char, urlPtr unsafe.Pointer) {
func driverShowFileOpenPicker(callback func(string, func()), filter *FileFilter) {
fileCallback = callback
- mimes := strings.Join(filter.MimeTypes, "|")
+ mimeStr, extStr := cStringsForFilter(filter)
+ defer C.free(unsafe.Pointer(mimeStr))
+ defer C.free(unsafe.Pointer(extStr))
- // extensions must have the '.' removed for UTI lookups on iOS
- extList := []string{}
- for _, ext := range filter.Extensions {
- extList = append(extList, ext[1:])
- }
- exts := strings.Join(extList, "|")
+ C.showFileOpenPicker(mimeStr, extStr)
+}
- mimeStr := C.CString(mimes)
+func driverShowFileSavePicker(callback func(string, func()), filter *FileFilter) {
+ fileCallback = callback
+
+ mimeStr, extStr := cStringsForFilter(filter)
defer C.free(unsafe.Pointer(mimeStr))
- extStr := C.CString(exts)
defer C.free(unsafe.Pointer(extStr))
- C.showFileOpenPicker(mimeStr, extStr)
+ C.showFileSavePicker(mimeStr, extStr)
}
diff --git a/vendor/github.com/fyne-io/mobile/app/darwin_ios.m b/vendor/github.com/fyne-io/mobile/app/darwin_ios.m
index 7e9441a4a5..0081dc8630 100644
--- a/vendor/github.com/fyne-io/mobile/app/darwin_ios.m
+++ b/vendor/github.com/fyne-io/mobile/app/darwin_ios.m
@@ -294,9 +294,7 @@ void hideKeyboard() {
});
}
-void showFileOpenPicker(char* mimes, char *exts) {
- GoAppAppDelegate *appDelegate = (GoAppAppDelegate *)[[UIApplication sharedApplication] delegate];
-
+NSMutableArray *docTypesForMimeExts(char *mimes, char *exts) {
NSMutableArray *docTypes = [NSMutableArray array];
if (mimes != NULL && strlen(mimes) > 0) {
NSString *mimeList = [NSString stringWithUTF8String:mimes];
@@ -325,6 +323,14 @@ void showFileOpenPicker(char* mimes, char *exts) {
[docTypes addObject:@"public.data"];
}
+ return docTypes;
+}
+
+void showFileOpenPicker(char* mimes, char *exts) {
+ GoAppAppDelegate *appDelegate = (GoAppAppDelegate *)[[UIApplication sharedApplication] delegate];
+
+ NSMutableArray *docTypes = docTypesForMimeExts(mimes, exts);
+
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc]
initWithDocumentTypes:docTypes inMode:UIDocumentPickerModeOpen];
documentPicker.delegate = appDelegate;
@@ -334,6 +340,27 @@ void showFileOpenPicker(char* mimes, char *exts) {
});
}
+void showFileSavePicker(char* mimes, char *exts) {
+ GoAppAppDelegate *appDelegate = (GoAppAppDelegate *)[[UIApplication sharedApplication] delegate];
+
+ NSMutableArray *docTypes = docTypesForMimeExts(mimes, exts);
+
+ NSURL *temporaryDirectoryURL = [NSURL fileURLWithPath: NSTemporaryDirectory() isDirectory: YES];
+ NSURL *temporaryFileURL = [temporaryDirectoryURL URLByAppendingPathComponent:@"filename"];
+
+ char* bytes = "\n";
+ NSData *data = [NSData dataWithBytes:bytes length:1];
+ BOOL ok = [data writeToURL:temporaryFileURL atomically:YES];
+
+ UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc]
+ initWithURL:temporaryFileURL inMode:UIDocumentPickerModeMoveToService];
+ documentPicker.delegate = appDelegate;
+
+ dispatch_async(dispatch_get_main_queue(), ^{
+ [appDelegate.controller presentViewController:documentPicker animated:YES completion:nil];
+ });
+}
+
void closeFileResource(void* urlPtr) {
if (urlPtr == NULL) {
return;
diff --git a/vendor/github.com/fyne-io/mobile/app/shiny.go b/vendor/github.com/fyne-io/mobile/app/shiny.go
index c7e1adc351..b7c1926a43 100644
--- a/vendor/github.com/fyne-io/mobile/app/shiny.go
+++ b/vendor/github.com/fyne-io/mobile/app/shiny.go
@@ -25,3 +25,7 @@ func driverHideVirtualKeyboard() {
// driverShowFileOpenPicker does nothing on desktop
func driverShowFileOpenPicker(func(string, func()), *FileFilter) {
}
+
+// driverShowFileSavePicker does nothing on desktop
+func driverShowFileSavePicker(func(string, func()), *FileFilter) {
+}
diff --git a/vendor/github.com/fyne-io/mobile/app/x11.go b/vendor/github.com/fyne-io/mobile/app/x11.go
index e93dc4e663..edc3977af1 100644
--- a/vendor/github.com/fyne-io/mobile/app/x11.go
+++ b/vendor/github.com/fyne-io/mobile/app/x11.go
@@ -135,3 +135,7 @@ func driverHideVirtualKeyboard() {
// driverShowFileOpenPicker does nothing on desktop
func driverShowFileOpenPicker(func(string, func()), *FileFilter) {
}
+
+// driverShowFileSavePicker does nothing on desktop
+func driverShowFileSavePicker(func(string, func()), *FileFilter) {
+}
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/GLFW_C_REVISION.txt b/vendor/github.com/go-gl/glfw/v3.3/glfw/GLFW_C_REVISION.txt
index 6a8efe19df..2630b14ca1 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/GLFW_C_REVISION.txt
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/GLFW_C_REVISION.txt
@@ -1 +1 @@
-0a49ef0a00baa3ab520ddc452f0e3b1e099c5589
+746cdea490a70dd6747272a171b19e5ec1fb9900
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/build.go b/vendor/github.com/go-gl/glfw/v3.3/glfw/build.go
index acf4d926e3..b60c631dbd 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/build.go
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/build.go
@@ -7,8 +7,10 @@ package glfw
#cgo windows CFLAGS: -D_GLFW_WIN32 -Iglfw/deps/mingw
// Linker Options:
-#cgo windows LDFLAGS: -lopengl32 -lgdi32
+#cgo windows LDFLAGS: -lgdi32
+#cgo !gles2,windows LDFLAGS: -lopengl32
+#cgo gles2,windows LDFLAGS: -lGLESv2
// Darwin Build Tags
// ----------------
@@ -16,8 +18,10 @@ package glfw
#cgo darwin CFLAGS: -D_GLFW_COCOA -Wno-deprecated-declarations
// Linker Options:
-#cgo darwin LDFLAGS: -framework Cocoa -framework OpenGL -framework IOKit -framework CoreVideo
+#cgo darwin LDFLAGS: -framework Cocoa -framework IOKit -framework CoreVideo
+#cgo !gles2,darwin LDFLAGS: -framework OpenGL
+#cgo gles2,darwin LDFLAGS: -lGLESv2
// Linux Build Tags
// ----------------
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/linmath.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/linmath.h
index 9c2e2a0abf..0ab7a414b4 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/linmath.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/deps/linmath.h
@@ -237,9 +237,9 @@ static inline void mat4x4_rotate_Y(mat4x4 Q, mat4x4 M, float angle)
float s = sinf(angle);
float c = cosf(angle);
mat4x4 R = {
- { c, 0.f, s, 0.f},
+ { c, 0.f, -s, 0.f},
{ 0.f, 1.f, 0.f, 0.f},
- { -s, 0.f, c, 0.f},
+ { s, 0.f, c, 0.f},
{ 0.f, 0.f, 0.f, 1.f}
};
mat4x4_mul(Q, M, R);
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW/glfw3.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW/glfw3.h
index 66dff64905..35bbf0755f 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW/glfw3.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/include/GLFW/glfw3.h
@@ -52,7 +52,7 @@ extern "C" {
* This is the reference documentation for OpenGL and OpenGL ES context related
* functions. For more task-oriented information, see the @ref context_guide.
*/
-/*! @defgroup vulkan Vulkan reference
+/*! @defgroup vulkan Vulkan support reference
* @brief Functions and types related to Vulkan.
*
* This is the reference documentation for Vulkan related functions and types.
@@ -193,7 +193,38 @@ extern "C" {
#endif /*__APPLE__*/
-#elif !defined(GLFW_INCLUDE_NONE)
+#elif defined(GLFW_INCLUDE_GLU)
+
+ #if defined(__APPLE__)
+
+ #if defined(GLFW_INCLUDE_GLU)
+ #include
+ #endif
+
+ #else /*__APPLE__*/
+
+ #if defined(GLFW_INCLUDE_GLU)
+ #include
+ #endif
+
+ #endif /*__APPLE__*/
+
+#elif !defined(GLFW_INCLUDE_NONE) && \
+ !defined(__gl_h_) && \
+ !defined(__gles1_gl_h_) && \
+ !defined(__gles2_gl2_h_) && \
+ !defined(__gles2_gl3_h_) && \
+ !defined(__gles2_gl31_h_) && \
+ !defined(__gles2_gl32_h_) && \
+ !defined(__gl_glcorearb_h_) && \
+ !defined(__gl2_h_) /*legacy*/ && \
+ !defined(__gl3_h_) /*legacy*/ && \
+ !defined(__gl31_h_) /*legacy*/ && \
+ !defined(__gl32_h_) /*legacy*/ && \
+ !defined(__glcorearb_h_) /*legacy*/ && \
+ !defined(__GL_H__) /*non-standard*/ && \
+ !defined(__gltypes_h_) /*non-standard*/ && \
+ !defined(__glee_h_) /*non-standard*/
#if defined(__APPLE__)
@@ -201,9 +232,6 @@ extern "C" {
#define GL_GLEXT_LEGACY
#endif
#include
- #if defined(GLFW_INCLUDE_GLU)
- #include
- #endif
#else /*__APPLE__*/
@@ -211,9 +239,6 @@ extern "C" {
#if defined(GLFW_INCLUDE_GLEXT)
#include
#endif
- #if defined(GLFW_INCLUDE_GLU)
- #include
- #endif
#endif /*__APPLE__*/
@@ -270,7 +295,7 @@ extern "C" {
* API changes.
* @ingroup init
*/
-#define GLFW_VERSION_REVISION 2
+#define GLFW_VERSION_REVISION 3
/*! @} */
/*! @brief One.
@@ -949,9 +974,9 @@ extern "C" {
* and [attribute](@ref GLFW_OPENGL_FORWARD_COMPAT_attrib).
*/
#define GLFW_OPENGL_FORWARD_COMPAT 0x00022006
-/*! @brief OpenGL debug context hint and attribute.
+/*! @brief Debug mode context hint and attribute.
*
- * OpenGL debug context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
+ * Debug mode context [hint](@ref GLFW_OPENGL_DEBUG_CONTEXT_hint) and
* [attribute](@ref GLFW_OPENGL_DEBUG_CONTEXT_attrib).
*/
#define GLFW_OPENGL_DEBUG_CONTEXT 0x00022007
@@ -1328,7 +1353,7 @@ typedef void (* GLFWwindowiconifyfun)(GLFWwindow*,int);
* @endcode
*
* @param[in] window The window that was maximized or restored.
- * @param[in] iconified `GLFW_TRUE` if the window was maximized, or
+ * @param[in] maximized `GLFW_TRUE` if the window was maximized, or
* `GLFW_FALSE` if it was restored.
*
* @sa @ref window_maximize
@@ -1753,6 +1778,10 @@ typedef struct GLFWgamepadstate
* bundle, if present. This can be disabled with the @ref
* GLFW_COCOA_CHDIR_RESOURCES init hint.
*
+ * @remark @x11 This function will set the `LC_CTYPE` category of the
+ * application locale according to the current environment if that category is
+ * still "C". This is because the "C" locale breaks Unicode text input.
+ *
* @thread_safety This function must only be called from the main thread.
*
* @sa @ref intro_init
@@ -1776,6 +1805,8 @@ GLFWAPI int glfwInit(void);
* call this function, as it is called by @ref glfwInit before it returns
* failure.
*
+ * This function has no effect if GLFW is not initialized.
+ *
* @errors Possible errors include @ref GLFW_PLATFORM_ERROR.
*
* @remark This function may be called before @ref glfwInit.
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_init.m b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_init.m
index 4b8e74b624..209639e270 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_init.m
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_init.m
@@ -439,13 +439,6 @@ - (void)applicationWillFinishLaunching:(NSNotification *)notification
}
else
createMenuBar();
-
- // Fix for issue #1648: menubar not clickable on macOS Catalina until
- // it lost and regained focus
- dispatch_async(dispatch_get_main_queue(), ^{
- // In case we are unbundled, make us a proper UI application
- [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
- });
}
}
@@ -453,6 +446,11 @@ - (void)applicationDidFinishLaunching:(NSNotification *)notification
{
_glfw.ns.finishedLaunching = GLFW_TRUE;
_glfwPlatformPostEmptyEvent();
+
+ // In case we are unbundled, make us a proper UI application
+ if (_glfw.hints.init.ns.menubar)
+ [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
+
[NSApp stop:nil];
}
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_monitor.m b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_monitor.m
index 8ef94a3b0f..55638cf0d9 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_monitor.m
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_monitor.m
@@ -39,8 +39,21 @@
// Get the name of the specified display, or NULL
//
-static char* getDisplayName(CGDirectDisplayID displayID)
+static char* getMonitorName(CGDirectDisplayID displayID, NSScreen* screen)
{
+ // IOKit doesn't work on Apple Silicon anymore
+ // Luckily, 10.15 introduced -[NSScreen localizedName].
+ // Use it if available, and fall back to IOKit otherwise.
+ if (screen)
+ {
+ if ([screen respondsToSelector:@selector(localizedName)])
+ {
+ NSString* name = [screen valueForKey:@"localizedName"];
+ if (name)
+ return _glfw_strdup([name UTF8String]);
+ }
+ }
+
io_iterator_t it;
io_service_t service;
CFDictionaryRef info;
@@ -209,31 +222,6 @@ static void endFadeReservation(CGDisplayFadeReservationToken token)
}
}
-// Finds and caches the NSScreen corresponding to the specified monitor
-//
-static GLFWbool refreshMonitorScreen(_GLFWmonitor* monitor)
-{
- if (monitor->ns.screen)
- return GLFW_TRUE;
-
- for (NSScreen* screen in [NSScreen screens])
- {
- NSNumber* displayID = [screen deviceDescription][@"NSScreenNumber"];
-
- // HACK: Compare unit numbers instead of display IDs to work around
- // display replacement on machines with automatic graphics
- // switching
- if (monitor->ns.unitNumber == CGDisplayUnitNumber([displayID unsignedIntValue]))
- {
- monitor->ns.screen = screen;
- return GLFW_TRUE;
- }
- }
-
- _glfwInputError(GLFW_PLATFORM_ERROR, "Cocoa: Failed to find a screen for monitor");
- return GLFW_FALSE;
-}
-
// Returns the display refresh rate queried from the I/O registry
//
static double getFallbackRefreshRate(CGDirectDisplayID displayID)
@@ -277,14 +265,20 @@ static double getFallbackRefreshRate(CGDirectDisplayID displayID)
CFSTR("IOFBCurrentPixelCount"),
kCFAllocatorDefault,
kNilOptions);
- if (!clockRef || !countRef)
- break;
uint32_t clock = 0, count = 0;
- CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
- CFNumberGetValue(countRef, kCFNumberIntType, &count);
- CFRelease(clockRef);
- CFRelease(countRef);
+
+ if (clockRef)
+ {
+ CFNumberGetValue(clockRef, kCFNumberIntType, &clock);
+ CFRelease(clockRef);
+ }
+
+ if (countRef)
+ {
+ CFNumberGetValue(countRef, kCFNumberIntType, &count);
+ CFRelease(countRef);
+ }
if (clock > 0 && count > 0)
refreshRate = clock / (double) count;
@@ -328,27 +322,46 @@ void _glfwPollMonitorsNS(void)
if (CGDisplayIsAsleep(displays[i]))
continue;
+ const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
+ NSScreen* screen = nil;
+
+ for (screen in [NSScreen screens])
+ {
+ NSNumber* screenNumber = [screen deviceDescription][@"NSScreenNumber"];
+
+ // HACK: Compare unit numbers instead of display IDs to work around
+ // display replacement on machines with automatic graphics
+ // switching
+ if (CGDisplayUnitNumber([screenNumber unsignedIntValue]) == unitNumber)
+ break;
+ }
+
// HACK: Compare unit numbers instead of display IDs to work around
// display replacement on machines with automatic graphics
// switching
- const uint32_t unitNumber = CGDisplayUnitNumber(displays[i]);
- for (uint32_t j = 0; j < disconnectedCount; j++)
+ uint32_t j;
+ for (j = 0; j < disconnectedCount; j++)
{
if (disconnected[j] && disconnected[j]->ns.unitNumber == unitNumber)
{
+ disconnected[j]->ns.screen = screen;
disconnected[j] = NULL;
break;
}
}
+ if (j < disconnectedCount)
+ continue;
+
const CGSize size = CGDisplayScreenSize(displays[i]);
- char* name = getDisplayName(displays[i]);
+ char* name = getMonitorName(displays[i], screen);
if (!name)
name = _glfw_strdup("Unknown");
_GLFWmonitor* monitor = _glfwAllocMonitor(name, size.width, size.height);
monitor->ns.displayID = displays[i];
monitor->ns.unitNumber = unitNumber;
+ monitor->ns.screen = screen;
free(name);
@@ -457,8 +470,11 @@ void _glfwPlatformGetMonitorContentScale(_GLFWmonitor* monitor,
{
@autoreleasepool {
- if (!refreshMonitorScreen(monitor))
- return;
+ if (!monitor->ns.screen)
+ {
+ _glfwInputError(GLFW_PLATFORM_ERROR,
+ "Cocoa: Cannot query content scale without screen");
+ }
const NSRect points = [monitor->ns.screen frame];
const NSRect pixels = [monitor->ns.screen convertRectToBacking:points];
@@ -477,8 +493,11 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
{
@autoreleasepool {
- if (!refreshMonitorScreen(monitor))
- return;
+ if (!monitor->ns.screen)
+ {
+ _glfwInputError(GLFW_PLATFORM_ERROR,
+ "Cocoa: Cannot query workarea without screen");
+ }
const NSRect frameRect = [monitor->ns.screen visibleFrame];
@@ -521,7 +540,7 @@ void _glfwPlatformGetMonitorWorkarea(_GLFWmonitor* monitor,
}
// Skip duplicate modes
- if (i < *count)
+ if (j < *count)
continue;
(*count)++;
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_platform.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_platform.h
index 15c2169619..05c23b70f9 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_platform.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_platform.h
@@ -92,7 +92,7 @@ typedef VkResult (APIENTRY *PFN_vkCreateMetalSurfaceEXT)(VkInstance,const VkMeta
#define _glfw_dlclose(handle) dlclose(handle)
#define _glfw_dlsym(handle, name) dlsym(handle, name)
-#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.view)
+#define _GLFW_EGL_NATIVE_WINDOW ((EGLNativeWindowType) window->ns.layer)
#define _GLFW_EGL_NATIVE_DISPLAY EGL_DEFAULT_DISPLAY
#define _GLFW_PLATFORM_WINDOW_STATE _GLFWwindowNS ns
@@ -121,6 +121,7 @@ typedef struct _GLFWwindowNS
id layer;
GLFWbool maximized;
+ GLFWbool occluded;
GLFWbool retina;
// Cached window properties to filter out duplicate events
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_window.m b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_window.m
index 129e975e55..9fa72a6b7e 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_window.m
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/cocoa_window.m
@@ -322,6 +322,14 @@ - (void)windowDidResignKey:(NSNotification *)notification
_glfwInputWindowFocus(window, GLFW_FALSE);
}
+- (void)windowDidChangeOcclusionState:(NSNotification* )notification
+{
+ if ([window->ns.object occlusionState] & NSWindowOcclusionStateVisible)
+ window->ns.occluded = GLFW_FALSE;
+ else
+ window->ns.occluded = GLFW_TRUE;
+}
+
@end
@@ -723,14 +731,24 @@ - (void)insertText:(id)string replacementRange:(NSRange)replacementRange
else
characters = (NSString*) string;
- const NSUInteger length = [characters length];
- for (NSUInteger i = 0; i < length; i++)
+ NSRange range = NSMakeRange(0, [characters length]);
+ while (range.length)
{
- const unichar codepoint = [characters characterAtIndex:i];
- if ((codepoint & 0xff00) == 0xf700)
- continue;
+ uint32_t codepoint = 0;
+
+ if ([characters getBytes:&codepoint
+ maxLength:sizeof(codepoint)
+ usedLength:NULL
+ encoding:NSUTF32StringEncoding
+ options:0
+ range:range
+ remainingRange:&range])
+ {
+ if (codepoint >= 0xf700 && codepoint <= 0xf7ff)
+ continue;
- _glfwInputChar(window, codepoint, mods, plain);
+ _glfwInputChar(window, codepoint, mods, plain);
+ }
}
}
@@ -901,6 +919,11 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
}
else if (ctxconfig->source == GLFW_EGL_CONTEXT_API)
{
+ // EGL implementation on macOS use CALayer* EGLNativeWindowType so we
+ // need to get the layer for EGL window surface creation.
+ [window->ns.view setWantsLayer:YES];
+ window->ns.layer = [window->ns.view layer];
+
if (!_glfwInitEGL())
return GLFW_FALSE;
if (!_glfwCreateContextEGL(window, ctxconfig, fbconfig))
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/egl_context.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/egl_context.c
index e458bfb87e..6288fb7ce4 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/egl_context.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/egl_context.c
@@ -588,18 +588,16 @@ GLFWbool _glfwCreateContextEGL(_GLFWwindow* window,
}
// Set up attributes for surface creation
- {
- int index = 0;
-
- if (fbconfig->sRGB)
- {
- if (_glfw.egl.KHR_gl_colorspace)
- setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
- }
+ index = 0;
- setAttrib(EGL_NONE, EGL_NONE);
+ if (fbconfig->sRGB)
+ {
+ if (_glfw.egl.KHR_gl_colorspace)
+ setAttrib(EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_SRGB_KHR);
}
+ setAttrib(EGL_NONE, EGL_NONE);
+
window->context.egl.surface =
eglCreateWindowSurface(_glfw.egl.display,
config,
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/internal.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/internal.h
index 92b9497abb..91631c0620 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/internal.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/internal.h
@@ -342,9 +342,9 @@ struct _GLFWcontext
int robustness;
int release;
- PFNGLGETSTRINGIPROC GetStringi;
+ PFNGLGETSTRINGIPROC GetStringi;
PFNGLGETINTEGERVPROC GetIntegerv;
- PFNGLGETSTRINGPROC GetString;
+ PFNGLGETSTRINGPROC GetString;
_GLFWmakecontextcurrentfun makeCurrent;
_GLFWswapbuffersfun swapBuffers;
@@ -396,23 +396,23 @@ struct _GLFWwindow
_GLFWcontext context;
struct {
- GLFWwindowposfun pos;
- GLFWwindowsizefun size;
- GLFWwindowclosefun close;
- GLFWwindowrefreshfun refresh;
- GLFWwindowfocusfun focus;
- GLFWwindowiconifyfun iconify;
- GLFWwindowmaximizefun maximize;
- GLFWframebuffersizefun fbsize;
+ GLFWwindowposfun pos;
+ GLFWwindowsizefun size;
+ GLFWwindowclosefun close;
+ GLFWwindowrefreshfun refresh;
+ GLFWwindowfocusfun focus;
+ GLFWwindowiconifyfun iconify;
+ GLFWwindowmaximizefun maximize;
+ GLFWframebuffersizefun fbsize;
GLFWwindowcontentscalefun scale;
- GLFWmousebuttonfun mouseButton;
- GLFWcursorposfun cursorPos;
- GLFWcursorenterfun cursorEnter;
- GLFWscrollfun scroll;
- GLFWkeyfun key;
- GLFWcharfun character;
- GLFWcharmodsfun charmods;
- GLFWdropfun drop;
+ GLFWmousebuttonfun mouseButton;
+ GLFWcursorposfun cursorPos;
+ GLFWcursorenterfun cursorEnter;
+ GLFWscrollfun scroll;
+ GLFWkeyfun key;
+ GLFWcharfun character;
+ GLFWcharmodsfun charmods;
+ GLFWdropfun drop;
} callbacks;
// This is defined in the window API's platform.h
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/nsgl_context.m b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/nsgl_context.m
index 3bcfafcc7a..10286849ba 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/nsgl_context.m
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/nsgl_context.m
@@ -51,7 +51,7 @@ static void swapBuffersNSGL(_GLFWwindow* window)
// HACK: Simulate vsync with usleep as NSGL swap interval does not apply to
// windows with a non-visible occlusion state
- if (!([window->ns.object occlusionState] & NSWindowOcclusionStateVisible))
+ if (window->ns.occluded)
{
int interval = 0;
[window->context.nsgl.object getValues:&interval
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.c
index 8ac3c12699..6a70a81d35 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.c
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
/*
* Copyright © 2015 Samsung Electronics Co., Ltd
@@ -40,14 +40,14 @@
extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zwp_idle_inhibitor_v1_interface;
-static const struct wl_interface *idle_inhibit_unstable_v1_wl_ii_types[] = {
+static const struct wl_interface *idle_inhibit_unstable_v1_types[] = {
&zwp_idle_inhibitor_v1_interface,
&wl_surface_interface,
};
static const struct wl_message zwp_idle_inhibit_manager_v1_requests[] = {
- { "destroy", "", idle_inhibit_unstable_v1_wl_ii_types + 0 },
- { "create_inhibitor", "no", idle_inhibit_unstable_v1_wl_ii_types + 0 },
+ { "destroy", "", idle_inhibit_unstable_v1_types + 0 },
+ { "create_inhibitor", "no", idle_inhibit_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_idle_inhibit_manager_v1_interface = {
@@ -57,7 +57,7 @@ WL_PRIVATE const struct wl_interface zwp_idle_inhibit_manager_v1_interface = {
};
static const struct wl_message zwp_idle_inhibitor_v1_requests[] = {
- { "destroy", "", idle_inhibit_unstable_v1_wl_ii_types + 0 },
+ { "destroy", "", idle_inhibit_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_idle_inhibitor_v1_interface = {
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.h
index 65ece1f4e7..7aa028e1b4 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-idle-inhibit-unstable-v1-client-protocol.h
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
#ifndef IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.c
index 3779b8c6ff..f97c52eb56 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.c
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
/*
* Copyright © 2014 Jonas Ådahl
@@ -44,7 +44,7 @@ extern const struct wl_interface wl_surface_interface;
extern const struct wl_interface zwp_confined_pointer_v1_interface;
extern const struct wl_interface zwp_locked_pointer_v1_interface;
-static const struct wl_interface *pointer_constraints_unstable_v1_wl_pc_types[] = {
+static const struct wl_interface *pointer_constraints_unstable_v1_types[] = {
NULL,
NULL,
&zwp_locked_pointer_v1_interface,
@@ -62,9 +62,9 @@ static const struct wl_interface *pointer_constraints_unstable_v1_wl_pc_types[]
};
static const struct wl_message zwp_pointer_constraints_v1_requests[] = {
- { "destroy", "", pointer_constraints_unstable_v1_wl_pc_types + 0 },
- { "lock_pointer", "noo?ou", pointer_constraints_unstable_v1_wl_pc_types + 2 },
- { "confine_pointer", "noo?ou", pointer_constraints_unstable_v1_wl_pc_types + 7 },
+ { "destroy", "", pointer_constraints_unstable_v1_types + 0 },
+ { "lock_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 2 },
+ { "confine_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 7 },
};
WL_PRIVATE const struct wl_interface zwp_pointer_constraints_v1_interface = {
@@ -74,14 +74,14 @@ WL_PRIVATE const struct wl_interface zwp_pointer_constraints_v1_interface = {
};
static const struct wl_message zwp_locked_pointer_v1_requests[] = {
- { "destroy", "", pointer_constraints_unstable_v1_wl_pc_types + 0 },
- { "set_cursor_position_hint", "ff", pointer_constraints_unstable_v1_wl_pc_types + 0 },
- { "set_region", "?o", pointer_constraints_unstable_v1_wl_pc_types + 12 },
+ { "destroy", "", pointer_constraints_unstable_v1_types + 0 },
+ { "set_cursor_position_hint", "ff", pointer_constraints_unstable_v1_types + 0 },
+ { "set_region", "?o", pointer_constraints_unstable_v1_types + 12 },
};
static const struct wl_message zwp_locked_pointer_v1_events[] = {
- { "locked", "", pointer_constraints_unstable_v1_wl_pc_types + 0 },
- { "unlocked", "", pointer_constraints_unstable_v1_wl_pc_types + 0 },
+ { "locked", "", pointer_constraints_unstable_v1_types + 0 },
+ { "unlocked", "", pointer_constraints_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_locked_pointer_v1_interface = {
@@ -91,13 +91,13 @@ WL_PRIVATE const struct wl_interface zwp_locked_pointer_v1_interface = {
};
static const struct wl_message zwp_confined_pointer_v1_requests[] = {
- { "destroy", "", pointer_constraints_unstable_v1_wl_pc_types + 0 },
- { "set_region", "?o", pointer_constraints_unstable_v1_wl_pc_types + 13 },
+ { "destroy", "", pointer_constraints_unstable_v1_types + 0 },
+ { "set_region", "?o", pointer_constraints_unstable_v1_types + 13 },
};
static const struct wl_message zwp_confined_pointer_v1_events[] = {
- { "confined", "", pointer_constraints_unstable_v1_wl_pc_types + 0 },
- { "unconfined", "", pointer_constraints_unstable_v1_wl_pc_types + 0 },
+ { "confined", "", pointer_constraints_unstable_v1_types + 0 },
+ { "unconfined", "", pointer_constraints_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_confined_pointer_v1_interface = {
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.h
index f66f8d75dd..8ae3420296 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-pointer-constraints-unstable-v1-client-protocol.h
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
#ifndef POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.c
index de60756936..cb8946b43b 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.c
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
/*
* Copyright © 2014 Jonas Ådahl
@@ -41,7 +41,7 @@
extern const struct wl_interface wl_pointer_interface;
extern const struct wl_interface zwp_relative_pointer_v1_interface;
-static const struct wl_interface *relative_pointer_unstable_v1_wl_rp_types[] = {
+static const struct wl_interface *relative_pointer_unstable_v1_types[] = {
NULL,
NULL,
NULL,
@@ -53,8 +53,8 @@ static const struct wl_interface *relative_pointer_unstable_v1_wl_rp_types[] = {
};
static const struct wl_message zwp_relative_pointer_manager_v1_requests[] = {
- { "destroy", "", relative_pointer_unstable_v1_wl_rp_types + 0 },
- { "get_relative_pointer", "no", relative_pointer_unstable_v1_wl_rp_types + 6 },
+ { "destroy", "", relative_pointer_unstable_v1_types + 0 },
+ { "get_relative_pointer", "no", relative_pointer_unstable_v1_types + 6 },
};
WL_PRIVATE const struct wl_interface zwp_relative_pointer_manager_v1_interface = {
@@ -64,11 +64,11 @@ WL_PRIVATE const struct wl_interface zwp_relative_pointer_manager_v1_interface =
};
static const struct wl_message zwp_relative_pointer_v1_requests[] = {
- { "destroy", "", relative_pointer_unstable_v1_wl_rp_types + 0 },
+ { "destroy", "", relative_pointer_unstable_v1_types + 0 },
};
static const struct wl_message zwp_relative_pointer_v1_events[] = {
- { "relative_motion", "uuffff", relative_pointer_unstable_v1_wl_rp_types + 0 },
+ { "relative_motion", "uuffff", relative_pointer_unstable_v1_types + 0 },
};
WL_PRIVATE const struct wl_interface zwp_relative_pointer_v1_interface = {
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.h
index 633d084ded..ead5e5d727 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-relative-pointer-unstable-v1-client-protocol.h
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
#ifndef RELATIVE_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define RELATIVE_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-viewporter-client-protocol.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-viewporter-client-protocol.c
index cee474e442..9526e837be 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-viewporter-client-protocol.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-viewporter-client-protocol.c
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
/*
* Copyright © 2013-2016 Collabora, Ltd.
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-viewporter-client-protocol.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-viewporter-client-protocol.h
index 90a4407d80..bade25aa21 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-viewporter-client-protocol.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-viewporter-client-protocol.h
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
#ifndef VIEWPORTER_CLIENT_PROTOCOL_H
#define VIEWPORTER_CLIENT_PROTOCOL_H
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.c
index 1aadb23c3a..d8dbb2fd3e 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-decoration-unstable-v1-client-protocol.c
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
/*
* Copyright © 2018 Simon Ser
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-unstable-v1-client-protocol.h
index 88378542bc..6bd1bb3e77 100644
--- 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-unstable-v1-client-protocol.h
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
#ifndef XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
#define XDG_DECORATION_UNSTABLE_V1_CLIENT_PROTOCOL_H
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-shell-client-protocol.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-shell-client-protocol.c
index 6bcc9fd165..9f8f0a5807 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-shell-client-protocol.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-shell-client-protocol.c
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
/*
* Copyright © 2008-2013 Kristian Høgsberg
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-shell-client-protocol.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-shell-client-protocol.h
index 14aa30527c..40baa88844 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-shell-client-protocol.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wayland-xdg-shell-client-protocol.h
@@ -1,4 +1,4 @@
-/* Generated by wayland-scanner 1.18.0 */
+/* Generated by wayland-scanner */
#ifndef XDG_SHELL_CLIENT_PROTOCOL_H
#define XDG_SHELL_CLIENT_PROTOCOL_H
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wgl_context.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wgl_context.h
index d982118809..1603f15f36 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wgl_context.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wgl_context.h
@@ -104,10 +104,6 @@ typedef BOOL (WINAPI * PFN_wglShareLists)(HGLRC,HGLRC);
#define wglMakeCurrent _glfw.wgl.MakeCurrent
#define wglShareLists _glfw.wgl.ShareLists
-#define _GLFW_RECREATION_NOT_NEEDED 0
-#define _GLFW_RECREATION_REQUIRED 1
-#define _GLFW_RECREATION_IMPOSSIBLE 2
-
#define _GLFW_PLATFORM_CONTEXT_STATE _GLFWcontextWGL wgl
#define _GLFW_PLATFORM_LIBRARY_CONTEXT_STATE _GLFWlibraryWGL wgl
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_init.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_init.c
index b2aa0a3d51..a7a6be6acc 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_init.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_init.c
@@ -143,6 +143,8 @@ static GLFWbool loadLibraries(void)
GetProcAddress(_glfw.win32.dwmapi.instance, "DwmFlush");
_glfw.win32.dwmapi.EnableBlurBehindWindow = (PFN_DwmEnableBlurBehindWindow)
GetProcAddress(_glfw.win32.dwmapi.instance, "DwmEnableBlurBehindWindow");
+ _glfw.win32.dwmapi.GetColorizationColor = (PFN_DwmGetColorizationColor)
+ GetProcAddress(_glfw.win32.dwmapi.instance, "DwmGetColorizationColor");
}
_glfw.win32.shcore.instance = LoadLibraryA("shcore.dll");
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_joystick.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_joystick.c
index 4ffc9fbf40..62ad7a5306 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_joystick.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_joystick.c
@@ -356,7 +356,7 @@ static BOOL CALLBACK deviceCallback(const DIDEVICEINSTANCE* di, void* user)
for (jid = 0; jid <= GLFW_JOYSTICK_LAST; jid++)
{
- _GLFWjoystick* js = _glfw.joysticks + jid;
+ js = _glfw.joysticks + jid;
if (js->present)
{
if (memcmp(&js->win32.guid, &di->guidInstance, sizeof(GUID)) == 0)
@@ -672,11 +672,11 @@ int _glfwPlatformPollJoystick(_GLFWjoystick* js, int mode)
};
// Screams of horror are appropriate at this point
- int state = LOWORD(*(DWORD*) data) / (45 * DI_DEGREES);
- if (state < 0 || state > 8)
- state = 8;
+ int stateIndex = LOWORD(*(DWORD*) data) / (45 * DI_DEGREES);
+ if (stateIndex < 0 || stateIndex > 8)
+ stateIndex = 8;
- _glfwInputJoystickHat(js, pi, states[state]);
+ _glfwInputJoystickHat(js, pi, states[stateIndex]);
pi++;
break;
}
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_monitor.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_monitor.c
index 4f5af36a4c..c8bae35965 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_monitor.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_monitor.c
@@ -185,6 +185,8 @@ void _glfwPollMonitorsWin32(void)
display.DeviceName) == 0)
{
disconnected[i] = NULL;
+ // handle may have changed, update
+ EnumDisplayMonitors(NULL, NULL, monitorCallback, (LPARAM) _glfw.monitors[i]);
break;
}
}
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_platform.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_platform.h
index be1dc54491..003b8a1466 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_platform.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_platform.h
@@ -77,6 +77,9 @@
#ifndef WM_DWMCOMPOSITIONCHANGED
#define WM_DWMCOMPOSITIONCHANGED 0x031E
#endif
+#ifndef WM_DWMCOLORIZATIONCOLORCHANGED
+ #define WM_DWMCOLORIZATIONCOLORCHANGED 0x0320
+#endif
#ifndef WM_COPYGLOBALDATA
#define WM_COPYGLOBALDATA 0x0049
#endif
@@ -99,7 +102,7 @@
#define DISPLAY_DEVICE_ACTIVE 0x00000001
#endif
#ifndef _WIN32_WINNT_WINBLUE
- #define _WIN32_WINNT_WINBLUE 0x0602
+ #define _WIN32_WINNT_WINBLUE 0x0603
#endif
#ifndef _WIN32_WINNT_WIN8
#define _WIN32_WINNT_WIN8 0x0602
@@ -247,9 +250,11 @@ typedef BOOL (WINAPI * PFN_AdjustWindowRectExForDpi)(LPRECT,DWORD,BOOL,DWORD,UIN
typedef HRESULT (WINAPI * PFN_DwmIsCompositionEnabled)(BOOL*);
typedef HRESULT (WINAPI * PFN_DwmFlush)(VOID);
typedef HRESULT(WINAPI * PFN_DwmEnableBlurBehindWindow)(HWND,const DWM_BLURBEHIND*);
+typedef HRESULT (WINAPI * PFN_DwmGetColorizationColor)(DWORD*,BOOL*);
#define DwmIsCompositionEnabled _glfw.win32.dwmapi.IsCompositionEnabled
#define DwmFlush _glfw.win32.dwmapi.Flush
#define DwmEnableBlurBehindWindow _glfw.win32.dwmapi.EnableBlurBehindWindow
+#define DwmGetColorizationColor _glfw.win32.dwmapi.GetColorizationColor
// shcore.dll function pointer typedefs
typedef HRESULT (WINAPI * PFN_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS);
@@ -316,8 +321,13 @@ typedef struct _GLFWwindowWin32
GLFWbool transparent;
GLFWbool scaleToMonitor;
+ // Cached size used to filter out duplicate events
+ int width, height;
+
// The last received cursor position, regardless of source
int lastCursorPosX, lastCursorPosY;
+ // The last recevied high surrogate when decoding pairs of UTF-16 messages
+ WCHAR highSurrogate;
} _GLFWwindowWin32;
@@ -373,6 +383,7 @@ typedef struct _GLFWlibraryWin32
PFN_DwmIsCompositionEnabled IsCompositionEnabled;
PFN_DwmFlush Flush;
PFN_DwmEnableBlurBehindWindow EnableBlurBehindWindow;
+ PFN_DwmGetColorizationColor GetColorizationColor;
} dwmapi;
struct {
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_window.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_window.c
index 0ce22ee7ad..d17b6da480 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_window.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/win32_window.c
@@ -377,12 +377,17 @@ static void updateWindowStyles(const _GLFWwindow* window)
//
static void updateFramebufferTransparency(const _GLFWwindow* window)
{
- BOOL enabled;
+ BOOL composition, opaque;
+ DWORD color;
if (!IsWindowsVistaOrGreater())
return;
- if (SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled)
+ if (FAILED(DwmIsCompositionEnabled(&composition)) || !composition)
+ return;
+
+ if (IsWindows8OrGreater() ||
+ (SUCCEEDED(DwmGetColorizationColor(&color, &opaque)) && !opaque))
{
HRGN region = CreateRectRgn(0, 0, -1, -1);
DWM_BLURBEHIND bb = {0};
@@ -390,37 +395,18 @@ static void updateFramebufferTransparency(const _GLFWwindow* window)
bb.hRgnBlur = region;
bb.fEnable = TRUE;
- if (SUCCEEDED(DwmEnableBlurBehindWindow(window->win32.handle, &bb)))
- {
- // Decorated windows don't repaint the transparent background
- // leaving a trail behind animations
- // HACK: Making the window layered with a transparency color key
- // seems to fix this. Normally, when specifying
- // a transparency color key to be used when composing the
- // layered window, all pixels painted by the window in this
- // color will be transparent. That doesn't seem to be the
- // case anymore, at least when used with blur behind window
- // plus negative region.
- LONG exStyle = GetWindowLongW(window->win32.handle, GWL_EXSTYLE);
- exStyle |= WS_EX_LAYERED;
- SetWindowLongW(window->win32.handle, GWL_EXSTYLE, exStyle);
-
- // Using a color key not equal to black to fix the trailing
- // issue. When set to black, something is making the hit test
- // not resize with the window frame.
- SetLayeredWindowAttributes(window->win32.handle,
- RGB(255, 0, 255), 255, LWA_COLORKEY);
- }
-
+ DwmEnableBlurBehindWindow(window->win32.handle, &bb);
DeleteObject(region);
}
else
{
- LONG exStyle = GetWindowLongW(window->win32.handle, GWL_EXSTYLE);
- exStyle &= ~WS_EX_LAYERED;
- SetWindowLongW(window->win32.handle, GWL_EXSTYLE, exStyle);
- RedrawWindow(window->win32.handle, NULL, NULL,
- RDW_ERASE | RDW_INVALIDATE | RDW_FRAME);
+ // HACK: Disable framebuffer transparency on Windows 7 when the
+ // colorization color is opaque, because otherwise the window
+ // contents is blended additively with the previous frame instead
+ // of replacing it
+ DWM_BLURBEHIND bb = {0};
+ bb.dwFlags = DWM_BB_ENABLE;
+ DwmEnableBlurBehindWindow(window->win32.handle, &bb);
}
}
@@ -519,7 +505,17 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
case WM_NCCREATE:
{
if (_glfwIsWindows10AnniversaryUpdateOrGreaterWin32())
- EnableNonClientDpiScaling(hWnd);
+ {
+ const CREATESTRUCTW* cs = (const CREATESTRUCTW*) lParam;
+ const _GLFWwndconfig* wndconfig = cs->lpCreateParams;
+
+ // On per-monitor DPI aware V1 systems, only enable
+ // non-client scaling for windows that scale the client area
+ // We need WM_GETDPISCALEDSIZE from V2 to keep the client
+ // area static when the non-client area is scaled
+ if (wndconfig && wndconfig->scaleToMonitor)
+ EnableNonClientDpiScaling(hWnd);
+ }
break;
}
@@ -645,11 +641,35 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
case WM_CHAR:
case WM_SYSCHAR:
- case WM_UNICHAR:
{
- const GLFWbool plain = (uMsg != WM_SYSCHAR);
+ if (wParam >= 0xd800 && wParam <= 0xdbff)
+ window->win32.highSurrogate = (WCHAR) wParam;
+ else
+ {
+ unsigned int codepoint = 0;
+
+ if (wParam >= 0xdc00 && wParam <= 0xdfff)
+ {
+ if (window->win32.highSurrogate)
+ {
+ codepoint += (window->win32.highSurrogate - 0xd800) << 10;
+ codepoint += (WCHAR) wParam - 0xdc00;
+ codepoint += 0x10000;
+ }
+ }
+ else
+ codepoint = (WCHAR) wParam;
+
+ window->win32.highSurrogate = 0;
+ _glfwInputChar(window, codepoint, getKeyMods(), uMsg != WM_SYSCHAR);
+ }
- if (uMsg == WM_UNICHAR && wParam == UNICODE_NOCHAR)
+ return 0;
+ }
+
+ case WM_UNICHAR:
+ {
+ if (wParam == UNICODE_NOCHAR)
{
// WM_UNICHAR is not sent by Windows, but is sent by some
// third-party input method engine
@@ -657,7 +677,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
return TRUE;
}
- _glfwInputChar(window, (unsigned int) wParam, getKeyMods(), plain);
+ _glfwInputChar(window, (unsigned int) wParam, getKeyMods(), GLFW_TRUE);
return 0;
}
@@ -944,6 +964,8 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
case WM_SIZE:
{
+ const int width = LOWORD(lParam);
+ const int height = HIWORD(lParam);
const GLFWbool iconified = wParam == SIZE_MINIMIZED;
const GLFWbool maximized = wParam == SIZE_MAXIMIZED ||
(window->win32.maximized &&
@@ -958,8 +980,14 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
if (window->win32.maximized != maximized)
_glfwInputWindowMaximize(window, maximized);
- _glfwInputFramebufferSize(window, LOWORD(lParam), HIWORD(lParam));
- _glfwInputWindowSize(window, LOWORD(lParam), HIWORD(lParam));
+ if (width != window->win32.width || height != window->win32.height)
+ {
+ window->win32.width = width;
+ window->win32.height = height;
+
+ _glfwInputFramebufferSize(window, width, height);
+ _glfwInputWindowSize(window, width, height);
+ }
if (window->monitor && window->win32.iconified != iconified)
{
@@ -1073,6 +1101,7 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
}
case WM_DWMCOMPOSITIONCHANGED:
+ case WM_DWMCOLORIZATIONCOLORCHANGED:
{
if (window->win32.transparent)
updateFramebufferTransparency(window);
@@ -1112,9 +1141,11 @@ static LRESULT CALLBACK windowProc(HWND hWnd, UINT uMsg,
const float xscale = HIWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
const float yscale = LOWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
- // Only apply the suggested size if the OS is new enough to have
- // sent a WM_GETDPISCALEDSIZE before this
- if (_glfwIsWindows10CreatorsUpdateOrGreaterWin32())
+ // Resize windowed mode windows that either permit rescaling or that
+ // need it to compensate for non-client area scaling
+ if (!window->monitor &&
+ (window->win32.scaleToMonitor ||
+ _glfwIsWindows10CreatorsUpdateOrGreaterWin32()))
{
RECT* suggested = (RECT*) lParam;
SetWindowPos(window->win32.handle, HWND_TOP,
@@ -1229,7 +1260,7 @@ static int createNativeWindow(_GLFWwindow* window,
NULL, // No parent window
NULL, // No window menu
GetModuleHandleW(NULL),
- NULL);
+ (LPVOID) wndconfig);
free(wideTitle);
@@ -1296,6 +1327,8 @@ static int createNativeWindow(_GLFWwindow* window,
window->win32.transparent = GLFW_TRUE;
}
+ _glfwPlatformGetWindowSize(window, &window->win32.width, &window->win32.height);
+
return GLFW_TRUE;
}
@@ -1797,7 +1830,8 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
{
- BOOL enabled;
+ BOOL composition, opaque;
+ DWORD color;
if (!window->win32.transparent)
return GLFW_FALSE;
@@ -1805,7 +1839,20 @@ int _glfwPlatformFramebufferTransparent(_GLFWwindow* window)
if (!IsWindowsVistaOrGreater())
return GLFW_FALSE;
- return SUCCEEDED(DwmIsCompositionEnabled(&enabled)) && enabled;
+ if (FAILED(DwmIsCompositionEnabled(&composition)) || !composition)
+ return GLFW_FALSE;
+
+ if (!IsWindows8OrGreater())
+ {
+ // HACK: Disable framebuffer transparency on Windows 7 when the
+ // colorization color is opaque, because otherwise the window
+ // contents is blended additively with the previous frame instead
+ // of replacing it
+ if (FAILED(DwmGetColorizationColor(&color, &opaque)) || opaque)
+ return GLFW_FALSE;
+ }
+
+ return GLFW_TRUE;
}
void _glfwPlatformSetWindowResizable(_GLFWwindow* window, GLFWbool enabled)
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/window.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/window.c
index 4b356b2167..44de03bbca 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/window.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/window.c
@@ -1099,3 +1099,4 @@ GLFWAPI void glfwPostEmptyEvent(void)
_GLFW_REQUIRE_INIT();
_glfwPlatformPostEmptyEvent();
}
+
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_init.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_init.c
index 97b53673f1..49e7cc522a 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_init.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_init.c
@@ -347,9 +347,9 @@ static void pointerHandleAxis(void* data,
axis == WL_POINTER_AXIS_VERTICAL_SCROLL);
if (axis == WL_POINTER_AXIS_HORIZONTAL_SCROLL)
- x = wl_fixed_to_double(value) * scrollFactor;
+ x = -wl_fixed_to_double(value) * scrollFactor;
else if (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)
- y = wl_fixed_to_double(value) * scrollFactor;
+ y = -wl_fixed_to_double(value) * scrollFactor;
_glfwInputScroll(window, x, y);
}
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_monitor.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_monitor.c
index 48b25b9ff6..a7b05c60da 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_monitor.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_monitor.c
@@ -88,6 +88,14 @@ static void outputHandleDone(void* data, struct wl_output* output)
{
struct _GLFWmonitor *monitor = data;
+ if (monitor->widthMM <= 0 || monitor->heightMM <= 0)
+ {
+ // If Wayland does not provide a physical size, assume the default 96 DPI
+ const GLFWvidmode* mode = &monitor->modes[monitor->wl.currentMode];
+ monitor->widthMM = (int) (mode->width * 25.4f / 96.f);
+ monitor->heightMM = (int) (mode->height * 25.4f / 96.f);
+ }
+
_glfwInputMonitor(monitor, GLFW_CONNECTED, _GLFW_INSERT_LAST);
}
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_platform.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_platform.h
index 0324e67ac9..41a7fdfa7a 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_platform.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_platform.h
@@ -57,7 +57,7 @@ typedef VkBool32 (APIENTRY *PFN_vkGetPhysicalDeviceWaylandPresentationSupportKHR
#include "osmesa_context.h"
#include "wayland-xdg-shell-client-protocol.h"
-#include "wayland-xdg-decoration-unstable-v1-client-protocol.h"
+#include "wayland-xdg-decoration-client-protocol.h"
#include "wayland-viewporter-client-protocol.h"
#include "wayland-relative-pointer-unstable-v1-client-protocol.h"
#include "wayland-pointer-constraints-unstable-v1-client-protocol.h"
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_window.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_window.c
index a90257189c..d10861cd84 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_window.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/wl_window.c
@@ -208,8 +208,8 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
if (fd < 0)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
- "Wayland: Creating a buffer file for %d B failed: %m",
- length);
+ "Wayland: Creating a buffer file for %d B failed: %s",
+ length, strerror(errno));
return NULL;
}
@@ -217,7 +217,7 @@ static struct wl_buffer* createShmBuffer(const GLFWimage* image)
if (data == MAP_FAILED)
{
_glfwInputError(GLFW_PLATFORM_ERROR,
- "Wayland: mmap failed: %m");
+ "Wayland: mmap failed: %s", strerror(errno));
close(fd);
return NULL;
}
@@ -312,10 +312,10 @@ static void createDecorations(_GLFWwindow* window)
static void destroyDecoration(_GLFWdecorationWayland* decoration)
{
- if (decoration->surface)
- wl_surface_destroy(decoration->surface);
if (decoration->subsurface)
wl_subsurface_destroy(decoration->subsurface);
+ if (decoration->surface)
+ wl_surface_destroy(decoration->surface);
if (decoration->viewport)
wp_viewport_destroy(decoration->viewport);
decoration->surface = NULL;
@@ -870,10 +870,17 @@ static void handleEvents(int timeout)
if (read_ret != 8)
return;
- for (i = 0; i < repeats; ++i)
- _glfwInputKey(_glfw.wl.keyboardFocus, _glfw.wl.keyboardLastKey,
- _glfw.wl.keyboardLastScancode, GLFW_REPEAT,
- _glfw.wl.xkb.modifiers);
+ if (_glfw.wl.keyboardFocus)
+ {
+ for (i = 0; i < repeats; ++i)
+ {
+ _glfwInputKey(_glfw.wl.keyboardFocus,
+ _glfw.wl.keyboardLastKey,
+ _glfw.wl.keyboardLastScancode,
+ GLFW_REPEAT,
+ _glfw.wl.xkb.modifiers);
+ }
+ }
}
if (fds[2].revents & POLLIN)
@@ -1111,8 +1118,10 @@ void _glfwPlatformGetFramebufferSize(_GLFWwindow* window,
int* width, int* height)
{
_glfwPlatformGetWindowSize(window, width, height);
- *width *= window->wl.scale;
- *height *= window->wl.scale;
+ if (width)
+ *width *= window->wl.scale;
+ if (height)
+ *height *= window->wl.scale;
}
void _glfwPlatformGetWindowFrameSize(_GLFWwindow* window,
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_init.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_init.c
index 2f220ec508..87b3eb781b 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_init.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_init.c
@@ -38,24 +38,15 @@
#include
-// Translate an X11 key code to a GLFW key code.
+// Translate the X11 KeySyms for a key to a GLFW key code
+// NOTE: This is only used as a fallback, in case the XKB method fails
+// It is layout-dependent and will fail partially on most non-US layouts
//
-static int translateKeyCode(int scancode)
+static int translateKeySyms(const KeySym* keysyms, int width)
{
- int keySym;
-
- // Valid key code range is [8,255], according to the Xlib manual
- if (scancode < 8 || scancode > 255)
- return GLFW_KEY_UNKNOWN;
-
- if (_glfw.x11.xkb.available)
+ if (width > 1)
{
- // Try secondary keysym, for numeric keypad keys
- // Note: This way we always force "NumLock = ON", which is intentional
- // since the returned key code should correspond to a physical
- // location.
- keySym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, _glfw.x11.xkb.group, 1);
- switch (keySym)
+ switch (keysyms[1])
{
case XK_KP_0: return GLFW_KEY_KP_0;
case XK_KP_1: return GLFW_KEY_KP_1;
@@ -73,22 +64,9 @@ static int translateKeyCode(int scancode)
case XK_KP_Enter: return GLFW_KEY_KP_ENTER;
default: break;
}
-
- // Now try primary keysym for function keys (non-printable keys)
- // These should not depend on the current keyboard layout
- keySym = XkbKeycodeToKeysym(_glfw.x11.display, scancode, _glfw.x11.xkb.group, 0);
- }
- else
- {
- int dummy;
- KeySym* keySyms;
-
- keySyms = XGetKeyboardMapping(_glfw.x11.display, scancode, 1, &dummy);
- keySym = keySyms[0];
- XFree(keySyms);
}
- switch (keySym)
+ switch (keysyms[0])
{
case XK_Escape: return GLFW_KEY_ESCAPE;
case XK_Tab: return GLFW_KEY_TAB;
@@ -232,7 +210,7 @@ static int translateKeyCode(int scancode)
//
static void createKeyTables(void)
{
- int scancode, key;
+ int scancode, scancodeMin, scancodeMax;
memset(_glfw.x11.keycodes, -1, sizeof(_glfw.x11.keycodes));
memset(_glfw.x11.scancodes, -1, sizeof(_glfw.x11.scancodes));
@@ -242,89 +220,217 @@ static void createKeyTables(void)
// Use XKB to determine physical key locations independently of the
// current keyboard layout
- char name[XkbKeyNameLength + 1];
XkbDescPtr desc = XkbGetMap(_glfw.x11.display, 0, XkbUseCoreKbd);
- XkbGetNames(_glfw.x11.display, XkbKeyNamesMask, desc);
+ XkbGetNames(_glfw.x11.display, XkbKeyNamesMask | XkbKeyAliasesMask, desc);
+
+ scancodeMin = desc->min_key_code;
+ scancodeMax = desc->max_key_code;
+
+ const struct
+ {
+ int key;
+ char* name;
+ } keymap[] =
+ {
+ { GLFW_KEY_GRAVE_ACCENT, "TLDE" },
+ { GLFW_KEY_1, "AE01" },
+ { GLFW_KEY_2, "AE02" },
+ { GLFW_KEY_3, "AE03" },
+ { GLFW_KEY_4, "AE04" },
+ { GLFW_KEY_5, "AE05" },
+ { GLFW_KEY_6, "AE06" },
+ { GLFW_KEY_7, "AE07" },
+ { GLFW_KEY_8, "AE08" },
+ { GLFW_KEY_9, "AE09" },
+ { GLFW_KEY_0, "AE10" },
+ { GLFW_KEY_MINUS, "AE11" },
+ { GLFW_KEY_EQUAL, "AE12" },
+ { GLFW_KEY_Q, "AD01" },
+ { GLFW_KEY_W, "AD02" },
+ { GLFW_KEY_E, "AD03" },
+ { GLFW_KEY_R, "AD04" },
+ { GLFW_KEY_T, "AD05" },
+ { GLFW_KEY_Y, "AD06" },
+ { GLFW_KEY_U, "AD07" },
+ { GLFW_KEY_I, "AD08" },
+ { GLFW_KEY_O, "AD09" },
+ { GLFW_KEY_P, "AD10" },
+ { GLFW_KEY_LEFT_BRACKET, "AD11" },
+ { GLFW_KEY_RIGHT_BRACKET, "AD12" },
+ { GLFW_KEY_A, "AC01" },
+ { GLFW_KEY_S, "AC02" },
+ { GLFW_KEY_D, "AC03" },
+ { GLFW_KEY_F, "AC04" },
+ { GLFW_KEY_G, "AC05" },
+ { GLFW_KEY_H, "AC06" },
+ { GLFW_KEY_J, "AC07" },
+ { GLFW_KEY_K, "AC08" },
+ { GLFW_KEY_L, "AC09" },
+ { GLFW_KEY_SEMICOLON, "AC10" },
+ { GLFW_KEY_APOSTROPHE, "AC11" },
+ { GLFW_KEY_Z, "AB01" },
+ { GLFW_KEY_X, "AB02" },
+ { GLFW_KEY_C, "AB03" },
+ { GLFW_KEY_V, "AB04" },
+ { GLFW_KEY_B, "AB05" },
+ { GLFW_KEY_N, "AB06" },
+ { GLFW_KEY_M, "AB07" },
+ { GLFW_KEY_COMMA, "AB08" },
+ { GLFW_KEY_PERIOD, "AB09" },
+ { GLFW_KEY_SLASH, "AB10" },
+ { GLFW_KEY_BACKSLASH, "BKSL" },
+ { GLFW_KEY_WORLD_1, "LSGT" },
+ { GLFW_KEY_SPACE, "SPCE" },
+ { GLFW_KEY_ESCAPE, "ESC" },
+ { GLFW_KEY_ENTER, "RTRN" },
+ { GLFW_KEY_TAB, "TAB" },
+ { GLFW_KEY_BACKSPACE, "BKSP" },
+ { GLFW_KEY_INSERT, "INS" },
+ { GLFW_KEY_DELETE, "DELE" },
+ { GLFW_KEY_RIGHT, "RGHT" },
+ { GLFW_KEY_LEFT, "LEFT" },
+ { GLFW_KEY_DOWN, "DOWN" },
+ { GLFW_KEY_UP, "UP" },
+ { GLFW_KEY_PAGE_UP, "PGUP" },
+ { GLFW_KEY_PAGE_DOWN, "PGDN" },
+ { GLFW_KEY_HOME, "HOME" },
+ { GLFW_KEY_END, "END" },
+ { GLFW_KEY_CAPS_LOCK, "CAPS" },
+ { GLFW_KEY_SCROLL_LOCK, "SCLK" },
+ { GLFW_KEY_NUM_LOCK, "NMLK" },
+ { GLFW_KEY_PRINT_SCREEN, "PRSC" },
+ { GLFW_KEY_PAUSE, "PAUS" },
+ { GLFW_KEY_F1, "FK01" },
+ { GLFW_KEY_F2, "FK02" },
+ { GLFW_KEY_F3, "FK03" },
+ { GLFW_KEY_F4, "FK04" },
+ { GLFW_KEY_F5, "FK05" },
+ { GLFW_KEY_F6, "FK06" },
+ { GLFW_KEY_F7, "FK07" },
+ { GLFW_KEY_F8, "FK08" },
+ { GLFW_KEY_F9, "FK09" },
+ { GLFW_KEY_F10, "FK10" },
+ { GLFW_KEY_F11, "FK11" },
+ { GLFW_KEY_F12, "FK12" },
+ { GLFW_KEY_F13, "FK13" },
+ { GLFW_KEY_F14, "FK14" },
+ { GLFW_KEY_F15, "FK15" },
+ { GLFW_KEY_F16, "FK16" },
+ { GLFW_KEY_F17, "FK17" },
+ { GLFW_KEY_F18, "FK18" },
+ { GLFW_KEY_F19, "FK19" },
+ { GLFW_KEY_F20, "FK20" },
+ { GLFW_KEY_F21, "FK21" },
+ { GLFW_KEY_F22, "FK22" },
+ { GLFW_KEY_F23, "FK23" },
+ { GLFW_KEY_F24, "FK24" },
+ { GLFW_KEY_F25, "FK25" },
+ { GLFW_KEY_KP_0, "KP0" },
+ { GLFW_KEY_KP_1, "KP1" },
+ { GLFW_KEY_KP_2, "KP2" },
+ { GLFW_KEY_KP_3, "KP3" },
+ { GLFW_KEY_KP_4, "KP4" },
+ { GLFW_KEY_KP_5, "KP5" },
+ { GLFW_KEY_KP_6, "KP6" },
+ { GLFW_KEY_KP_7, "KP7" },
+ { GLFW_KEY_KP_8, "KP8" },
+ { GLFW_KEY_KP_9, "KP9" },
+ { GLFW_KEY_KP_DECIMAL, "KPDL" },
+ { GLFW_KEY_KP_DIVIDE, "KPDV" },
+ { GLFW_KEY_KP_MULTIPLY, "KPMU" },
+ { GLFW_KEY_KP_SUBTRACT, "KPSU" },
+ { GLFW_KEY_KP_ADD, "KPAD" },
+ { GLFW_KEY_KP_ENTER, "KPEN" },
+ { GLFW_KEY_KP_EQUAL, "KPEQ" },
+ { GLFW_KEY_LEFT_SHIFT, "LFSH" },
+ { GLFW_KEY_LEFT_CONTROL, "LCTL" },
+ { GLFW_KEY_LEFT_ALT, "LALT" },
+ { GLFW_KEY_LEFT_SUPER, "LWIN" },
+ { GLFW_KEY_RIGHT_SHIFT, "RTSH" },
+ { GLFW_KEY_RIGHT_CONTROL, "RCTL" },
+ { GLFW_KEY_RIGHT_ALT, "RALT" },
+ { GLFW_KEY_RIGHT_ALT, "LVL3" },
+ { GLFW_KEY_RIGHT_ALT, "MDSW" },
+ { GLFW_KEY_RIGHT_SUPER, "RWIN" },
+ { GLFW_KEY_MENU, "MENU" }
+ };
// Find the X11 key code -> GLFW key code mapping
- for (scancode = desc->min_key_code; scancode <= desc->max_key_code; scancode++)
+ for (scancode = scancodeMin; scancode <= scancodeMax; scancode++)
{
- memcpy(name, desc->names->keys[scancode].name, XkbKeyNameLength);
- name[XkbKeyNameLength] = '\0';
-
- // Map the key name to a GLFW key code. Note: We only map printable
- // keys here, and we use the US keyboard layout. The rest of the
- // keys (function keys) are mapped using traditional KeySym
- // translations.
- if (strcmp(name, "TLDE") == 0) key = GLFW_KEY_GRAVE_ACCENT;
- else if (strcmp(name, "AE01") == 0) key = GLFW_KEY_1;
- else if (strcmp(name, "AE02") == 0) key = GLFW_KEY_2;
- else if (strcmp(name, "AE03") == 0) key = GLFW_KEY_3;
- else if (strcmp(name, "AE04") == 0) key = GLFW_KEY_4;
- else if (strcmp(name, "AE05") == 0) key = GLFW_KEY_5;
- else if (strcmp(name, "AE06") == 0) key = GLFW_KEY_6;
- else if (strcmp(name, "AE07") == 0) key = GLFW_KEY_7;
- else if (strcmp(name, "AE08") == 0) key = GLFW_KEY_8;
- else if (strcmp(name, "AE09") == 0) key = GLFW_KEY_9;
- else if (strcmp(name, "AE10") == 0) key = GLFW_KEY_0;
- else if (strcmp(name, "AE11") == 0) key = GLFW_KEY_MINUS;
- else if (strcmp(name, "AE12") == 0) key = GLFW_KEY_EQUAL;
- else if (strcmp(name, "AD01") == 0) key = GLFW_KEY_Q;
- else if (strcmp(name, "AD02") == 0) key = GLFW_KEY_W;
- else if (strcmp(name, "AD03") == 0) key = GLFW_KEY_E;
- else if (strcmp(name, "AD04") == 0) key = GLFW_KEY_R;
- else if (strcmp(name, "AD05") == 0) key = GLFW_KEY_T;
- else if (strcmp(name, "AD06") == 0) key = GLFW_KEY_Y;
- else if (strcmp(name, "AD07") == 0) key = GLFW_KEY_U;
- else if (strcmp(name, "AD08") == 0) key = GLFW_KEY_I;
- else if (strcmp(name, "AD09") == 0) key = GLFW_KEY_O;
- else if (strcmp(name, "AD10") == 0) key = GLFW_KEY_P;
- else if (strcmp(name, "AD11") == 0) key = GLFW_KEY_LEFT_BRACKET;
- else if (strcmp(name, "AD12") == 0) key = GLFW_KEY_RIGHT_BRACKET;
- else if (strcmp(name, "AC01") == 0) key = GLFW_KEY_A;
- else if (strcmp(name, "AC02") == 0) key = GLFW_KEY_S;
- else if (strcmp(name, "AC03") == 0) key = GLFW_KEY_D;
- else if (strcmp(name, "AC04") == 0) key = GLFW_KEY_F;
- else if (strcmp(name, "AC05") == 0) key = GLFW_KEY_G;
- else if (strcmp(name, "AC06") == 0) key = GLFW_KEY_H;
- else if (strcmp(name, "AC07") == 0) key = GLFW_KEY_J;
- else if (strcmp(name, "AC08") == 0) key = GLFW_KEY_K;
- else if (strcmp(name, "AC09") == 0) key = GLFW_KEY_L;
- else if (strcmp(name, "AC10") == 0) key = GLFW_KEY_SEMICOLON;
- else if (strcmp(name, "AC11") == 0) key = GLFW_KEY_APOSTROPHE;
- else if (strcmp(name, "AB01") == 0) key = GLFW_KEY_Z;
- else if (strcmp(name, "AB02") == 0) key = GLFW_KEY_X;
- else if (strcmp(name, "AB03") == 0) key = GLFW_KEY_C;
- else if (strcmp(name, "AB04") == 0) key = GLFW_KEY_V;
- else if (strcmp(name, "AB05") == 0) key = GLFW_KEY_B;
- else if (strcmp(name, "AB06") == 0) key = GLFW_KEY_N;
- else if (strcmp(name, "AB07") == 0) key = GLFW_KEY_M;
- else if (strcmp(name, "AB08") == 0) key = GLFW_KEY_COMMA;
- else if (strcmp(name, "AB09") == 0) key = GLFW_KEY_PERIOD;
- else if (strcmp(name, "AB10") == 0) key = GLFW_KEY_SLASH;
- else if (strcmp(name, "BKSL") == 0) key = GLFW_KEY_BACKSLASH;
- else if (strcmp(name, "LSGT") == 0) key = GLFW_KEY_WORLD_1;
- else key = GLFW_KEY_UNKNOWN;
-
- if ((scancode >= 0) && (scancode < 256))
- _glfw.x11.keycodes[scancode] = key;
+ int key = GLFW_KEY_UNKNOWN;
+
+ // Map the key name to a GLFW key code. Note: We use the US
+ // keyboard layout. Because function keys aren't mapped correctly
+ // when using traditional KeySym translations, they are mapped
+ // here instead.
+ for (int i = 0; i < sizeof(keymap) / sizeof(keymap[0]); i++)
+ {
+ if (strncmp(desc->names->keys[scancode].name,
+ keymap[i].name,
+ XkbKeyNameLength) == 0)
+ {
+ key = keymap[i].key;
+ break;
+ }
+ }
+
+ // Fall back to key aliases in case the key name did not match
+ for (int i = 0; i < desc->names->num_key_aliases; i++)
+ {
+ if (key != GLFW_KEY_UNKNOWN)
+ break;
+
+ if (strncmp(desc->names->key_aliases[i].real,
+ desc->names->keys[scancode].name,
+ XkbKeyNameLength) != 0)
+ {
+ continue;
+ }
+
+ for (int j = 0; j < sizeof(keymap) / sizeof(keymap[0]); j++)
+ {
+ if (strncmp(desc->names->key_aliases[i].alias,
+ keymap[j].name,
+ XkbKeyNameLength) == 0)
+ {
+ key = keymap[j].key;
+ break;
+ }
+ }
+ }
+
+ _glfw.x11.keycodes[scancode] = key;
}
XkbFreeNames(desc, XkbKeyNamesMask, True);
XkbFreeKeyboard(desc, 0, True);
}
+ else
+ XDisplayKeycodes(_glfw.x11.display, &scancodeMin, &scancodeMax);
+
+ int width;
+ KeySym* keysyms = XGetKeyboardMapping(_glfw.x11.display,
+ scancodeMin,
+ scancodeMax - scancodeMin + 1,
+ &width);
- for (scancode = 0; scancode < 256; scancode++)
+ for (scancode = scancodeMin; scancode <= scancodeMax; scancode++)
{
// Translate the un-translated key codes using traditional X11 KeySym
// lookups
if (_glfw.x11.keycodes[scancode] < 0)
- _glfw.x11.keycodes[scancode] = translateKeyCode(scancode);
+ {
+ const size_t base = (scancode - scancodeMin) * width;
+ _glfw.x11.keycodes[scancode] = translateKeySyms(&keysyms[base], width);
+ }
// Store the reverse translation for faster key name lookup
if (_glfw.x11.keycodes[scancode] > 0)
_glfw.x11.scancodes[_glfw.x11.keycodes[scancode]] = scancode;
}
+
+ XFree(keysyms);
}
// Check whether the IM has a usable style
@@ -352,13 +458,13 @@ static GLFWbool hasUsableInputMethodStyle(void)
// Check whether the specified atom is supported
//
-static Atom getSupportedAtom(Atom* supportedAtoms,
- unsigned long atomCount,
- const char* atomName)
+static Atom getAtomIfSupported(Atom* supportedAtoms,
+ unsigned long atomCount,
+ const char* atomName)
{
const Atom atom = XInternAtom(_glfw.x11.display, atomName, False);
- for (unsigned int i = 0; i < atomCount; i++)
+ for (unsigned long i = 0; i < atomCount; i++)
{
if (supportedAtoms[i] == atom)
return atom;
@@ -426,33 +532,33 @@ static void detectEWMH(void)
// See which of the atoms we support that are supported by the WM
_glfw.x11.NET_WM_STATE =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE");
_glfw.x11.NET_WM_STATE_ABOVE =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_ABOVE");
_glfw.x11.NET_WM_STATE_FULLSCREEN =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_FULLSCREEN");
_glfw.x11.NET_WM_STATE_MAXIMIZED_VERT =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_VERT");
_glfw.x11.NET_WM_STATE_MAXIMIZED_HORZ =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_MAXIMIZED_HORZ");
_glfw.x11.NET_WM_STATE_DEMANDS_ATTENTION =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_STATE_DEMANDS_ATTENTION");
_glfw.x11.NET_WM_FULLSCREEN_MONITORS =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_FULLSCREEN_MONITORS");
_glfw.x11.NET_WM_WINDOW_TYPE =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE");
_glfw.x11.NET_WM_WINDOW_TYPE_NORMAL =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WM_WINDOW_TYPE_NORMAL");
_glfw.x11.NET_WORKAREA =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_WORKAREA");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_WORKAREA");
_glfw.x11.NET_CURRENT_DESKTOP =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_CURRENT_DESKTOP");
_glfw.x11.NET_ACTIVE_WINDOW =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_ACTIVE_WINDOW");
_glfw.x11.NET_FRAME_EXTENTS =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_FRAME_EXTENTS");
_glfw.x11.NET_REQUEST_FRAME_EXTENTS =
- getSupportedAtom(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
+ getAtomIfSupported(supportedAtoms, atomCount, "_NET_REQUEST_FRAME_EXTENTS");
if (supportedAtoms)
XFree(supportedAtoms);
@@ -660,13 +766,12 @@ static GLFWbool initExtensions(void)
_glfw.x11.xkb.detectable = GLFW_TRUE;
}
- _glfw.x11.xkb.group = 0;
XkbStateRec state;
if (XkbGetState(_glfw.x11.display, XkbUseCoreKbd, &state) == Success)
- {
- XkbSelectEventDetails(_glfw.x11.display, XkbUseCoreKbd, XkbStateNotify, XkbAllStateComponentsMask, XkbGroupStateMask);
_glfw.x11.xkb.group = (unsigned int)state.group;
- }
+
+ XkbSelectEventDetails(_glfw.x11.display, XkbUseCoreKbd, XkbStateNotify,
+ XkbGroupStateMask, XkbGroupStateMask);
}
#if defined(__CYGWIN__)
@@ -851,6 +956,9 @@ static Window createHelperWindow(void)
//
static int errorHandler(Display *display, XErrorEvent* event)
{
+ if (_glfw.x11.display != display)
+ return 0;
+
_glfw.x11.errorCode = event->error_code;
return 0;
}
@@ -931,15 +1039,12 @@ Cursor _glfwCreateCursorX11(const GLFWimage* image, int xhot, int yhot)
int _glfwPlatformInit(void)
{
-#if !defined(X_HAVE_UTF8_STRING)
- // HACK: If the current locale is "C" and the Xlib UTF-8 functions are
- // unavailable, apply the environment's locale in the hope that it's
- // both available and not "C"
- // This is done because the "C" locale breaks wide character input,
- // which is what we fall back on when UTF-8 support is missing
+ // HACK: If the application has left the locale as "C" then both wide
+ // character text input and explicit UTF-8 input via XIM will break
+ // This sets the CTYPE part of the current locale from the environment
+ // in the hope that it is set to something more sane than "C"
if (strcmp(setlocale(LC_CTYPE, NULL), "C") == 0)
setlocale(LC_CTYPE, "");
-#endif
XInitThreads();
XrmInitialize();
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_monitor.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_monitor.c
index 1f804aeb40..fb3a67bace 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_monitor.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_monitor.c
@@ -164,7 +164,7 @@ void _glfwPollMonitorsX11(void)
if (widthMM <= 0 || heightMM <= 0)
{
// HACK: If RandR does not provide a physical size, assume the
- // X11 default 96 DPI and calcuate from the CRTC viewport
+ // X11 default 96 DPI and calculate from the CRTC viewport
// NOTE: These members are affected by rotation, unlike the mode
// info and output info members
widthMM = (int) (ci->width * 25.4f / 96.f);
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_platform.h b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_platform.h
index 7377b2c082..4873bd7483 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_platform.h
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_platform.h
@@ -199,8 +199,9 @@ typedef struct _GLFWwindowX11
// The last position the cursor was warped to by GLFW
int warpCursorPosX, warpCursorPosY;
- // The time of the last KeyPress event
- Time lastKeyTime;
+ // The time of the last KeyPress event per keycode, for discarding
+ // duplicate key events generated for some keys by ibus
+ Time keyPressTimes[256];
} _GLFWwindowX11;
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_window.c b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_window.c
index 271e108094..90c4d9bed6 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_window.c
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw/src/x11_window.c
@@ -1195,6 +1195,8 @@ static void processEvent(XEvent *event)
{
_glfw.x11.xkb.group = ((XkbEvent*) event)->state.group;
}
+
+ return;
}
}
@@ -1273,16 +1275,20 @@ static void processEvent(XEvent *event)
if (window->x11.ic)
{
- // HACK: Ignore duplicate key press events generated by ibus
- // These have the same timestamp as the original event
- // Corresponding release events are filtered out
- // implicitly by the GLFW key repeat logic
- if (window->x11.lastKeyTime < event->xkey.time)
+ // HACK: Do not report the key press events duplicated by XIM
+ // Duplicate key releases are filtered out implicitly by
+ // the GLFW key repeat logic in _glfwInputKey
+ // A timestamp per key is used to handle simultaneous keys
+ // NOTE: Always allow the first event for each key through
+ // (the server never sends a timestamp of zero)
+ // NOTE: Timestamp difference is compared to handle wrap-around
+ Time diff = event->xkey.time - window->x11.keyPressTimes[keycode];
+ if (diff == event->xkey.time || (diff > 0 && diff < (1 << 31)))
{
if (keycode)
_glfwInputKey(window, key, keycode, GLFW_PRESS, mods);
- window->x11.lastKeyTime = event->xkey.time;
+ window->x11.keyPressTimes[keycode] = event->xkey.time;
}
if (!filtered)
@@ -1557,6 +1563,8 @@ static void processEvent(XEvent *event)
// the position into root (screen) coordinates
if (!event->xany.send_event && window->x11.parent != _glfw.x11.root)
{
+ _glfwGrabErrorHandlerX11();
+
Window dummy;
XTranslateCoordinates(_glfw.x11.display,
window->x11.parent,
@@ -1564,6 +1572,10 @@ static void processEvent(XEvent *event)
xpos, ypos,
&xpos, &ypos,
&dummy);
+
+ _glfwReleaseErrorHandlerX11();
+ if (_glfw.x11.errorCode == BadWindow)
+ return;
}
if (xpos != window->x11.xpos || ypos != window->x11.ypos)
@@ -1971,7 +1983,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
const _GLFWctxconfig* ctxconfig,
const _GLFWfbconfig* fbconfig)
{
- Visual* visual;
+ Visual* visual = NULL;
int depth;
if (ctxconfig->client != GLFW_NO_API)
@@ -1997,8 +2009,7 @@ int _glfwPlatformCreateWindow(_GLFWwindow* window,
}
}
- if (ctxconfig->client == GLFW_NO_API ||
- ctxconfig->source == GLFW_OSMESA_CONTEXT_API)
+ if (!visual)
{
visual = DefaultVisual(_glfw.x11.display, _glfw.x11.screen);
depth = DefaultDepth(_glfw.x11.display, _glfw.x11.screen);
@@ -2580,13 +2591,19 @@ int _glfwPlatformWindowHovered(_GLFWwindow* window)
int rootX, rootY, childX, childY;
unsigned int mask;
- if (!XQueryPointer(_glfw.x11.display, w,
- &root, &w, &rootX, &rootY, &childX, &childY, &mask))
- {
- return GLFW_FALSE;
- }
+ _glfwGrabErrorHandlerX11();
+
+ const Bool result = XQueryPointer(_glfw.x11.display, w,
+ &root, &w, &rootX, &rootY,
+ &childX, &childY, &mask);
- if (w == window->x11.handle)
+ _glfwReleaseErrorHandlerX11();
+
+ if (_glfw.x11.errorCode == BadWindow)
+ w = _glfw.x11.root;
+ else if (!result)
+ return GLFW_FALSE;
+ else if (w == window->x11.handle)
return GLFW_TRUE;
}
@@ -2950,8 +2967,9 @@ void _glfwPlatformSetCursor(_GLFWwindow* window, _GLFWcursor* cursor)
void _glfwPlatformSetClipboardString(const char* string)
{
+ char* copy = _glfw_strdup(string);
free(_glfw.x11.clipboardString);
- _glfw.x11.clipboardString = _glfw_strdup(string);
+ _glfw.x11.clipboardString = copy;
XSetSelectionOwner(_glfw.x11.display,
_glfw.x11.CLIPBOARD,
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
new file mode 100644
index 0000000000..22ff0280b2
--- /dev/null
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/glfw_tree_rebuild.go
@@ -0,0 +1,10 @@
+package glfw
+
+//go:generate ../../scripts/glfw_tree_rebuild.sh
+
+// upstreamTreeSHA is a recursive hash of the full contents of the upstream
+// glfw, as generated by git (doesn't need to be committed) when you run `go
+// 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"
diff --git a/vendor/github.com/go-gl/glfw/v3.3/glfw/util.go b/vendor/github.com/go-gl/glfw/v3.3/glfw/util.go
index 0d0a2c9baf..2bf56ed2d6 100644
--- a/vendor/github.com/go-gl/glfw/v3.3/glfw/util.go
+++ b/vendor/github.com/go-gl/glfw/v3.3/glfw/util.go
@@ -5,11 +5,6 @@ package glfw
//#include "glfw/include/GLFW/glfw3.h"
import "C"
-import (
- "reflect"
- "unsafe"
-)
-
func glfwbool(b C.int) bool {
if b == C.int(True) {
return true
@@ -19,20 +14,10 @@ func glfwbool(b C.int) bool {
func bytes(origin []byte) (pointer *uint8, free func()) {
n := len(origin)
-
if n == 0 {
return nil, func() {}
}
- data := C.malloc(C.size_t(n))
-
- dataSlice := *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{
- Data: uintptr(data),
- Len: n,
- Cap: n,
- }))
-
- copy(dataSlice, origin)
-
- return &dataSlice[0], func() { C.free(data) }
+ ptr := C.CBytes(origin)
+ return (*uint8)(ptr), func() { C.free(ptr) }
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 958b2782f8..96f646bd03 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.2
+# github.com/fyne-io/mobile v0.1.3-0.20210318200029-09e9c4e13a8f
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-20200625191551-73d3c3675aa3 => github.com/fyne-io/glfw/v3.3/glfw v0.0.0-20201123143003-f2279069162d
+# github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210311203641-62640a716d48
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
diff --git a/widget/entry.go b/widget/entry.go
index 0f28e899a8..9b838a9d24 100644
--- a/widget/entry.go
+++ b/widget/entry.go
@@ -237,10 +237,19 @@ func (e *Entry) DoubleTapped(p *fyne.PointEvent) {
})
}
-// DragEnd is called at end of a drag event. It does nothing.
+// DragEnd is called at end of a drag event.
//
// Implements: fyne.Draggable
func (e *Entry) DragEnd() {
+ e.propertyLock.Lock()
+ if e.CursorColumn == e.selectColumn && e.CursorRow == e.selectRow {
+ e.selecting = false
+ }
+ shouldRefresh := !e.selecting
+ e.propertyLock.Unlock()
+ if shouldRefresh {
+ e.Refresh()
+ }
}
// Dragged is called when the pointer moves while a button is held down.
@@ -283,9 +292,6 @@ func (e *Entry) ExtendBaseWidget(wid fyne.Widget) {
//
// Implements: fyne.Focusable
func (e *Entry) FocusGained() {
- if e.Disabled() {
- return
- }
e.setFieldsAndRefresh(func() {
e.focused = true
})
@@ -297,6 +303,7 @@ func (e *Entry) FocusGained() {
func (e *Entry) FocusLost() {
e.setFieldsAndRefresh(func() {
e.focused = false
+ e.selectKeyDown = false
})
}
@@ -329,6 +336,9 @@ func (e *Entry) Keyboard() mobile.KeyboardType {
//
// Implements: desktop.Keyable
func (e *Entry) KeyDown(key *fyne.KeyEvent) {
+ if e.Disabled() {
+ return
+ }
// For keyboard cursor controlled selection we now need to store shift key state and selection "start"
// Note: selection start is where the highlight started (if the user moves the selection up or left then
// the selectRow/Column will not match SelectionStart)
@@ -345,6 +355,9 @@ func (e *Entry) KeyDown(key *fyne.KeyEvent) {
//
// Implements: desktop.Keyable
func (e *Entry) KeyUp(key *fyne.KeyEvent) {
+ if e.Disabled() {
+ return
+ }
// Handle shift release for keyboard selection
// Note: if shift is released then the user may repress it without moving to adjust their old selection
if key.Name == desktop.KeyShiftLeft || key.Name == desktop.KeyShiftRight {
@@ -821,21 +834,10 @@ func (e *Entry) pasteFromClipboard(clipboard fyne.Clipboard) {
}
provider := e.textProvider()
runes := []rune(text)
- provider.insertAt(e.cursorTextPos(), runes)
+ pos := e.cursorTextPos()
+ provider.insertAt(pos, runes)
+ e.CursorRow, e.CursorColumn = e.rowColFromTextPos(pos + len(runes))
- newlines := strings.Count(text, "\n")
- if newlines == 0 {
- e.CursorColumn += len(runes)
- } else {
- e.CursorRow += newlines
- lastNewlineIndex := 0
- for i, r := range runes {
- if r == '\n' {
- lastNewlineIndex = i
- }
- }
- e.CursorColumn = len(runes) - lastNewlineIndex - 1
- }
e.updateText(provider.String())
e.Refresh()
}
@@ -876,14 +878,15 @@ func (e *Entry) registerShortcut() {
func (e *Entry) rowColFromTextPos(pos int) (row int, col int) {
provider := e.textProvider()
canWrap := e.Wrapping == fyne.TextWrapBreak || e.Wrapping == fyne.TextWrapWord
- for i := 0; i < provider.rows(); i++ {
+ totalRows := provider.rows()
+ for i := 0; i < totalRows; i++ {
b := provider.rowBoundary(i)
if b[0] <= pos {
if b[1] < pos {
row++
}
col = pos - b[0]
- if canWrap && b[0] == pos && col == 0 && pos != 0 {
+ if canWrap && b[0] == pos && col == 0 && pos != 0 && row < (totalRows-1) {
row++
}
} else {
@@ -1185,7 +1188,7 @@ func (r *entryRenderer) Refresh() {
provider := r.entry.textProvider()
text := r.entry.Text
content := r.entry.content
- focused := r.entry.focused
+ focusedAppearance := r.entry.focused && !r.entry.disabled
size := r.entry.size
wrapping := r.entry.Wrapping
r.entry.propertyLock.RUnlock()
@@ -1225,7 +1228,7 @@ func (r *entryRenderer) Refresh() {
}
r.box.FillColor = theme.InputBackgroundColor()
- if focused {
+ if focusedAppearance {
r.line.FillColor = theme.PrimaryColor()
} else {
if r.entry.Disabled() {
@@ -1249,7 +1252,7 @@ func (r *entryRenderer) Refresh() {
}
if r.entry.Validator != nil {
- if !r.entry.focused && r.entry.Text != "" && r.entry.validationError != nil {
+ if !r.entry.focused && !r.entry.Disabled() && r.entry.Text != "" && r.entry.validationError != nil {
r.line.FillColor = theme.ErrorColor()
}
r.ensureValidationSetup()
@@ -1303,7 +1306,7 @@ func (e *entryContent) CreateRenderer() fyne.WidgetRenderer {
return r
}
-// DragEnd is called at end of a drag event. It does nothing.
+// DragEnd is called at end of a drag event.
//
// Implements: fyne.Draggable
func (e *entryContent) DragEnd() {
@@ -1369,7 +1372,7 @@ func (r *entryContentRenderer) Refresh() {
provider := r.content.entry.textProvider()
placeholder := r.content.entry.placeholderProvider()
content := r.content.entry.Text
- focused := r.content.entry.focused
+ focusedAppearance := r.content.entry.focused && !r.content.entry.disabled
selections := r.selection
r.updateScrollDirections()
r.content.entry.propertyLock.RUnlock()
@@ -1384,7 +1387,7 @@ func (r *entryContentRenderer) Refresh() {
placeholder.Hide()
}
- if focused {
+ if focusedAppearance {
r.cursor.Show()
r.content.entry.cursorAnim.start()
} else {
@@ -1394,7 +1397,7 @@ func (r *entryContentRenderer) Refresh() {
r.moveCursor()
for _, selection := range selections {
- selection.(*canvas.Rectangle).Hidden = !r.content.entry.focused && !r.content.entry.disabled
+ selection.(*canvas.Rectangle).Hidden = !r.content.entry.focused
selection.(*canvas.Rectangle).FillColor = theme.PrimaryColor()
}
@@ -1418,7 +1421,7 @@ func (r *entryContentRenderer) buildSelection() {
}
r.content.entry.propertyLock.RUnlock()
- if selectRow == -1 {
+ if selectRow == -1 || (cursorRow == selectRow && cursorCol == selectCol) {
r.selection = r.selection[:0]
return
diff --git a/widget/entry_internal_test.go b/widget/entry_internal_test.go
index ec307c7346..13992c9521 100644
--- a/widget/entry_internal_test.go
+++ b/widget/entry_internal_test.go
@@ -93,6 +93,47 @@ func TestEntry_DragSelect(t *testing.T) {
assert.Equal(t, "r the laz", entry.SelectedText())
}
+func TestEntry_DragSelectEmpty(t *testing.T) {
+ entry := NewEntry()
+ entry.SetText("Testing")
+
+ ev1 := getClickPosition("T", 0)
+ ev2 := getClickPosition("Testing", 0)
+
+ // Test empty selection - drag from 'e' to 'e' (empty)
+ de := &fyne.DragEvent{PointEvent: *ev1, Dragged: fyne.NewDelta(1, 0)}
+ entry.Dragged(de)
+ de = &fyne.DragEvent{PointEvent: *ev1, Dragged: fyne.NewDelta(1, 0)}
+ entry.Dragged(de)
+
+ entry.propertyLock.RLock()
+ assert.True(t, entry.selecting)
+ entry.propertyLock.RUnlock()
+
+ entry.DragEnd()
+ assert.Equal(t, "", entry.SelectedText())
+ entry.propertyLock.RLock()
+ assert.False(t, entry.selecting)
+ entry.propertyLock.RUnlock()
+
+ // Test non-empty selection - drag from 'T' to 'g' (empty)
+ ev1 = getClickPosition("", 0)
+ de = &fyne.DragEvent{PointEvent: *ev1, Dragged: fyne.NewDelta(1, 0)}
+ entry.Dragged(de)
+ de = &fyne.DragEvent{PointEvent: *ev2, Dragged: fyne.NewDelta(1, 0)}
+ entry.Dragged(de)
+
+ entry.propertyLock.RLock()
+ assert.True(t, entry.selecting)
+ entry.propertyLock.RUnlock()
+
+ entry.DragEnd()
+ assert.Equal(t, "Testing", entry.SelectedText())
+ entry.propertyLock.RLock()
+ assert.True(t, entry.selecting)
+ entry.propertyLock.RUnlock()
+}
+
func TestEntry_DragSelectWithScroll(t *testing.T) {
entry := NewEntry()
entry.SetText("The quick brown fox jumped over and over the lazy dog.")
@@ -255,6 +296,35 @@ func TestEntry_PasteFromClipboard(t *testing.T) {
assert.Equal(t, entry.Text, testContent)
}
+func TestEntry_PasteFromClipboard_MultilineWrapping(t *testing.T) {
+ entry := NewMultiLineEntry()
+ entry.Wrapping = fyne.TextWrapWord
+
+ w := test.NewApp().NewWindow("")
+ w.SetContent(entry)
+ w.Resize(fyne.NewSize(100, 64))
+
+ test.Type(entry, "T")
+ assert.Equal(t, 0, entry.CursorRow)
+ assert.Equal(t, 1, entry.CursorColumn)
+
+ clipboard := fyne.CurrentApp().Driver().AllWindows()[0].Clipboard()
+ clipboard.SetContent("esting entry")
+
+ entry.pasteFromClipboard(clipboard)
+
+ assert.Equal(t, entry.Text, "Testing entry")
+ assert.Equal(t, 1, entry.CursorRow)
+ assert.Equal(t, 5, entry.CursorColumn)
+
+ clipboard.SetContent(" paste\ncontent")
+ entry.pasteFromClipboard(clipboard)
+
+ assert.Equal(t, "Testing entry paste\ncontent", entry.Text)
+ assert.Equal(t, 2, entry.CursorRow)
+ assert.Equal(t, 7, entry.CursorColumn)
+}
+
func TestEntry_Tab(t *testing.T) {
e := NewEntry()
e.SetText("a\n\tb\nc")
@@ -290,6 +360,17 @@ func TestEntry_TabSelection(t *testing.T) {
test.AssertImageMatches(t, "entry/tab-select.png", w.Canvas().Capture())
}
+func TestEntry_ShiftSelection_ResetOnFocusLost(t *testing.T) {
+ e := NewEntry()
+ e.SetText("Hello")
+
+ e.KeyDown(&fyne.KeyEvent{Name: desktop.KeyShiftLeft})
+ assert.True(t, e.selectKeyDown)
+
+ e.FocusLost()
+ assert.False(t, e.selectKeyDown)
+}
+
func getClickPosition(str string, row int) *fyne.PointEvent {
x := fyne.MeasureText(str, theme.TextSize(), fyne.TextStyle{}).Width + theme.Padding()
diff --git a/widget/entry_test.go b/widget/entry_test.go
index 96cfc0b52e..20b46952e4 100644
--- a/widget/entry_test.go
+++ b/widget/entry_test.go
@@ -195,6 +195,22 @@ func TestEntry_Disableable(t *testing.T) {
test.AssertRendersToMarkup(t, "entry/disableable_enabled_custom_value.xml", c)
}
+func TestEntry_Disabled_TextSelection(t *testing.T) {
+ entry, window := setupImageTest(t, false)
+ defer teardownImageTest(window)
+ entry.SetText("Testing")
+ entry.Disable()
+ c := window.Canvas()
+
+ assert.True(t, entry.Disabled())
+ test.DoubleTap(entry)
+
+ test.AssertImageMatches(t, "entry/disabled_text_selected.png", c.Capture())
+
+ entry.FocusLost()
+ test.AssertImageMatches(t, "entry/disabled_text_unselected.png", c.Capture())
+}
+
func TestEntry_EmptySelection(t *testing.T) {
entry := widget.NewEntry()
entry.SetText("text")
@@ -331,6 +347,34 @@ func TestEntry_MultilineSelect(t *testing.T) {
assert.Equal(t, "ng\nTe", e.SelectedText())
}
+func TestEntry_MultilineWrapping_DeleteWithBackspace(t *testing.T) {
+ entry := widget.NewMultiLineEntry()
+ entry.Wrapping = fyne.TextWrapWord
+ entry.Resize(fyne.NewSize(64, 64))
+ test.Type(entry, "line1")
+ test.Type(entry, "\nline2")
+ test.Type(entry, "\nline3")
+
+ assert.Equal(t, 5, entry.CursorColumn)
+ assert.Equal(t, 2, entry.CursorRow)
+
+ for i := 0; i < 4; i++ {
+ entry.TypedKey(&fyne.KeyEvent{Name: fyne.KeyBackspace})
+ assert.Equal(t, 4-i, entry.CursorColumn)
+ assert.Equal(t, 2, entry.CursorRow)
+ }
+
+ entry.TypedKey(&fyne.KeyEvent{Name: fyne.KeyBackspace})
+ assert.Equal(t, 0, entry.CursorColumn)
+ assert.Equal(t, 2, entry.CursorRow)
+
+ assert.NotPanics(t, func() {
+ entry.TypedKey(&fyne.KeyEvent{Name: fyne.KeyBackspace})
+ })
+ assert.Equal(t, 5, entry.CursorColumn)
+ assert.Equal(t, 1, entry.CursorRow)
+}
+
func TestEntry_Notify(t *testing.T) {
entry := widget.NewEntry()
changed := false
diff --git a/widget/entry_validation.go b/widget/entry_validation.go
index 5a380f6f84..5a162ea841 100644
--- a/widget/entry_validation.go
+++ b/widget/entry_validation.go
@@ -95,7 +95,7 @@ func (r *validationStatusRenderer) MinSize() fyne.Size {
func (r *validationStatusRenderer) Refresh() {
r.entry.propertyLock.RLock()
defer r.entry.propertyLock.RUnlock()
- if r.entry.Text == "" {
+ if r.entry.Text == "" || r.entry.disabled {
r.icon.Hide()
return
}
diff --git a/widget/entry_validation_test.go b/widget/entry_validation_test.go
index d190bdf12e..4e72353a9e 100644
--- a/widget/entry_validation_test.go
+++ b/widget/entry_validation_test.go
@@ -15,6 +15,18 @@ import (
var validator = validation.NewRegexp(`^\d{4}-\d{2}-\d{2}$`, "Input is not a valid date")
+func TestEntry_DisabledHideValidation(t *testing.T) {
+ entry, window := setupImageTest(t, false)
+ defer teardownImageTest(window)
+ c := window.Canvas()
+
+ entry.Validator = validator
+ entry.SetText("invalid text")
+ entry.Disable()
+
+ test.AssertImageMatches(t, "entry/validation_disabled.png", c.Capture())
+}
+
func TestEntry_ValidatedEntry(t *testing.T) {
entry, window := setupImageTest(t, false)
defer teardownImageTest(window)
diff --git a/widget/slider.go b/widget/slider.go
index 9623555f7a..63822a2dc6 100644
--- a/widget/slider.go
+++ b/widget/slider.go
@@ -100,7 +100,14 @@ func (s *Slider) DragEnd() {
func (s *Slider) Dragged(e *fyne.DragEvent) {
ratio := s.getRatio(&(e.PointEvent))
+ lastValue := s.Value
+
s.updateValue(ratio)
+
+ if s.almostEqual(lastValue, s.Value) {
+ return
+ }
+
s.Refresh()
if s.OnChanged != nil {
@@ -156,10 +163,15 @@ func (s *Slider) clampValueToRange() {
return
}
- i := -(math.Log10(s.Step))
- p := math.Pow(10, i)
-
- s.Value = float64(int(s.Value*p)) / p
+ rem := math.Mod(s.Value, s.Step)
+ if rem == 0 {
+ return
+ }
+ min := s.Value - rem
+ if rem > s.Step/2 {
+ min += s.Step
+ }
+ s.Value = min
}
func (s *Slider) updateValue(ratio float64) {
@@ -174,9 +186,15 @@ func (s *Slider) SetValue(value float64) {
return
}
+ lastValue := s.Value
+
s.Value = value
s.clampValueToRange()
+ if s.almostEqual(lastValue, s.Value) {
+ return
+ }
+
if s.OnChanged != nil {
s.OnChanged(s.Value)
}
@@ -206,6 +224,11 @@ func (s *Slider) CreateRenderer() fyne.WidgetRenderer {
return slide
}
+func (s *Slider) almostEqual(a, b float64) bool {
+ delta := math.Abs(a - b)
+ return delta <= s.Step/2
+}
+
// Unbind disconnects any configured data source from this Slider.
// The current value will remain at the last value of the data source.
//
diff --git a/widget/slider_test.go b/widget/slider_test.go
index 52292d76bc..38c5e539e1 100644
--- a/widget/slider_test.go
+++ b/widget/slider_test.go
@@ -118,7 +118,8 @@ func TestSlider_VerticalLayout(t *testing.T) {
}
func TestSlider_OnChanged(t *testing.T) {
- slider := NewSlider(0, 1)
+ slider := NewSlider(0, 2)
+ slider.Resize(slider.MinSize())
assert.Empty(t, slider.OnChanged)
changes := 0
@@ -130,10 +131,53 @@ func TestSlider_OnChanged(t *testing.T) {
assert.Equal(t, 0, changes)
slider.SetValue(0.5)
+ assert.Equal(t, 0, changes)
+
+ drag := &fyne.DragEvent{}
+ drag.PointEvent.Position = fyne.NewPos(25, 2)
+ slider.Dragged(drag)
assert.Equal(t, 1, changes)
- drag := &fyne.DragEvent{Dragged: fyne.NewDelta(10, 2)}
+ drag.PointEvent.Position = fyne.NewPos(25, 2)
slider.Dragged(drag)
+ assert.Equal(t, 1, changes)
+
+ drag.PointEvent.Position = fyne.NewPos(50, 2)
+ slider.Dragged(drag)
+ assert.Equal(t, 2, changes)
+}
+
+func TestSlider_OnChanged_Float(t *testing.T) {
+ slider := NewSlider(0, 1)
+ slider.Step = 0.5
+ slider.Resize(slider.MinSize())
+ assert.Empty(t, slider.OnChanged)
+
+ changes := 0
+
+ slider.OnChanged = func(_ float64) {
+ changes++
+ }
+
+ assert.Equal(t, 0, changes)
+
+ slider.SetValue(0.15)
+ assert.Equal(t, 0, changes)
+
+ drag := &fyne.DragEvent{}
+ drag.PointEvent.Position = fyne.NewPos(25, 2)
+ slider.Dragged(drag)
+ assert.Equal(t, 1, changes)
+
+ drag.PointEvent.Position = fyne.NewPos(25, 2)
+ slider.Dragged(drag)
+ assert.Equal(t, 1, changes)
+
+ drag.PointEvent.Position = fyne.NewPos(50, 2)
+ slider.Dragged(drag)
+ assert.Equal(t, 2, changes)
+
+ slider.SetValue(0.9)
assert.Equal(t, 2, changes)
}
diff --git a/widget/testdata/entry/disabled_text_selected.png b/widget/testdata/entry/disabled_text_selected.png
new file mode 100644
index 0000000000..9b07619a9d
Binary files /dev/null and b/widget/testdata/entry/disabled_text_selected.png differ
diff --git a/widget/testdata/entry/disabled_text_unselected.png b/widget/testdata/entry/disabled_text_unselected.png
new file mode 100644
index 0000000000..fc8c4a35fa
Binary files /dev/null and b/widget/testdata/entry/disabled_text_unselected.png differ
diff --git a/widget/testdata/entry/validation_disabled.png b/widget/testdata/entry/validation_disabled.png
new file mode 100644
index 0000000000..8fbcc323a1
Binary files /dev/null and b/widget/testdata/entry/validation_disabled.png differ