Update go2tv-lite
This commit is contained in:
@@ -3,12 +3,14 @@ package main
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
@@ -18,10 +20,8 @@ import (
|
||||
|
||||
"github.com/alexballas/go2tv/devices"
|
||||
"github.com/alexballas/go2tv/httphandlers"
|
||||
"github.com/alexballas/go2tv/internal/interactive"
|
||||
"github.com/alexballas/go2tv/soapcalls"
|
||||
"github.com/alexballas/go2tv/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -37,6 +37,8 @@ var (
|
||||
versionPtr = flag.Bool("version", false, "Print version.")
|
||||
)
|
||||
|
||||
type dummyScreen struct{}
|
||||
|
||||
type flagResults struct {
|
||||
dmrURL string
|
||||
exit bool
|
||||
@@ -47,6 +49,24 @@ func main() {
|
||||
var mediaType string
|
||||
var mediaFile interface{}
|
||||
var isSeek bool
|
||||
var tvdata *soapcalls.TVPayload
|
||||
var s *httphandlers.HTTPserver
|
||||
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
|
||||
go func() {
|
||||
for range c {
|
||||
if tvdata != nil {
|
||||
_ = tvdata.SendtoTV("Stop")
|
||||
}
|
||||
if s != nil {
|
||||
s.StopServer()
|
||||
}
|
||||
fmt.Println("force exiting..")
|
||||
os.Exit(0)
|
||||
}
|
||||
}()
|
||||
|
||||
flag.Parse()
|
||||
|
||||
@@ -110,13 +130,12 @@ func main() {
|
||||
whereToListen, err := utils.URLtoListenIPandPort(flagRes.dmrURL)
|
||||
check(err)
|
||||
|
||||
scr, err := interactive.InitTcellNewScreen()
|
||||
check(err)
|
||||
scr := &dummyScreen{}
|
||||
|
||||
callbackPath, err := utils.RandomString()
|
||||
check(err)
|
||||
|
||||
tvdata := &soapcalls.TVPayload{
|
||||
tvdata = &soapcalls.TVPayload{
|
||||
ControlURL: upnpServicesURLs.AvtransportControlURL,
|
||||
EventURL: upnpServicesURLs.AvtransportEventSubURL,
|
||||
RenderingControlURL: upnpServicesURLs.RenderingControlURL,
|
||||
@@ -132,7 +151,7 @@ func main() {
|
||||
Seekable: isSeek,
|
||||
}
|
||||
|
||||
s := httphandlers.NewServer(whereToListen)
|
||||
s = httphandlers.NewServer(whereToListen)
|
||||
serverStarted := make(chan struct{})
|
||||
|
||||
// We pass the tvdata here as we need the callback handlers to be able to react
|
||||
@@ -144,7 +163,10 @@ func main() {
|
||||
// Wait for HTTP server to properly initialize
|
||||
<-serverStarted
|
||||
|
||||
scr.InterInit(tvdata)
|
||||
err = tvdata.SendtoTV("Play1")
|
||||
check(err)
|
||||
|
||||
select {}
|
||||
}
|
||||
|
||||
func check(err error) {
|
||||
@@ -321,3 +343,12 @@ func checkVerflag() {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *dummyScreen) EmitMsg(msg string) {
|
||||
fmt.Println(msg)
|
||||
}
|
||||
|
||||
func (s *dummyScreen) Fini() {
|
||||
fmt.Println("exiting..")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
1.12.0
|
||||
devel
|
||||
|
@@ -16,13 +16,14 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"errors"
|
||||
|
||||
"github.com/alexballas/go2tv/devices"
|
||||
"github.com/alexballas/go2tv/httphandlers"
|
||||
"github.com/alexballas/go2tv/internal/gui"
|
||||
"github.com/alexballas/go2tv/internal/interactive"
|
||||
"github.com/alexballas/go2tv/soapcalls"
|
||||
"github.com/alexballas/go2tv/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@@ -1 +1 @@
|
||||
1.12.0
|
||||
devel
|
||||
|
54
vendor/fyne.io/fyne/v2/.gitignore
generated
vendored
54
vendor/fyne.io/fyne/v2/.gitignore
generated
vendored
@@ -1,54 +0,0 @@
|
||||
### Project Specific
|
||||
cmd/fyne/fyne
|
||||
cmd/fyne/fyne.exe
|
||||
cmd/fyne_demo/fyne_demo
|
||||
cmd/fyne_demo/fyne_demo.apk
|
||||
cmd/fyne_demo/fyne-demo.app
|
||||
cmd/fyne_demo/fyne_demo.exe
|
||||
cmd/fyne_settings/fyne_settings
|
||||
cmd/fyne_settings/fyne_settings.apk
|
||||
cmd/fyne_settings/fyne_settings.app
|
||||
cmd/fyne_settings/fyne_settings.exe
|
||||
cmd/hello/hello
|
||||
cmd/hello/hello.apk
|
||||
cmd/hello/hello.app
|
||||
cmd/hello/hello.exe
|
||||
fyne-cross
|
||||
|
||||
### Tests
|
||||
**/testdata/failed
|
||||
|
||||
### Go
|
||||
# Output of the coverage tool
|
||||
*.out
|
||||
|
||||
### macOS
|
||||
# General
|
||||
.DS_Store
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
### JetBrains
|
||||
.idea
|
||||
|
||||
### VSCode
|
||||
.vscode
|
||||
|
||||
### Vim
|
||||
# Swap
|
||||
[._]*.s[a-v][a-z]
|
||||
[._]*.sw[a-p]
|
||||
[._]s[a-v][a-z]
|
||||
[._]sw[a-p]
|
||||
|
||||
# Session
|
||||
Session.vim
|
||||
|
||||
# Temporary
|
||||
.netrwhist
|
||||
*~
|
||||
# Auto-generated tag files
|
||||
tags
|
||||
# Persistent undo
|
||||
[._]*.un~
|
1
vendor/fyne.io/fyne/v2/.godocdown.import
generated
vendored
1
vendor/fyne.io/fyne/v2/.godocdown.import
generated
vendored
@@ -1 +0,0 @@
|
||||
fyne.io/fyne/v2
|
14
vendor/fyne.io/fyne/v2/AUTHORS
generated
vendored
14
vendor/fyne.io/fyne/v2/AUTHORS
generated
vendored
@@ -1,14 +0,0 @@
|
||||
Andy Williams <andy@andy.xyz>
|
||||
Steve OConnor <steveoc64@gmail.com>
|
||||
Luca Corbo <lu.corbo@gmail.com>
|
||||
Paul Hovey <paul@paulhovey.org>
|
||||
Charles Corbett <nafredy@gmail.com>
|
||||
Tilo Prütz <tilo@pruetz.net>
|
||||
Stephen Houston <smhouston88@gmail.com>
|
||||
Storm Hess <stormhess@gloryskulls.com>
|
||||
Stuart Scott <stuart.murray.scott@gmail.com>
|
||||
Jacob Alzén <>
|
||||
Charles A. Daniels <charles@cdaniels.net>
|
||||
Pablo Fuentes <f.pablo1@hotmail.com>
|
||||
Changkun Ou <hi@changkun.de>
|
||||
|
965
vendor/fyne.io/fyne/v2/CHANGELOG.md
generated
vendored
965
vendor/fyne.io/fyne/v2/CHANGELOG.md
generated
vendored
@@ -1,965 +0,0 @@
|
||||
# Changelog
|
||||
|
||||
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.2.3 - 8 July 2022
|
||||
|
||||
### Fixed
|
||||
|
||||
* Regression: Preferences are not parsed at program start (#3125)
|
||||
* Wrappable RichText in a Split container causes crash (#3003, #2961)
|
||||
* meta.Version is always 1.0.0 on android & ios (#3109)
|
||||
|
||||
|
||||
## 2.2.2 - 30 June 2022
|
||||
|
||||
### Fixed
|
||||
|
||||
* Windows missing version metadata when packaged (#3046)
|
||||
* Fyne package would not build apps using old Fyne versions
|
||||
* System tray icon may not be removed on app exit in Windows
|
||||
* Emphasis in Markdown gives erroneous output in RichText (#2974)
|
||||
* When last visible window is closed, hidden window is set visible (#3059)
|
||||
* Do not close app when last window is closed but systrayMenu exists (#3092)
|
||||
* Image with ImageFillOriginal not showing (#3102)
|
||||
|
||||
|
||||
## 2.2.1 - 12 June 2022
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix various race conditions and compatibility issues with System tray menus
|
||||
* Resolve issue where macOS systray menu may not appear
|
||||
* Updated yaml dependency to fix CVE-2022-28948
|
||||
* Tab buttons stop working after removing a tab (#3050)
|
||||
* os.SetEnv("FYNE_FONT") doesn't work in v2.2.0 (#3056)
|
||||
|
||||
|
||||
## 2.2.0 - 7 June 2022
|
||||
|
||||
### Added
|
||||
|
||||
* Add SetIcon method on ToolbarAction (#2475)
|
||||
* Access compiled app metadata using new `App.Metadata()` method
|
||||
* Add support for System tray icon and menu (#283)
|
||||
* Support for Android Application Bundle (.aab) (#2663)
|
||||
* Initial support for OpenBSD and NetBSD
|
||||
* Add keyboard shortcuts to menu (#682)
|
||||
* Add technical preview of web driver and `fyne serve` command
|
||||
* Added `iossimulator` build target (#1917)
|
||||
* Allow dynamic themes via JSON templates (#211)
|
||||
* Custom hyperlink callback (#2979)
|
||||
* Add support for `.ico` file when compiling for windows (#2412)
|
||||
* Add binding.NewStringWithFormat (#2890)
|
||||
* Add Entry.SetMinRowsVisible
|
||||
* Add Menu.Refresh() and MainMenu.Refresh() (#2853)
|
||||
* Packages for Linux and BSD now support installing into the home directory
|
||||
* Add `.RemoveAll()` to containers
|
||||
* Add an AllString validator for chaining together string validators
|
||||
|
||||
### Changed
|
||||
|
||||
* Toolbar item constructors now return concrete types instead of ToolbarItem
|
||||
* Low importance buttons no longer draw button color as a background
|
||||
* ProgressBar widget height is now consistent with other widgets
|
||||
* Include check in DocTabs menu to show current tab
|
||||
* Don't call OnScrolled if offset did not change (#2646)
|
||||
* Prefer ANDROID_NDK_HOME over the ANDROID_HOME ndk-bundle location (#2920)
|
||||
* Support serialisation / deserialisation of the widget tree (#5)
|
||||
* Better error reporting / handling when OpenGL is not available (#2689)
|
||||
* Memory is now better reclaimed on Android when the OS requests it
|
||||
* Notifications on Linux and BSD now show the application icon
|
||||
* Change listeners for preferences no longer run when setting the same value
|
||||
* The file dialog now shows extensions in the list view for better readability
|
||||
* Many optimisations and widget performance enhancements
|
||||
* Updated various dependencies to their latest versions
|
||||
|
||||
### Fixed
|
||||
|
||||
* SendNotification does not show app name on Windows (#1940)
|
||||
* Copy-paste via keyboard don't work translated keyboard mappings on Windows (#1220)
|
||||
* OnScrolled triggered when offset hasn't changed (#1868)
|
||||
* Carriage Return (\r) is rendered as space (#2456)
|
||||
* storage.List() returns list with nil elements for empty directories (#2858)
|
||||
* Entry widget, position of cursor when clicking empty space (#2877)
|
||||
* SelectEntry cause UI hang (#2925)
|
||||
* Font cutoff with bold italics (#3001)
|
||||
* Fyne error: Preferences load error (#2936, 3015)
|
||||
* Scrolled List bad redraw when window is maximized (#3013)
|
||||
* Linux and BSD packages not being installable if the name contained spaces
|
||||
|
||||
|
||||
## 2.1.4 - 17 March 2022
|
||||
|
||||
### Fixed
|
||||
|
||||
* SetTheme() is not fully effective for widget.Form (#2810)
|
||||
* FolderOpenDialog SetDismissText is ineffective (#2830)
|
||||
* window.Resize() does not work if SetFixedSize(true) is set after (#2819)
|
||||
* Container.Remove() race causes crash (#2826, #2775, #2481)
|
||||
* FixedSize Window improperly sized if contains image with ImageFillOriginal (#2800)
|
||||
|
||||
|
||||
## 2.1.3 - 24 February 2022
|
||||
|
||||
### Fixed
|
||||
|
||||
* The text on button can't be show correctly when use imported font (#2512)
|
||||
* Fix issues with DocTabs scrolling (#2709)
|
||||
* Fix possible crash for tapping extended Radio or Check item
|
||||
* Resolve lookup of relative icons in FyneApp.toml
|
||||
* Window not shown when SetFixedSize is used without Resize (#2784)
|
||||
* Text and links in markdown can be rendered on top of each other (#2695)
|
||||
* Incorrect cursor movement in a multiline entry with wrapping (#2698)
|
||||
|
||||
|
||||
## 2.1.2 - 6 December 2021
|
||||
|
||||
### Fixed
|
||||
|
||||
* Scrolling list bound to data programmatically causes nil pointer dereference (#2549)
|
||||
* Rich text from markdown can get newlines wrong (#2589)
|
||||
* Fix crash on 32bit operating systems (#2603)
|
||||
* Compile failure on MacOS 10.12 Sierra (#2478)
|
||||
* Don't focus widgets on mobile where keyboard should not display (#2598)
|
||||
* storage.List doesn't return complete URI on Android for "content:" scheme (#2619)
|
||||
* Last word of the line and first word of the next line are joined in markdown parse (#2647)
|
||||
* Support for building `cmd/fyne` on Windows arm64
|
||||
* Fixed FreeBSD requiring installed glfw library dependency (#1928)
|
||||
* Apple M1: error when using mouse drag to resize window (#2188)
|
||||
* Struct binding panics in reload with slice field (#2607)
|
||||
* File Dialog favourites can break for certain locations (#2595)
|
||||
* Define user friendly names for Android Apps (#2653)
|
||||
* Entry validator not updating if content is changed via data binding after SetContent (#2639)
|
||||
* CenterOnScreen not working for FixedSize Window (#2550)
|
||||
* Panic in boundStringListItem.Get() (#2643)
|
||||
* Can't set an app/window icon to be an svg. (#1196)
|
||||
* SetFullScreen(false) can give error (#2588)
|
||||
|
||||
|
||||
## 2.1.1 - 22 October 2021
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix issue where table could select cells beyond data bound
|
||||
* Some fast taps could be ignored (#2484)
|
||||
* iOS app stops re-drawing mid-frame after a while (#950)
|
||||
* Mobile simulation mode did not work on Apple M1 computers
|
||||
* TextGrid background color can show gaps in render (#2493)
|
||||
* Fix alignment of files in list view of file dialog
|
||||
* Crash setting visible window on macOS to fixed size (#2488)
|
||||
* fyne bundle ignores -name flag in windows (#2395)
|
||||
* Lines with nil colour would crash renderer
|
||||
* Android -nm tool not found with NDK 23 (#2498)
|
||||
* Runtime panic because out of touchID (#2407)
|
||||
* Long text in Select boxes overflows out of the box (#2522)
|
||||
* Calling SetText on Label may not refresh correctly
|
||||
* Menu can be triggered by # key but not always Alt
|
||||
* Cursor position updates twice with delay (#2525)
|
||||
* widgets freeze after being in background and then a crash upon pop-up menu (#2536)
|
||||
* too many Refresh() calls may now cause visual artifacts in the List widget (#2548)
|
||||
* Entry.SetText may panic if called on a multiline entry with selected text (#2482)
|
||||
* TextGrid not always drawing correctly when resized (#2501)
|
||||
|
||||
|
||||
## 2.1.0 - 17 September 2021
|
||||
|
||||
### Added
|
||||
|
||||
* DocTabs container for handling multiple open files
|
||||
* Lifecycle API for handling foreground, background and other event
|
||||
* Add RichText widget and Markdown parser
|
||||
* Add TabWidth to TextStyle to specify tab size in spaces
|
||||
* Add CheckGroup widget for multi-select
|
||||
* Add FyneApp.toml metadata file to ease build commands
|
||||
* Include http and https in standard repositories
|
||||
* Add selection color to themes
|
||||
* Include baseline information in driver font measurement
|
||||
* Document storage API (App.Storage().Create() and others)
|
||||
* Add "App Files" to file dialog for apps that use document storage
|
||||
* Tab overflow on AppTabs
|
||||
* Add URI and Unbound type to data bindings
|
||||
* Add keyboard support for menus, pop-ups and buttons
|
||||
* Add SimpleRenderer to help make simple widgets (#709)
|
||||
* Add scroll functions for List, Table, Tree (#1892)
|
||||
* Add selection and disabling to MenuItem
|
||||
* Add Alignment to widget.Select (#2329)
|
||||
* Expose ScanCode for keyboard events originating from hardware (#1523)
|
||||
* Support macOS GPU switching (#2423)
|
||||
|
||||
### Changed
|
||||
|
||||
* Focusable widgets are no longer focused on tap, add canvas.Focus(obj) in Tapped handler if required
|
||||
* Move to background based selection for List, Table and Tree
|
||||
* Update fyne command line tool to use --posix style parameters
|
||||
* Switch from gz to xz compression for unix packages
|
||||
* Performance improvements with line, text and raster rendering
|
||||
* Items not yet visible can no longer be focused
|
||||
* Lines can now be drawn down to 1px (instead of 1dp) (#2298)
|
||||
* Support multiple lines of text on button (#2378)
|
||||
* Improved text layout speed by caching string size calculations
|
||||
* Updated to require Go 1.14 so we can use some new features
|
||||
* Window Resize request is now asynchronous
|
||||
* Up/Down keys take cursor home/end when on first/last lines respectively
|
||||
|
||||
### Fixed
|
||||
|
||||
* Correctly align text tabs (#1791)
|
||||
* Mobile apps theme does not match system (#472)
|
||||
* Toolbar with widget.Label makes the ToolbarAction buttons higher (#2257)
|
||||
* Memory leaks in renderers and canvases cache maps (#735)
|
||||
* FileDialog SetFilter does not work on Android devices (#2353)
|
||||
* Hover fix for List and Tree with Draggable objects
|
||||
* Line resize can flip slope (#2208)
|
||||
* Deadlocks when using widgets with data (#2348)
|
||||
* Changing input type with keyboard visible would not update soft keyboards
|
||||
* MainMenu() Close item does NOT call function defined in SetCloseIntercept (#2355)
|
||||
* Entry cursor position with mouse is offset vertically by theme.SizeNameInputBorder (#2387)
|
||||
* Backspace key is not working on Android AOSP (#1941)
|
||||
* macOS: 'NSUserNotification' has been deprecated (#1833)
|
||||
* macOS: Native menu would add new items if refreshed
|
||||
* iOS builds fail since Go 1.16
|
||||
* Re-add support for 32 bit iOS devices, if built with Go 1.14
|
||||
* Android builds fail on Apple M1 (#2439)
|
||||
* SetFullScreen(true) before ShowAndRun fails (#2446)
|
||||
* Interacting with another app when window.SetFullScreen(true) will cause the application to hide itself. (#2448)
|
||||
* Sequential writes to preferences does not save to file (#2449)
|
||||
* Correct Android keyboard handling (#2447)
|
||||
* MIUI-Android: The widget’s Hyperlink cannot open the URL (#1514)
|
||||
* Improved performance of data binding conversions and text MinSize
|
||||
|
||||
|
||||
## 2.0.4 - 6 August 2021
|
||||
|
||||
### Changed
|
||||
|
||||
* Disable Form labels when the element it applys to is disabled (#1530)
|
||||
* Entry popup menu now fires shortcuts so extended widgets can intercept
|
||||
* Update Android builds to SDK 30
|
||||
|
||||
### Fixed
|
||||
|
||||
* sendnotification show appID for name on windows (#1940)
|
||||
* Fix accidental removal of windows builds during cross-compile
|
||||
* Removing an item from a container did not update layout
|
||||
* Update title bar on Windows 10 to match OS theme (#2184)
|
||||
* Tapped triggered after Drag (#2235)
|
||||
* Improved documentation and example code for file dialog (#2156)
|
||||
* Preferences file gets unexpectedly cleared (#2241)
|
||||
* Extra row dividers rendered on using SetColumnWidth to update a table (#2266)
|
||||
* Fix resizing fullscreen issue
|
||||
* Fullscreen changes my display resolution when showing a dialog (#1832)
|
||||
* Entry validation does not work for empty field (#2179)
|
||||
* Tab support for focus handling missing on mobile
|
||||
* ScrollToBottom not always scrolling all the way when items added to container.Scroller
|
||||
* Fixed scrollbar disappearing after changing content (#2303)
|
||||
* Calling SetContent a second time with the same content will not show
|
||||
* Drawing text can panic when Color is nil (#2347)
|
||||
* Optimisations when drawing transparent rectangle or whitespace strings
|
||||
|
||||
|
||||
## 2.0.3 - 30 April 2021
|
||||
|
||||
### Fixed
|
||||
|
||||
* Optimisations for TextGrid rendering
|
||||
* Data binding with widget.List sometimes crash while scrolling (#2125)
|
||||
* Fix compilation on FreeBSD 13
|
||||
* DataLists should notify only once when change.
|
||||
* Keyboard will appear on Android in disabled Entry Widget (#2139)
|
||||
* Save dialog with filename for Android
|
||||
* form widget can't draw hinttext of appended item. (#2028)
|
||||
* Don't create empty shortcuts (#2148)
|
||||
* Install directory for windows install command contains ".exe"
|
||||
* Fix compilation for Linux Wayland apps
|
||||
* Fix tab button layout on mobile (#2117)
|
||||
* Options popup does not move if a SelectEntry widget moves with popup open
|
||||
* Speed improvements to Select and SelectEntry drop down
|
||||
* theme/fonts has an apache LICENSE file but it should have SIL OFL (#2193)
|
||||
* Fix build requirements for target macOS platforms (#2154)
|
||||
* ScrollEvent.Position and ScrollEvent.AbsolutePosition is 0,0 (#2199)
|
||||
|
||||
|
||||
## 2.0.2 - 1 April 2021
|
||||
|
||||
### Changed
|
||||
|
||||
* 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
|
||||
|
||||
* An Entry with `Wrapping=fyne.TextWrapOff` no longer blocks scroll events from a parent
|
||||
|
||||
### Fixed
|
||||
|
||||
* Dialog.Resize() has no effect if called before Dialog.Show() (#1863)
|
||||
* SelectTab does not always correctly set the blue underline to the selected tab (#1872)
|
||||
* Entry Validation Broken when using Data binding (#1890)
|
||||
* Fix background colour not applying until theme change
|
||||
* android runtime error with fyne.dialog (#1896)
|
||||
* Fix scale calculations for Wayland phones (PinePhone)
|
||||
* Correct initial state of entry validation
|
||||
* fix entry widget mouse drag selection when scrolled
|
||||
* List widget panic when refreshing after changing content length (#1864)
|
||||
* Fix image caching that was too aggressive on resize
|
||||
* Pointer and cursor misalignment in widget.Entry (#1937)
|
||||
* SIGSEGV Sometimes When Closing a Program by Clicking a Button (#1604)
|
||||
* Advanced Color Picker shows Black for custom primary color as RGBA (#1970)
|
||||
* Canvas.Focus() before window visible causes application to crash (#1893)
|
||||
* Menu over Content (#1973)
|
||||
* Error compiling fyne on Apple M1 arm64 (#1739)
|
||||
* Cells are not getting draw in correct location after column resize. (#1951)
|
||||
* Possible panic when selecting text in a widget.Entry (#1983)
|
||||
* Form validation doesn't enable submit button (#1965)
|
||||
* Creating a window shows it before calling .Show() and .Hide() does not work (#1835)
|
||||
* Dialogs are not refreshed correctly on .Show() (#1866)
|
||||
* Failed creating setting storage : no such directory (#2023)
|
||||
* Erroneous custom filter types not supported error on mobile (#2012)
|
||||
* High importance button show no hovered state (#1785)
|
||||
* List widget does not render all visible content after content data gets shorter (#1948)
|
||||
* Calling Select on List before draw can crash (#1960)
|
||||
* Dialog not resizing in newly created window (#1692)
|
||||
* Dialog not returning to requested size (#1382)
|
||||
* Entry without scrollable content prevents scrolling of outside scroller (#1939)
|
||||
* fyne_demo crash after selecting custom Theme and table (#2018)
|
||||
* Table widget crash when scrolling rapidly (#1887)
|
||||
* Cursor animation sometimes distorts the text (#1778)
|
||||
* Extended password entry panics when password revealer is clicked (#2036)
|
||||
* Data binding limited to 1024 simultaneous operations (#1838)
|
||||
* Custom theme does not refresh when variant changes (#2006)
|
||||
|
||||
|
||||
## 2.0 - 22 January 2021
|
||||
|
||||
### Changes that are not backward compatible
|
||||
|
||||
These changes may break some apps, please read the
|
||||
[upgrading doc](https://developer.fyne.io/api/v2.0/upgrading) for more info
|
||||
The import path is now `fyne.io/fyne/v2` when you are ready to make the update.
|
||||
|
||||
* Coordinate system to float32
|
||||
* Size and Position units were changed from int to float32
|
||||
* `Text.TextSize` moved to float32 and `fyne.MeasureText` now takes a float32 size parameter
|
||||
* Removed `Size.Union` (use `Size.Max` instead)
|
||||
* Added fyne.Delta for difference-based X, Y float32 representation
|
||||
* DraggedEvent.DraggedX and DraggedY (int, int) to DraggedEvent.Dragged (Delta)
|
||||
* ScrollEvent.DeltaX and DeltaY (int, int) moved to ScrollEvent.Scrolled (Delta)
|
||||
|
||||
* Theme API update
|
||||
* `fyne.Theme` moved to `fyne.LegacyTheme` and can be load to a new theme using `theme.FromLegacy`
|
||||
* A new, more flexible, Theme interface has been created that we encourage developers to use
|
||||
|
||||
* The second parameter of `theme.NewThemedResource` was removed, it was previously ignored
|
||||
* The desktop.Cursor definition was renamed desktop.StandardCursor to make way for custom cursors
|
||||
* Button `Style` and `HideShadow` were removed, use `Importance`
|
||||
|
||||
* iOS apps preferences will be lost in this upgrade as we move to more advanced storage
|
||||
* Dialogs no longer show when created, unless using the ShowXxx convenience methods
|
||||
* Entry widget now contains scrolling so should no longer be wrapped in a scroll container
|
||||
|
||||
* Removed deprecated types including:
|
||||
- `dialog.FileIcon` (now `widget.FileIcon`)
|
||||
- `widget.Radio` (now `widget.RadioGroup`)
|
||||
- `widget.AccordionContainer` (now `widget.Accordion`)
|
||||
- `layout.NewFixedGridLayout()` (now `layout.NewGridWrapLayout()`)
|
||||
- `widget.ScrollContainer` (now `container.Scroll`)
|
||||
- `widget.SplitContainer` (now `container.Spilt`)
|
||||
- `widget.Group` (replaced by `widget.Card`)
|
||||
- `widget.Box` (now `container.NewH/VBox`, with `Children` field moved to `Objects`)
|
||||
- `widget.TabContainer` and `widget.AppTabs` (now `container.AppTabs`)
|
||||
* Many deprecated fields have been removed, replacements listed in API docs 1.4
|
||||
- for specific information you can browse https://developer.fyne.io/api/v1.4/
|
||||
|
||||
### Added
|
||||
|
||||
* Data binding API to connect data sources to widgets and sync data
|
||||
- Add preferences data binding and `Preferences.AddChangeListener`
|
||||
- Add bind support to `Check`, `Entry`, `Label`, `List`, `ProgressBar` and `Slider` widgets
|
||||
* Animation API for handling smooth element transitions
|
||||
- Add animations to buttons, tabs and entry cursor
|
||||
* Storage repository API for connecting custom file sources
|
||||
- Add storage functions `Copy`, `Delete` and `Move` for `URI`
|
||||
- Add `CanRead`, `CanWrite` and `CanList` to storage APIs
|
||||
* New Theme API for easier customisation of apps
|
||||
- Add ability for custom themes to support light/dark preference
|
||||
- Support for custom icons in theme definition
|
||||
- New `theme.FromLegacy` helper to use old theme API definitions
|
||||
* Add fyne.Vector for managing x/y float32 coordinates
|
||||
* Add MouseButtonTertiary for middle mouse button events on desktop
|
||||
* Add `canvas.ImageScaleFastest` for faster, less precise, scaling
|
||||
* Add new `dialog.Form` that will phase out `dialog.Entry`
|
||||
* Add keyboard control for main menu
|
||||
* Add `Scroll.OnScrolled` event for seeing changes in scroll container
|
||||
* Add `TextStyle` and `OnSubmitted` to `Entry` widget
|
||||
* Add support for `HintText` and showing validation errors in `Form` widget
|
||||
* Added basic support for tab character in `Entry`, `Label` and `TextGrid`
|
||||
|
||||
### Changed
|
||||
|
||||
* Coordinate system is now float32 - see breaking changes above
|
||||
* ScrollEvent and DragEvent moved to Delta from (int, int)
|
||||
* Change bundled resources to use more efficient string storage
|
||||
* Left and Right mouse buttons on Desktop are being moved to `MouseButtonPrimary` and `MouseButtonSecondary`
|
||||
* Many optimisations and widget performance enhancements
|
||||
|
||||
* Moving to new `container.New()` and `container.NewWithoutLayout()` constructors (replacing `fyne.NewContainer` and `fyne.NewContainerWithoutLayout`)
|
||||
* Moving storage APIs `OpenFileFromURI`, `SaveFileToURI` and `ListerForURI` to `Reader`, `Writer` and `List` functions
|
||||
|
||||
### Fixed
|
||||
|
||||
* Validating a widget in widget.Form before renderer was created could cause a panic
|
||||
* Added file and folder support for mobile simulation support (#1470)
|
||||
* Appending options to a disabled widget.RadioGroup shows them as enabled (#1697)
|
||||
* Toggling toolbar icons does not refresh (#1809)
|
||||
* Black screen when slide up application on iPhone (#1610)
|
||||
* Properly align Label in FormItem (#1531)
|
||||
* Mobile dropdowns are too low (#1771)
|
||||
* Cursor does not go down to next line with wrapping (#1737)
|
||||
* Entry: while adding text beyond visible reagion there is no auto-scroll (#912)
|
||||
|
||||
|
||||
## 1.4.3 - 4 January 2021
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fix crash when showing file open dialog on iPadOS
|
||||
* Fix possible missing icon on initial show of disabled button
|
||||
* Capturing a canvas on macOS retina display would not capture full resolution
|
||||
* Fix the release build flag for mobile
|
||||
* Fix possible race conditions for canvas capture
|
||||
* Improvements to `fyne get` command downloader
|
||||
* Fix tree, so it refreshes visible nodes on Refresh()
|
||||
* TabContainer Panic when removing selected tab (#1668)
|
||||
* Incorrect clipping behaviour with nested scroll containers (#1682)
|
||||
* MacOS Notifications are not shown on subsequent app runs (#1699)
|
||||
* Fix the behavior when dragging the divider of split container (#1618)
|
||||
|
||||
|
||||
## 1.4.2 - 9 December 2020
|
||||
|
||||
### Added
|
||||
|
||||
* [fyne-cli] Add support for passing custom build tags (#1538)
|
||||
|
||||
### Changed
|
||||
|
||||
* Run validation on content change instead of on each Refresh in widget.Entry
|
||||
|
||||
### Fixed
|
||||
|
||||
* [fyne-cli] Android: allow to specify an inline password for the keystore
|
||||
* Fixed Card widget MinSize (#1581)
|
||||
* Fix missing release tag to enable BuildRelease in Settings.BuildType()
|
||||
* Dialog shadow does not resize after Refresh (#1370)
|
||||
* Android Duplicate Number Entry (#1256)
|
||||
* Support older macOS by default - back to 10.11 (#886)
|
||||
* Complete certification of macOS App Store releases (#1443)
|
||||
* Fix compilation errors for early stage Wayland testing
|
||||
* Fix entry.SetValidationError() not working correctly
|
||||
|
||||
|
||||
## 1.4.1 - 20 November 2020
|
||||
|
||||
### Changed
|
||||
|
||||
* Table columns can now be different sizes using SetColumnWidth
|
||||
* Avoid unnecessary validation check on Refresh in widget.Form
|
||||
|
||||
### Fixed
|
||||
|
||||
* Tree could flicker on mouse hover (#1488)
|
||||
* Content of table cells could overflow when sized correctly
|
||||
* file:// based URI on Android would fail to list folder (#1495)
|
||||
* Images in iOS release were not all correct size (#1498)
|
||||
* iOS compile failed with Go 1.15 (#1497)
|
||||
* Possible crash when minimising app containing List on Windows
|
||||
* File chooser dialog ignores drive Z (#1513)
|
||||
* Entry copy/paste is crashing on android 7.1 (#1511)
|
||||
* Fyne package creating invalid windows packages (#1521)
|
||||
* Menu bar initially doesn't respond to mouse input on macOS (#505)
|
||||
* iOS: Missing CFBundleIconName and asset catalog (#1504)
|
||||
* CenterOnScreen causes crash on MacOS when called from goroutine (#1539)
|
||||
* desktop.MouseHover Button state is not reliable (#1533)
|
||||
* Initial validation status in widget.Form is not respected
|
||||
* Fix nil reference in disabled buttons (#1558)
|
||||
|
||||
|
||||
## 1.4 - 1 November 2020
|
||||
|
||||
### Added (highlights)
|
||||
|
||||
* List (#156), Table (#157) and Tree collection Widgets
|
||||
* Card, FileItem, Separator widgets
|
||||
* ColorPicker dialog
|
||||
* User selection of primary colour
|
||||
* Container API package to ease using layouts and container widgets
|
||||
* Add input validation
|
||||
* ListableURI for working with directories etc
|
||||
* Added PaddedLayout
|
||||
|
||||
* Window.SetCloseIntercept (#467)
|
||||
* Canvas.InteractiveArea() to indicate where widgets should avoid
|
||||
* TextFormatter for ProgressBar
|
||||
* FileDialog.SetLocation() (#821)
|
||||
* Added dialog.ShowFolderOpen (#941)
|
||||
* Support to install on iOS and android with 'fyne install'
|
||||
* Support asset bundling with go:generate
|
||||
* Add fyne release command for preparing signed apps
|
||||
* Add keyboard and focus support to Radio and Select widgets
|
||||
|
||||
### Changed
|
||||
|
||||
* Theme update - new blue highlight, move buttons to outline
|
||||
* Android SDK target updated to 29
|
||||
* Mobile log entries now start "Fyne" instead of "GoLog"
|
||||
* Don't expand Select to its largest option (#1247)
|
||||
* Button.HideShadow replaced by Button.Importance = LowImportance
|
||||
|
||||
* Deprecate NewContainer in favour of NewContainerWithoutLayout
|
||||
* Deprecate HBox and VBox in favour of new container APIs
|
||||
* Move Container.AddObject to Container.Add matching Container.Remove
|
||||
* Start move from widget.TabContainer to container.AppTabs
|
||||
* Replace Radio with RadioGroup
|
||||
* Deprecate WidgetRenderer.BackgroundColor
|
||||
|
||||
### Fixed
|
||||
|
||||
* Support focus traversal in dialog (#948), (#948)
|
||||
* Add missing AbsolutePosition in some mouse events (#1274)
|
||||
* Don't let scrollbar handle become too small
|
||||
* Ensure tab children are resized before being shown (#1331)
|
||||
* Don't hang if OpenURL loads browser (#1332)
|
||||
* Content not filling dialog (#1360)
|
||||
* Overlays not adjusting on orientation change in mobile (#1334)
|
||||
* Fix missing key events for some keypad keys (#1325)
|
||||
* Issue with non-english folder names in Linux favourites (#1248)
|
||||
* Fix overlays escaping screen interactive bounds (#1358)
|
||||
* Key events not blocked by overlays (#814)
|
||||
* Update scroll container content if it is changed (#1341)
|
||||
* Respect SelectEntry datta changes on refresh (#1462)
|
||||
* Incorrect SelectEntry dropdown button position (#1361)
|
||||
* don't allow both single and double tap events to fire (#1381)
|
||||
* Fix issue where long or tall images could jump on load (#1266, #1432)
|
||||
* Weird behaviour when resizing or minimizing a ScrollContainer (#1245)
|
||||
* Fix panic on NewTextGrid().Text()
|
||||
* Fix issue where scrollbar could jump after mousewheel scroll
|
||||
* Add missing raster support in software render
|
||||
* Respect GOOS/GOARCH in fyne command utilities
|
||||
* BSD support in build tools
|
||||
* SVG Cache could return the incorrect resource (#1479)
|
||||
|
||||
* Many optimisations and widget performance enhancements
|
||||
* Various fixes to file creation and saving on mobile devices
|
||||
|
||||
|
||||
## 1.3.3 - 10 August 2020
|
||||
|
||||
### Added
|
||||
|
||||
* Use icons for file dialog favourites (#1186)
|
||||
* Add ScrollContainer ScrollToBottom and ScrollToTop
|
||||
|
||||
### Changed
|
||||
|
||||
* Make file filter case sensitive (#1185)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Allow popups to create dialogs (#1176)
|
||||
* Use default cursor for dragging scrollbars (#1172)
|
||||
* Correctly parse SVG files with missing X/Y for rect
|
||||
* Fix visibility of Entry placeholder when text is set (#1193)
|
||||
* Fix encoding issue with Windows notifications (#1191)
|
||||
* Fix issue where content expanding on Windows could freeze (#1189)
|
||||
* Fix errors on Windows when reloading Fyne settings (#1165)
|
||||
* Dialogs not updating theme correctly (#1201)
|
||||
* Update the extended progressbar on refresh (#1219)
|
||||
* Segfault if font fails (#1200)
|
||||
* Slider rendering incorrectly when window maximized (#1223)
|
||||
* Changing form label not refreshed (#1231)
|
||||
* Files and folders starting "." show no name (#1235)
|
||||
|
||||
|
||||
## 1.3.2 - 11 July 2020
|
||||
|
||||
### Added
|
||||
|
||||
* Linux packaged apps now include a Makefile to aid install
|
||||
|
||||
### Changed
|
||||
|
||||
* Fyne package supports specific architectures for Android
|
||||
* Reset missing textures on refresh
|
||||
* Custom confirm callbacks now called on implicitly shown dialogs
|
||||
* SelectEntry can update drop-down list during OnChanged callback
|
||||
* TextGrid whitespace color now matches theme changes
|
||||
* Order of Window Resize(), SetFixedSize() and CenterOnScreen() does no matter before Show()
|
||||
* Containers now refresh their visuals as well as their Children on Refresh()
|
||||
|
||||
### Fixed
|
||||
|
||||
* Capped StrokeWidth on canvas.Line (#831)
|
||||
* Canvas lines, rectangles and circles do not resize and refresh correctly
|
||||
* Black flickering on resize on MacOS and OS X (possibly not on Catalina) (#1122)
|
||||
* Crash when resizing window under macOS (#1051, #1140)
|
||||
* Set SetFixedSize to true, the menus are overlapped (#1105)
|
||||
* Ctrl+v into text input field crashes app. Presumably clipboard is empty (#1123, #1132)
|
||||
* Slider default value doesn't stay inside range (#1128)
|
||||
* The position of window is changed when status change from show to hide, then to show (#1116)
|
||||
* Creating a windows inside onClose handler causes Fyne to panic (#1106)
|
||||
* Backspace in entry after SetText("") can crash (#1096)
|
||||
* Empty main menu causes panic (#1073)
|
||||
* Installing using `fyne install` on Linux now works on distrubutions that don't use `/usr/local`
|
||||
* Fix recommendations from staticcheck
|
||||
* Unable to overwrite file when using dialog.ShowFileSave (#1168)
|
||||
|
||||
|
||||
## 1.3 - 5 June 2020
|
||||
|
||||
### Added
|
||||
|
||||
* File open and save dialogs (#225)
|
||||
* Add notifications support (#398)
|
||||
* Add text wrap support (#332)
|
||||
* Add Accordion widget (#206)
|
||||
* Add TextGrid widget (#115)
|
||||
* Add SplitContainer widget (#205)
|
||||
* Add new URI type and handlers for cross-platform data access
|
||||
* Desktop apps can now create splash windows
|
||||
* Add ScaleMode to images, new ImageScalePixels feature for retro graphics
|
||||
* Allow widgets to influence mouse cursor style (#726)
|
||||
* Support changing the text on form submit/cancel buttons
|
||||
* Support reporting CapsLock key events (#552)
|
||||
* Add OnClosed callback for Dialog
|
||||
* Add new image test helpers for validating render output
|
||||
* Support showing different types of soft keyboard on mobile devices (#971, #975)
|
||||
|
||||
### Changed
|
||||
|
||||
* Upgraded underlying GLFW library to fix various issues (#183, #61)
|
||||
* Add submenu support and hover effects (#395)
|
||||
* Default to non-premultiplied alpha (NRGBA) across toolkit
|
||||
* Rename FixedGridLayout to GridWrapLayout (deprecate old API) (#836)
|
||||
* Windows redraw and animations continue on window resize and move
|
||||
* New...PopUp() methods are being replaced by Show...Popup() or New...Popup().Show()
|
||||
* Apps started on a goroutine will now panic as this is not supported
|
||||
* On Linux apps now simulate 120DPI instead of 96DPI
|
||||
* Improved fyne_settings scale picking user interface
|
||||
* Reorganised fyne_demo to accommodate growing collection of widgets and containers
|
||||
* Rendering now happens on a different thread to events for more consistent drawing
|
||||
* Improved text selection on mobile devices
|
||||
|
||||
### Fixed (highlights)
|
||||
|
||||
* Panic when trying to paste empty clipboard into entry (#743)
|
||||
* Scale does not match user configuration in Windows 10 (#635)
|
||||
* Copy/Paste not working on Entry Field in Windows OS (#981)
|
||||
* Select widgets with many options overflow UI without scrolling (#675)
|
||||
* android: typing in entry expands only after full refresh (#972)
|
||||
* iOS app stops re-drawing mid frame after a while (#950)
|
||||
* Too many successive GUI updates do not properly update the view (904)
|
||||
* iOS apps would not build using Apple's new certificates
|
||||
* Preserve aspect ratio in SVG stroke drawing (#976)
|
||||
* Fixed many race conditions in widget data handling
|
||||
* Various crashes and render glitches in extended widgets
|
||||
* Fix security issues reported by gosec (#742)
|
||||
|
||||
|
||||
## 1.2.4 - 13 April 2020
|
||||
|
||||
### Added
|
||||
|
||||
* Added Direction field to ScrollContainer and NewHScrollContainer, NewVScrollContainer constructors (#763)
|
||||
* Added Scroller.SetMinSize() to enable better defaults for scrolled content
|
||||
* Added "fyne vendor" subcommand to help packaging fyne dependencies in projects
|
||||
* Added "fyne version" subcommand to help with bug reporting (#656)
|
||||
* Clipboard (cut/copy/paste) is now supported on iOS and Android (#414)
|
||||
* Preferences.RemoveValue() now allows deletion of a stored user preference
|
||||
|
||||
### Changed
|
||||
|
||||
* Report keys based on name not key code - fixes issue with shortcuts with AZERTY (#790)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Mobile builds now support go modules (#660)
|
||||
* Building for mobile would try to run desktop build first
|
||||
* Mobile apps now draw the full safe area on a screen (#799)
|
||||
* Preferences were not stored on mobile apps (#779)
|
||||
* Window on Windows is not controllable after exiting FullScreen mode (#727)
|
||||
* Soft keyboard not working on some Samsung/LG smart phones (#787)
|
||||
* Selecting a tab on extended TabContainer doesn't refresh button (#810)
|
||||
* Appending tab to empty TabContainer causes divide by zero on mobile (#820)
|
||||
* Application crashes on startup (#816)
|
||||
* Form does not always update on theme change (#842)
|
||||
|
||||
|
||||
## 1.2.3 - 2 March 2020
|
||||
|
||||
### Added
|
||||
|
||||
* Add media and volume icons to default themes (#649)
|
||||
* Add Canvas.PixelCoordinateForPosition to find pixel locations if required
|
||||
* Add ProgressInfinite dialog
|
||||
|
||||
### Changed
|
||||
|
||||
* Warn if -executable or -sourceDir flags are used for package on mobile (#652)
|
||||
* Update scale based on device for mobile apps
|
||||
* Windows without a title will now be named "Fyne Application"
|
||||
* Revert fix to quit mobile apps - this is not allowed in guidelines
|
||||
|
||||
### Fixed
|
||||
|
||||
* App.UniqueID() did not return current app ID
|
||||
* Fyne package ignored -name flag for ios and android builds (#657)
|
||||
* Possible crash when appending tabs to TabContainer
|
||||
* FixedSize windows not rescaling when dragged between monitors (#654)
|
||||
* Fix issues where older Android devices may not background or rotate (#677)
|
||||
* Crash when setting theme before window content set (#688)
|
||||
* Correct form extend behaviour (#694)
|
||||
* Select drop-down width is wrong if the drop-down is too tall for the window (#706)
|
||||
|
||||
|
||||
## 1.2.2 - 29 January 2020
|
||||
|
||||
### Added
|
||||
|
||||
* Add SelectedText() function to Entry widget
|
||||
* New mobile.Device interface exposing ShowVirtualKeyboard() (and Hide...)
|
||||
|
||||
### Changed
|
||||
|
||||
* Scale calculations are now relative to system scale - the default "1" matches the system
|
||||
* Update scale on Linux to be "auto" by default (and numbers are relative to 96DPI standard) (#595)
|
||||
* When auto scaling check the monitor in the middle of the window, not top left
|
||||
* bundled files now have a standard header to optimise some tools like go report card
|
||||
* Shortcuts are now handled by the event queue - fixed possible deadlock
|
||||
|
||||
### Fixed
|
||||
|
||||
* Scroll horizontally when holding shift key (#579)
|
||||
* Updating text and calling refresh for widget doesn't work (#607)
|
||||
* Corrected visual behaviour of extended widgets including Entry, Select, Check, Radio and Icon (#615)
|
||||
* Entries and Selects that are extended would crash on right click.
|
||||
* PasswordEntry created from Entry with Password = true has no revealer
|
||||
* Dialog width not always sufficient for title
|
||||
* Pasting unicode characters could panic (#597)
|
||||
* Setting theme before application start panics on macOS (#626)
|
||||
* MenuItem type conflicts with other projects (#632)
|
||||
|
||||
|
||||
## 1.2.1 - 24 December 2019
|
||||
|
||||
### Added
|
||||
|
||||
* Add TouchDown, TouchUp and TouchCancel API in driver/mobile for device specific events
|
||||
* Add support for adding and removing tabs from a tab container (#444)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Issues when settings changes may not be monitored (#576)
|
||||
* Layout of hidden tab container contents on mobile (#578)
|
||||
* Mobile apps would not quit when Quit() was called (#580)
|
||||
* Shadows disappeared when theme changes (#589)
|
||||
* iOS apps could stop rendering after many refreshes (#584)
|
||||
* Fyne package could fail on Windows (#586)
|
||||
* Horizontal only scroll container may not refresh using scroll wheel
|
||||
|
||||
|
||||
## 1.2 - 12 December 2019
|
||||
|
||||
### Added
|
||||
|
||||
* Mobile support - iOS and Android, including "fyne package" command
|
||||
* Support for OpenGL ES and embedded linux
|
||||
* New BaseWidget for building custom widgets
|
||||
* Support for diagonal gradients
|
||||
* Global settings are now saved and can be set using the new fyne_settings app
|
||||
* Support rendering in Go playground using playground.Render() helpers
|
||||
* "fyne install" command to package and install apps on the local computer
|
||||
* Add horizontal scrolling to ScrollContainer
|
||||
* Add preferences API
|
||||
* Add show/hide password icon when created from NewPasswordEntry
|
||||
* Add NewGridLayoutWithRows to specify a grid layout with a set number of rows
|
||||
* Add NewAdaptiveGridLayout which uses a column grid layout when horizontal and rows in vertical
|
||||
|
||||
|
||||
### Changed
|
||||
|
||||
* New Logo! Thanks to Storm for his work on this :)
|
||||
* Applications no longer have a default (Fyne logo) icon
|
||||
* Input events now execute one at a time to maintain the correct order
|
||||
* Button and other widget callbacks no longer launch new goroutines
|
||||
* FYNE_THEME and FYNE_SCALE are now overrides to the global configuration
|
||||
* The first opened window no longer exits the app when closed (unless none others are open or Window.SetMaster() is called)
|
||||
* "fyne package" now defaults icon to "Icon.png" so the parameter is optional
|
||||
* Calling ExtendBaseWidget() sets up the renderer for extended widgets
|
||||
* Entry widget now has a visible Disabled state, ReadOnly has been deprecated
|
||||
* Bundled images optimised to save space
|
||||
* Optimise rendering to reduce refresh on TabContainer and ScrollContainer
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
* Correct the color of Entry widget cursor if theme changes
|
||||
* Error where widgets created before main() function could crash (#490)
|
||||
* App.Run panics if called without a window (#527)
|
||||
* Support context menu for disabled entry widgets (#488)
|
||||
* Fix issue where images using fyne.ImageFillOriginal may not show initially (#558)
|
||||
|
||||
|
||||
## 1.1.2 - 12 October 2019
|
||||
|
||||
### Added
|
||||
|
||||
### Changed
|
||||
|
||||
* Default scale value for canvases is now 1.0 instead of Auto (DPI based)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Correct icon name in linux packages
|
||||
* Fullscreen before showing a window works again
|
||||
* Incorrect MinSize of FixedGrid layout in some situations
|
||||
* Update text size on theme change
|
||||
* Text handling crashes (#411, #484, #485)
|
||||
* Layout of image only buttons
|
||||
* TabItem.Content changes are reflected when refreshing TabContainer (#456)
|
||||
|
||||
## 1.1.1 - 17 August 2019
|
||||
|
||||
### Added
|
||||
|
||||
* Add support for custom Windows manifest files in fyne package
|
||||
|
||||
### Changed
|
||||
|
||||
* Dismiss non-modal popovers on secondary tap
|
||||
* Only measure visible objects in layouts and minSize calculations (#343)
|
||||
* Don't propagate show/hide in the model - allowing children of tabs to remain hidden
|
||||
* Disable cut/copy for password fields
|
||||
* Correctly calculate grid layout minsize as width changes
|
||||
* Select text at end of line when double tapping beyond width
|
||||
|
||||
### Fixed
|
||||
|
||||
* Scale could be too large on macOS Retina screens
|
||||
* Window with fixed size changes size when un-minimized on Windows (#300)
|
||||
* Setting text on a label could crash if it was not yet shown (#381)
|
||||
* Multiple Entry widgets could have selections simultaneously (#341)
|
||||
* Hover effect of radio widget too low (#383)
|
||||
* Missing shadow on Select widget
|
||||
* Incorrect rendering of subimages within Image object
|
||||
* Size calculation caches could be skipped causing degraded performance
|
||||
|
||||
|
||||
## 1.1 - 1 July 2019
|
||||
|
||||
### Added
|
||||
|
||||
* Menubar and PopUpMenu (#41)
|
||||
* PopUp widgets (regular and modal) and canvas overlay support (#242)
|
||||
* Add gradient (linear and radial) to canvas
|
||||
* Add shadow support for overlays, buttons and scrollcontainer
|
||||
* Text can now be selected (#67)
|
||||
* Support moving through inputs with Tab / Shift-Tab (#82)
|
||||
* canvas.Capture() to save the content of a canvas
|
||||
* Horizontal layout for widget.Radio
|
||||
* Select widget (#21)
|
||||
* Add support for disabling widgets (#234)
|
||||
* Support for changing icon color (#246)
|
||||
* Button hover effect
|
||||
* Pointer drag event to main API
|
||||
* support for desktop mouse move events
|
||||
* Add a new "hints" build tag that can suggest UI improvements
|
||||
|
||||
### Changed
|
||||
|
||||
* TabContainer tab location can now be set with SetTabLocation()
|
||||
* Dialog windows now appear as modal popups within a window
|
||||
* Don't add a button bar to a form if it has no buttons
|
||||
* Moved driver/gl package to internal/driver/gl
|
||||
* Clicking/Tapping in an entry will position the cursor
|
||||
* A container with no layout will not change the position or size of it's content
|
||||
* Update the fyne_demo app to reflect the expanding feature set
|
||||
|
||||
### Fixed
|
||||
|
||||
* Allow scrollbars to be dragged (#133)
|
||||
* Unicode char input with Option key on macOS (#247)
|
||||
* Resizng fixed size windows (#248)
|
||||
* Fixed various bugs in window sizing and padding
|
||||
* Button icons do not center align if label is empty (#284)
|
||||
|
||||
|
||||
## 1.0.1 - 20 April 2019
|
||||
|
||||
### Added
|
||||
|
||||
* Support for go modules
|
||||
* Transparent backgrounds for widgets
|
||||
* Entry.OnCursorChanged()
|
||||
* Radio.Append() and Radio.SetSelected() (#229)
|
||||
|
||||
### Changed
|
||||
|
||||
* Clicking outside a focused element will unfocus it
|
||||
* Handle key repeat for non-runes (#165)
|
||||
|
||||
### Fixed
|
||||
|
||||
* Remove duplicate options from a Radio widget (#230)
|
||||
* Issue where paste shortcut is not called for Ctrl-V keyboard combination
|
||||
* Cursor position when clearing text in Entry (#214)
|
||||
* Antialias of lines and circles (fyne-io/examples#14)
|
||||
* Crash on centering of windows (#220)
|
||||
* Possible crash when closing secondary windows
|
||||
* Possible crash when showing dialog
|
||||
* Initial visibility of scroll bar in ScrollContainer
|
||||
* Setting window icon when different from app icon.
|
||||
* Possible panic on app.Quit() (#175)
|
||||
* Various caches and race condition issues (#194, #217, #209).
|
||||
|
||||
|
||||
## 1.0 - 19 March 2019
|
||||
|
||||
The first major release of the Fyne toolkit delivers a stable release of the
|
||||
main functionality required to build basic GUI applications across multiple
|
||||
platforms.
|
||||
|
||||
### Features
|
||||
|
||||
* Canvas API (rect, line, circle, text, image)
|
||||
* Widget API (box, button, check, entry, form, group, hyperlink, icon, label, progress bar, radio, scroller, tabs and toolbar)
|
||||
* Light and dark themes
|
||||
* Pointer, key and shortcut APIs (generic and desktop extension)
|
||||
* OpenGL driver for Linux, macOS and Windows
|
||||
* Tools for embedding data and packaging releases
|
||||
|
76
vendor/fyne.io/fyne/v2/CODE_OF_CONDUCT.md
generated
vendored
76
vendor/fyne.io/fyne/v2/CODE_OF_CONDUCT.md
generated
vendored
@@ -1,76 +0,0 @@
|
||||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at info@fyne.io. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
61
vendor/fyne.io/fyne/v2/CONTRIBUTING.md
generated
vendored
61
vendor/fyne.io/fyne/v2/CONTRIBUTING.md
generated
vendored
@@ -1,61 +0,0 @@
|
||||
Thanks very much for your interest in contributing to Fyne!
|
||||
The community is what makes this project successful and we are glad to welcome you on board.
|
||||
|
||||
There are various ways to contribute, perhaps the following helps you know how to get started.
|
||||
|
||||
## Reporting a bug
|
||||
|
||||
If you've found something wrong we want to know about it, please help us understand the problem so we can resolve it.
|
||||
|
||||
1. Check to see if this already is recorded, if so add some more information [issue list](https://github.com/fyne-io/fyne/issues)
|
||||
2. If not then create a new issue using the [bug report template](https://github.com/fyne-io/fyne/issues/new?assignees=&labels=&template=bug_report.md&title=)
|
||||
3. Stay involved in the conversation on the issue as it is triaged and progressed.
|
||||
|
||||
|
||||
## Fixing an issue
|
||||
|
||||
Great! You found an issue and figured you can fix it for us.
|
||||
If you can follow these steps then your code should get accepted fast.
|
||||
|
||||
1. Read through the "Contributing Code" section further down this page.
|
||||
2. Write a unit test to show it is broken.
|
||||
3. Create the fix and you should see the test passes.
|
||||
4. Run the tests and make sure everything still works as expected using `go test ./...`.
|
||||
5. [Open a PR](https://github.com/fyne-io/fyne/compare) and work through the review checklist.
|
||||
|
||||
|
||||
## Adding a feature
|
||||
|
||||
It's always good news to hear that people want to contribute functionality.
|
||||
But first of all check that it fits within our [Vision](https://github.com/fyne-io/fyne/wiki/Vision) and if we are already considering it on our [Roadmap](https://github.com/fyne-io/fyne/wiki/Roadmap).
|
||||
If you're not sure then you should join our #fyne-contributors channel on the [Gophers Slack server](https://gophers.slack.com/app_redirect?channel=fyne-contributors).
|
||||
|
||||
Once you are ready to code then the following steps should give you a smooth process:
|
||||
|
||||
1. Read through the [Contributing Code](#contributing-code) section further down this page.
|
||||
2. Think about how you would structure your code and how it can be tested.
|
||||
3. Write some code and enjoy the ease of writing Go code for even a complex project :).
|
||||
4. Run the tests and make sure everything still works as expected using `go test ./...`.
|
||||
5. [Open a PR](https://github.com/fyne-io/fyne/compare) and work through the review checklist.
|
||||
|
||||
|
||||
# Contributing Code
|
||||
|
||||
We aim to maintain a very high standard of code, through design, test and implementation.
|
||||
To manage this we have various checks and processes in place that everyone should follow, including:
|
||||
|
||||
* We use the Go standard format (with tabs not spaces) - you can run `gofmt` before committing
|
||||
* Imports should be ordered according to the GoImports spec - you can use the `goimports` tool instead of `gofmt`.
|
||||
* Everything should have a unit test attached (as much as possible, to keep our coverage up)
|
||||
|
||||
# Decision Process
|
||||
|
||||
The following points apply to our decision making process:
|
||||
|
||||
* Any decisions or votes will be opened on the #fyne-contributors channel and follows lazy consensus.
|
||||
* Any contributors not responding in 4 days will be deemed in agreement.
|
||||
* Any PR that has not been responded to within 7 days can be automatically approved.
|
||||
* No functionality will be added unless at least 2 developers agree it belongs.
|
||||
|
||||
Bear in mind that this is a cross platform project so any new features would normally
|
||||
be required to work on multiple desktop and mobile platforms.
|
28
vendor/fyne.io/fyne/v2/LICENSE
generated
vendored
28
vendor/fyne.io/fyne/v2/LICENSE
generated
vendored
@@ -1,28 +0,0 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (C) 2018 Fyne.io developers (see AUTHORS)
|
||||
All rights reserved.
|
||||
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
* Neither the name of Fyne.io nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
|
||||
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
185
vendor/fyne.io/fyne/v2/README.md
generated
vendored
185
vendor/fyne.io/fyne/v2/README.md
generated
vendored
@@ -1,185 +0,0 @@
|
||||
<p align="center">
|
||||
<a href="https://pkg.go.dev/fyne.io/fyne/v2?tab=doc" title="Go API Reference" rel="nofollow"><img src="https://img.shields.io/badge/go-documentation-blue.svg?style=flat" alt="Go API Reference"></a>
|
||||
<a href="https://img.shields.io/github/v/release/fyne-io/fyne?include_prereleases" title="Latest Release" rel="nofollow"><img src="https://img.shields.io/github/v/release/fyne-io/fyne?include_prereleases" alt="Latest Release"></a>
|
||||
<a href='http://gophers.slack.com/messages/fyne'><img src='https://img.shields.io/badge/join-us%20on%20slack-gray.svg?longCache=true&logo=slack&colorB=blue' alt='Join us on Slack' /></a>
|
||||
<br />
|
||||
<a href="https://goreportcard.com/report/fyne.io/fyne/v2"><img src="https://goreportcard.com/badge/fyne.io/fyne/v2" alt="Code Status" /></a>
|
||||
<a href="https://github.com/fyne-io/fyne/actions"><img src="https://github.com/fyne-io/fyne/workflows/Platform%20Tests/badge.svg" alt="Build Status" /></a>
|
||||
<a href='https://coveralls.io/github/fyne-io/fyne?branch=develop'><img src='https://coveralls.io/repos/github/fyne-io/fyne/badge.svg?branch=develop' alt='Coverage Status' /></a>
|
||||
</p>
|
||||
|
||||
# About
|
||||
|
||||
[Fyne](https://fyne.io) is an easy-to-use UI toolkit and app API written in Go.
|
||||
It is designed to build applications that run on desktop and mobile devices with a
|
||||
single codebase.
|
||||
|
||||
Version 2.1 is the current release of the Fyne API, it introduced RichText
|
||||
and the DocTabs container, as well as the document storage API and FyneApp.toml
|
||||
metadata support.
|
||||
We are now working towards the next big release, codenamed
|
||||
[bowmore](https://github.com/fyne-io/fyne/milestone/15)
|
||||
and more news will follow in our news feeds and GitHub project.
|
||||
|
||||
# Prerequisites
|
||||
|
||||
To develop apps using Fyne you will need Go version 1.14 or later, a C compiler and your system's development tools.
|
||||
If you're not sure if that's all installed or you don't know how then check out our
|
||||
[Getting Started](https://fyne.io/develop/) document.
|
||||
|
||||
Using the standard go tools you can install Fyne's core library using:
|
||||
|
||||
$ go get fyne.io/fyne/v2
|
||||
|
||||
# Widget demo
|
||||
|
||||
To run a showcase of the features of Fyne execute the following:
|
||||
|
||||
$ go get fyne.io/fyne/v2/cmd/fyne_demo/
|
||||
$ fyne_demo
|
||||
|
||||
And you should see something like this (after you click a few buttons):
|
||||
|
||||
<p align="center" markdown="1" style="max-width: 100%">
|
||||
<img src="img/widgets-dark.png" width="752" height="617" alt="Fyne Demo Dark Theme" style="max-width: 100%" />
|
||||
</p>
|
||||
|
||||
Or if you are using the light theme:
|
||||
|
||||
<p align="center" markdown="1" style="max-width: 100%">
|
||||
<img src="img/widgets-light.png" width="752" height="617" alt="Fyne Demo Light Theme" style="max-width: 100%" />
|
||||
</p>
|
||||
|
||||
And even running on a mobile device:
|
||||
|
||||
<p align="center" markdown="1" style="max-width: 100%">
|
||||
<img src="img/widgets-mobile-light.png" width="348" height="617" alt="Fyne Demo Mobile Light Theme" style="max-width: 100%" />
|
||||
</p>
|
||||
|
||||
# Getting Started
|
||||
|
||||
Fyne is designed to be really easy to code with.
|
||||
If you have followed the prerequisite steps above then all you need is a
|
||||
Go IDE (or a text editor).
|
||||
|
||||
Open a new file and you're ready to write your first app!
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func main() {
|
||||
a := app.New()
|
||||
w := a.NewWindow("Hello")
|
||||
|
||||
hello := widget.NewLabel("Hello Fyne!")
|
||||
w.SetContent(container.NewVBox(
|
||||
hello,
|
||||
widget.NewButton("Hi!", func() {
|
||||
hello.SetText("Welcome :)")
|
||||
}),
|
||||
))
|
||||
|
||||
w.ShowAndRun()
|
||||
}
|
||||
```
|
||||
|
||||
And you can run that simply as:
|
||||
|
||||
$ go run main.go
|
||||
|
||||
It should look like this:
|
||||
|
||||
<div align="center">
|
||||
<table cellpadding="0" cellspacing="0" style="margin: auto; border-collapse: collapse;">
|
||||
<tr style="border: none;"><td style="border: none;">
|
||||
<img src="img/hello-light.png" width="207" height="212" alt="Fyne Hello Dark Theme" />
|
||||
</td><td style="border: none;">
|
||||
<img src="img/hello-dark.png" width="207" height="212" alt="Fyne Hello Dark Theme" />
|
||||
</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
> Note that Windows applications load from a command prompt by default, which means if you click an icon you may see a command window.
|
||||
> To fix this add the parameters `-ldflags -H=windowsgui` to your run or build commands.
|
||||
|
||||
## Run in mobile simulation
|
||||
|
||||
There is a helpful mobile simulation mode that gives a hint of how your app would work on a mobile device:
|
||||
|
||||
$ go run -tags mobile main.go
|
||||
|
||||
Another option is to use `fyne` command, see [Packaging for mobile](#packaging-for-mobile).
|
||||
|
||||
# Installing
|
||||
|
||||
Using `go install` will copy the executable into your go `bin` dir.
|
||||
To install the application with icons etc into your operating system's standard
|
||||
application location you can use the fyne utility and the "install" subcommand.
|
||||
|
||||
$ go get fyne.io/fyne/v2/cmd/fyne
|
||||
$ fyne install
|
||||
|
||||
# Packaging for mobile
|
||||
|
||||
To run on a mobile device it is necessary to package up the application.
|
||||
To do this we can use the fyne utility "package" subcommand.
|
||||
You will need to add appropriate parameters as prompted, but the basic command is shown below.
|
||||
Once packaged you can install using the platform development tools or the fyne "install" subcommand.
|
||||
|
||||
$ fyne package -os android -appID my.domain.appname
|
||||
$ fyne install -os android
|
||||
|
||||
The built Android application can run either in a real device or an Android emulator.
|
||||
However, building for iOS is slightly different.
|
||||
If the "-os" argument is "ios", it is build only for a real iOS device.
|
||||
Specify "-os" to "iossimulator" allows the application be able to run in an iOS simulator:
|
||||
|
||||
$ fyne package -os ios -appID my.domain.appname
|
||||
$ fyne package -os iossimulator -appID my.domain.appname
|
||||
|
||||
# Preparing a release
|
||||
|
||||
Using the fyne utility "release" subcommand you can package up your app for release
|
||||
to app stores and market places. Make sure you have the standard build tools installed
|
||||
and have followed the platform documentation for setting up accounts and signing.
|
||||
Then you can execute something like the following, notice the `-os ios` parameter allows
|
||||
building an iOS app from macOS computer. Other combinations work as well :)
|
||||
|
||||
$ fyne release -os ios -certificate "Apple Distribution" -profile "My App Distribution" -appID "com.example.myapp"
|
||||
|
||||
The above command will create a '.ipa' file that can then be uploaded to the iOS App Store.
|
||||
|
||||
# Documentation
|
||||
|
||||
More documentation is available at the [Fyne developer website](https://developer.fyne.io/) or on [pkg.go.dev](https://pkg.go.dev/fyne.io/fyne/v2?tab=doc).
|
||||
|
||||
# Examples
|
||||
|
||||
You can find many example applications in the [examples repository](https://github.com/fyne-io/examples/).
|
||||
Alternatively a list of applications using fyne can be found at [our website](https://apps.fyne.io/).
|
||||
|
||||
# Shipping the Fyne Toolkit
|
||||
|
||||
All Fyne apps will work without pre-installed libraries, this is one reason the apps are so portable.
|
||||
However, if looking to support Fyne in a bigger way on your operating system then you can install some utilities that help to make a more complete experience.
|
||||
|
||||
## Additional apps
|
||||
|
||||
It is recommended that you install the following additional apps:
|
||||
|
||||
| app | go get | description |
|
||||
| --- | ------ | ----------- |
|
||||
| fyne_settings | `fyne.io/fyne/v2/cmd/fyne_settings` | A GUI for managing your global Fyne settings like theme and scaling |
|
||||
| apps | `github.com/fyne-io/apps` | A graphical installer for the Fyne apps listed at https://apps.fyne.io |
|
||||
|
||||
These are optional applications but can help to create a more complete desktop experience.
|
||||
|
||||
## FyneDesk (Linux / BSD)
|
||||
|
||||
To go all the way with Fyne on your desktop / laptop computer you could install [FyneDesk](https://github.com/fyne-io/fynedesk) as well :)
|
84
vendor/fyne.io/fyne/v2/animation.go
generated
vendored
84
vendor/fyne.io/fyne/v2/animation.go
generated
vendored
@@ -1,84 +0,0 @@
|
||||
package fyne
|
||||
|
||||
import "time"
|
||||
|
||||
// AnimationCurve represents an animation algorithm for calculating the progress through a timeline.
|
||||
// Custom animations can be provided by implementing the "func(float32) float32" definition.
|
||||
// The input parameter will start at 0.0 when an animation starts and travel up to 1.0 at which point it will end.
|
||||
// A linear animation would return the same output value as is passed in.
|
||||
type AnimationCurve func(float32) float32
|
||||
|
||||
// AnimationRepeatForever is an AnimationCount value that indicates it should not stop looping.
|
||||
//
|
||||
// Since: 2.0
|
||||
const AnimationRepeatForever = -1
|
||||
|
||||
var (
|
||||
// AnimationEaseInOut is the default easing, it starts slowly, accelerates to the middle and slows to the end.
|
||||
//
|
||||
// Since: 2.0
|
||||
AnimationEaseInOut = animationEaseInOut
|
||||
// AnimationEaseIn starts slowly and accelerates to the end.
|
||||
//
|
||||
// Since: 2.0
|
||||
AnimationEaseIn = animationEaseIn
|
||||
// AnimationEaseOut starts at speed and slows to the end.
|
||||
//
|
||||
// Since: 2.0
|
||||
AnimationEaseOut = animationEaseOut
|
||||
// AnimationLinear is a linear mapping for animations that progress uniformly through their duration.
|
||||
//
|
||||
// Since: 2.0
|
||||
AnimationLinear = animationLinear
|
||||
)
|
||||
|
||||
// Animation represents an animated element within a Fyne canvas.
|
||||
// These animations may control individual objects or entire scenes.
|
||||
//
|
||||
// Since: 2.0
|
||||
type Animation struct {
|
||||
AutoReverse bool
|
||||
Curve AnimationCurve
|
||||
Duration time.Duration
|
||||
RepeatCount int
|
||||
Tick func(float32)
|
||||
}
|
||||
|
||||
// NewAnimation creates a very basic animation where the callback function will be called for every
|
||||
// rendered frame between time.Now() and the specified duration. The callback values start at 0.0 and
|
||||
// will be 1.0 when the animation completes.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewAnimation(d time.Duration, fn func(float32)) *Animation {
|
||||
return &Animation{Duration: d, Tick: fn}
|
||||
}
|
||||
|
||||
// Start registers the animation with the application run-loop and starts its execution.
|
||||
func (a *Animation) Start() {
|
||||
CurrentApp().Driver().StartAnimation(a)
|
||||
}
|
||||
|
||||
// Stop will end this animation and remove it from the run-loop.
|
||||
func (a *Animation) Stop() {
|
||||
CurrentApp().Driver().StopAnimation(a)
|
||||
}
|
||||
|
||||
func animationEaseIn(val float32) float32 {
|
||||
return val * val
|
||||
}
|
||||
|
||||
func animationEaseInOut(val float32) float32 {
|
||||
if val <= 0.5 {
|
||||
return val * val * 2
|
||||
}
|
||||
|
||||
return -1 + (4-val*2)*val
|
||||
}
|
||||
|
||||
func animationEaseOut(val float32) float32 {
|
||||
return val * (2 - val)
|
||||
}
|
||||
|
||||
func animationLinear(val float32) float32 {
|
||||
return val
|
||||
}
|
126
vendor/fyne.io/fyne/v2/app.go
generated
vendored
126
vendor/fyne.io/fyne/v2/app.go
generated
vendored
@@ -1,126 +0,0 @@
|
||||
package fyne
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
// An App is the definition of a graphical application.
|
||||
// Apps can have multiple windows, by default they will exit when all windows
|
||||
// have been closed. This can be modified using SetMaster() or SetCloseIntercept().
|
||||
// To start an application you need to call Run() somewhere in your main() function.
|
||||
// Alternatively use the window.ShowAndRun() function for your main window.
|
||||
type App interface {
|
||||
// Create a new window for the application.
|
||||
// The first window to open is considered the "master" and when closed
|
||||
// the application will exit.
|
||||
NewWindow(title string) Window
|
||||
|
||||
// Open a URL in the default browser application.
|
||||
OpenURL(url *url.URL) error
|
||||
|
||||
// Icon returns the application icon, this is used in various ways
|
||||
// depending on operating system.
|
||||
// This is also the default icon for new windows.
|
||||
Icon() Resource
|
||||
|
||||
// SetIcon sets the icon resource used for this application instance.
|
||||
SetIcon(Resource)
|
||||
|
||||
// Run the application - this starts the event loop and waits until Quit()
|
||||
// is called or the last window closes.
|
||||
// This should be called near the end of a main() function as it will block.
|
||||
Run()
|
||||
|
||||
// Calling Quit on the application will cause the application to exit
|
||||
// cleanly, closing all open windows.
|
||||
// This function does no thing on a mobile device as the application lifecycle is
|
||||
// managed by the operating system.
|
||||
Quit()
|
||||
|
||||
// Driver returns the driver that is rendering this application.
|
||||
// Typically not needed for day to day work, mostly internal functionality.
|
||||
Driver() Driver
|
||||
|
||||
// UniqueID returns the application unique identifier, if set.
|
||||
// This must be set for use of the Preferences() functions... see NewWithId(string)
|
||||
UniqueID() string
|
||||
|
||||
// SendNotification sends a system notification that will be displayed in the operating system's notification area.
|
||||
SendNotification(*Notification)
|
||||
|
||||
// Settings return the globally set settings, determining theme and so on.
|
||||
Settings() Settings
|
||||
|
||||
// Preferences returns the application preferences, used for storing configuration and state
|
||||
Preferences() Preferences
|
||||
|
||||
// Storage returns a storage handler specific to this application.
|
||||
Storage() Storage
|
||||
|
||||
// Lifecycle returns a type that allows apps to hook in to lifecycle events.
|
||||
//
|
||||
// Since: 2.1
|
||||
Lifecycle() Lifecycle
|
||||
|
||||
// Metadata returns the application metadata that was set at compile time.
|
||||
//
|
||||
// Since: 2.2
|
||||
Metadata() AppMetadata
|
||||
}
|
||||
|
||||
// app contains an App variable, but due to atomic.Value restrictions on
|
||||
// interfaces we need to use an indirect type, i.e. appContainer.
|
||||
var app atomic.Value // appContainer
|
||||
|
||||
// appContainer is a dummy container that holds an App instance. This
|
||||
// struct exists to guarantee that atomic.Value can store objects with
|
||||
// same type.
|
||||
type appContainer struct {
|
||||
current App
|
||||
}
|
||||
|
||||
// SetCurrentApp is an internal function to set the app instance currently running.
|
||||
func SetCurrentApp(current App) {
|
||||
app.Store(appContainer{current})
|
||||
}
|
||||
|
||||
// CurrentApp returns the current application, for which there is only 1 per process.
|
||||
func CurrentApp() App {
|
||||
val := app.Load()
|
||||
if val == nil {
|
||||
LogError("Attempt to access current Fyne app when none is started", nil)
|
||||
return nil
|
||||
}
|
||||
return (val).(appContainer).current
|
||||
}
|
||||
|
||||
// AppMetadata captures the build metadata for an application.
|
||||
//
|
||||
// Since: 2.2
|
||||
type AppMetadata struct {
|
||||
// ID is the unique ID of this application, used by many distribution platforms.
|
||||
ID string
|
||||
// Name is the human friendly name of this app.
|
||||
Name string
|
||||
// Version represents the version of this application, normally following semantic versioning.
|
||||
Version string
|
||||
// Build is the build number of this app, some times appended to the version number.
|
||||
Build int
|
||||
// Icon contains, if present, a resource of the icon that was bundled at build time.
|
||||
Icon Resource
|
||||
}
|
||||
|
||||
// Lifecycle represents the various phases that an app can transition through.
|
||||
//
|
||||
// Since: 2.1
|
||||
type Lifecycle interface {
|
||||
// SetOnEnteredForeground hooks into the app becoming foreground and gaining focus.
|
||||
SetOnEnteredForeground(func())
|
||||
// SetOnExitedForeground hooks into the app losing input focus and going into the background.
|
||||
SetOnExitedForeground(func())
|
||||
// SetOnStarted hooks into an event that says the app is now running.
|
||||
SetOnStarted(func())
|
||||
// SetOnStopped hooks into an event that says the app is no longer running.
|
||||
SetOnStopped(func())
|
||||
}
|
147
vendor/fyne.io/fyne/v2/app/app.go
generated
vendored
147
vendor/fyne.io/fyne/v2/app/app.go
generated
vendored
@@ -1,147 +0,0 @@
|
||||
// Package app provides app implementations for working with Fyne graphical interfaces.
|
||||
// The fastest way to get started is to call app.New() which will normally load a new desktop application.
|
||||
// If the "ci" tag is passed to go (go run -tags ci myapp.go) it will run an in-memory application.
|
||||
package app // import "fyne.io/fyne/v2/app"
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal"
|
||||
"fyne.io/fyne/v2/internal/app"
|
||||
intRepo "fyne.io/fyne/v2/internal/repository"
|
||||
"fyne.io/fyne/v2/storage/repository"
|
||||
|
||||
"golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
// Declare conformity with App interface
|
||||
var _ fyne.App = (*fyneApp)(nil)
|
||||
|
||||
type fyneApp struct {
|
||||
driver fyne.Driver
|
||||
icon fyne.Resource
|
||||
uniqueID string
|
||||
|
||||
lifecycle fyne.Lifecycle
|
||||
settings *settings
|
||||
storage *store
|
||||
prefs fyne.Preferences
|
||||
|
||||
running uint32 // atomic, 1 == running, 0 == stopped
|
||||
exec func(name string, arg ...string) *execabs.Cmd
|
||||
}
|
||||
|
||||
func (a *fyneApp) Icon() fyne.Resource {
|
||||
if a.icon != nil {
|
||||
return a.icon
|
||||
}
|
||||
|
||||
return a.Metadata().Icon
|
||||
}
|
||||
|
||||
func (a *fyneApp) SetIcon(icon fyne.Resource) {
|
||||
a.icon = icon
|
||||
}
|
||||
|
||||
func (a *fyneApp) UniqueID() string {
|
||||
if a.uniqueID != "" {
|
||||
return a.uniqueID
|
||||
}
|
||||
if a.Metadata().ID != "" {
|
||||
return a.Metadata().ID
|
||||
}
|
||||
|
||||
fyne.LogError("Preferences API requires a unique ID, use app.NewWithID() or the FyneApp.toml ID field", nil)
|
||||
a.uniqueID = "missing-id-" + strconv.FormatInt(time.Now().Unix(), 10) // This is a fake unique - it just has to not be reused...
|
||||
return a.uniqueID
|
||||
}
|
||||
|
||||
func (a *fyneApp) NewWindow(title string) fyne.Window {
|
||||
return a.driver.CreateWindow(title)
|
||||
}
|
||||
|
||||
func (a *fyneApp) Run() {
|
||||
if atomic.CompareAndSwapUint32(&a.running, 0, 1) {
|
||||
a.driver.Run()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (a *fyneApp) Quit() {
|
||||
for _, window := range a.driver.AllWindows() {
|
||||
window.Close()
|
||||
}
|
||||
|
||||
a.driver.Quit()
|
||||
a.settings.stopWatching()
|
||||
atomic.StoreUint32(&a.running, 0)
|
||||
}
|
||||
|
||||
func (a *fyneApp) Driver() fyne.Driver {
|
||||
return a.driver
|
||||
}
|
||||
|
||||
// Settings returns the application settings currently configured.
|
||||
func (a *fyneApp) Settings() fyne.Settings {
|
||||
return a.settings
|
||||
}
|
||||
|
||||
func (a *fyneApp) Storage() fyne.Storage {
|
||||
return a.storage
|
||||
}
|
||||
|
||||
func (a *fyneApp) Preferences() fyne.Preferences {
|
||||
if a.UniqueID() == "" {
|
||||
fyne.LogError("Preferences API requires a unique ID, use app.NewWithID() or the FyneApp.toml ID field", nil)
|
||||
}
|
||||
return a.prefs
|
||||
}
|
||||
|
||||
func (a *fyneApp) Lifecycle() fyne.Lifecycle {
|
||||
return a.lifecycle
|
||||
}
|
||||
|
||||
// New returns a new application instance with the default driver and no unique ID (unless specified in FyneApp.toml)
|
||||
func New() fyne.App {
|
||||
if meta.ID == "" {
|
||||
internal.LogHint("Applications should be created with a unique ID using app.NewWithID()")
|
||||
}
|
||||
return NewWithID(meta.ID)
|
||||
}
|
||||
|
||||
func newAppWithDriver(d fyne.Driver, id string) fyne.App {
|
||||
newApp := &fyneApp{uniqueID: id, driver: d, exec: execabs.Command, lifecycle: &app.Lifecycle{}}
|
||||
fyne.SetCurrentApp(newApp)
|
||||
newApp.settings = loadSettings()
|
||||
|
||||
newApp.prefs = newPreferences(newApp)
|
||||
newApp.storage = &store{a: newApp}
|
||||
if id != "" {
|
||||
if pref, ok := newApp.prefs.(interface{ load() }); ok {
|
||||
pref.load()
|
||||
}
|
||||
|
||||
root, _ := newApp.storage.docRootURI()
|
||||
newApp.storage.Docs = &internal.Docs{RootDocURI: root}
|
||||
} else {
|
||||
newApp.storage.Docs = &internal.Docs{} // an empty impl to avoid crashes
|
||||
}
|
||||
|
||||
if !d.Device().IsMobile() {
|
||||
newApp.settings.watchSettings()
|
||||
}
|
||||
|
||||
repository.Register("http", intRepo.NewHTTPRepository())
|
||||
repository.Register("https", intRepo.NewHTTPRepository())
|
||||
|
||||
return newApp
|
||||
}
|
||||
|
||||
// marker interface to pass system tray to supporting drivers
|
||||
type systrayDriver interface {
|
||||
SetSystemTrayMenu(*fyne.Menu)
|
||||
SetSystemTrayIcon(resource fyne.Resource)
|
||||
}
|
74
vendor/fyne.io/fyne/v2/app/app_darwin.go
generated
vendored
74
vendor/fyne.io/fyne/v2/app/app_darwin.go
generated
vendored
@@ -1,74 +0,0 @@
|
||||
//go:build !ci && !js && !wasm && !test_web_driver
|
||||
// +build !ci,!js,!wasm,!test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
bool isBundled();
|
||||
void sendNotification(char *title, char *content);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
func (a *fyneApp) SendNotification(n *fyne.Notification) {
|
||||
if C.isBundled() {
|
||||
titleStr := C.CString(n.Title)
|
||||
defer C.free(unsafe.Pointer(titleStr))
|
||||
contentStr := C.CString(n.Content)
|
||||
defer C.free(unsafe.Pointer(contentStr))
|
||||
|
||||
C.sendNotification(titleStr, contentStr)
|
||||
return
|
||||
}
|
||||
|
||||
fallbackNotification(n.Title, n.Content)
|
||||
}
|
||||
|
||||
// SetSystemTrayMenu creates a system tray item and attaches the specified menu.
|
||||
// By default this will use the application icon.
|
||||
func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) {
|
||||
if desk, ok := a.Driver().(systrayDriver); ok {
|
||||
desk.SetSystemTrayMenu(menu)
|
||||
}
|
||||
}
|
||||
|
||||
// SetSystemTrayIcon sets a custom image for the system tray icon.
|
||||
// You should have previously called `SetSystemTrayMenu` to initialise the menu icon.
|
||||
func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) {
|
||||
a.Driver().(systrayDriver).SetSystemTrayIcon(icon)
|
||||
}
|
||||
|
||||
func escapeNotificationString(in string) string {
|
||||
noSlash := strings.ReplaceAll(in, "\\", "\\\\")
|
||||
return strings.ReplaceAll(noSlash, "\"", "\\\"")
|
||||
}
|
||||
|
||||
//export fallbackSend
|
||||
func fallbackSend(cTitle, cContent *C.char) {
|
||||
title := C.GoString(cTitle)
|
||||
content := C.GoString(cContent)
|
||||
fallbackNotification(title, content)
|
||||
}
|
||||
|
||||
func fallbackNotification(title, content string) {
|
||||
template := `display notification "%s" with title "%s"`
|
||||
script := fmt.Sprintf(template, escapeNotificationString(content), escapeNotificationString(title))
|
||||
|
||||
err := execabs.Command("osascript", "-e", script).Start()
|
||||
if err != nil {
|
||||
fyne.LogError("Failed to launch darwin notify script", err)
|
||||
}
|
||||
}
|
61
vendor/fyne.io/fyne/v2/app/app_darwin.m
generated
vendored
61
vendor/fyne.io/fyne/v2/app/app_darwin.m
generated
vendored
@@ -1,61 +0,0 @@
|
||||
//go:build !ci
|
||||
// +build !ci
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
|
||||
#import <UserNotifications/UserNotifications.h>
|
||||
#endif
|
||||
|
||||
static int notifyNum = 0;
|
||||
|
||||
extern void fallbackSend(char *cTitle, char *cBody);
|
||||
|
||||
bool isBundled() {
|
||||
return [[NSBundle mainBundle] bundleIdentifier] != nil;
|
||||
}
|
||||
|
||||
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
|
||||
void doSendNotification(UNUserNotificationCenter *center, NSString *title, NSString *body) {
|
||||
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
|
||||
[content autorelease];
|
||||
content.title = title;
|
||||
content.body = body;
|
||||
|
||||
notifyNum++;
|
||||
NSString *identifier = [NSString stringWithFormat:@"fyne-notify-%d", notifyNum];
|
||||
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
|
||||
content:content trigger:nil];
|
||||
|
||||
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
|
||||
if (error != nil) {
|
||||
NSLog(@"Could not send notification: %@", error);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
void sendNotification(char *cTitle, char *cBody) {
|
||||
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
|
||||
NSString *title = [NSString stringWithUTF8String:cTitle];
|
||||
NSString *body = [NSString stringWithUTF8String:cBody];
|
||||
|
||||
UNAuthorizationOptions options = UNAuthorizationOptionAlert;
|
||||
[center requestAuthorizationWithOptions:options
|
||||
completionHandler:^(BOOL granted, NSError *_Nullable error) {
|
||||
if (!granted) {
|
||||
if (error != NULL) {
|
||||
NSLog(@"Error asking for permission to send notifications %@", error);
|
||||
// this happens if our app was not signed, so do it the old way
|
||||
fallbackSend((char *)[title UTF8String], (char *)[body UTF8String]);
|
||||
} else {
|
||||
NSLog(@"Unable to get permission to send notifications");
|
||||
}
|
||||
} else {
|
||||
doSendNotification(center, title, body);
|
||||
}
|
||||
}];
|
||||
}
|
||||
#else
|
||||
void sendNotification(char *cTitle, char *cBody) {
|
||||
fallbackSend(cTitle, cBody);
|
||||
}
|
||||
#endif
|
8
vendor/fyne.io/fyne/v2/app/app_debug.go
generated
vendored
8
vendor/fyne.io/fyne/v2/app/app_debug.go
generated
vendored
@@ -1,8 +0,0 @@
|
||||
//go:build debug
|
||||
// +build debug
|
||||
|
||||
package app
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
const buildMode = fyne.BuildDebug
|
53
vendor/fyne.io/fyne/v2/app/app_desktop_darwin.go
generated
vendored
53
vendor/fyne.io/fyne/v2/app/app_desktop_darwin.go
generated
vendored
@@ -1,53 +0,0 @@
|
||||
//go:build !ci && !ios && !js && !wasm && !test_web_driver
|
||||
// +build !ci,!ios,!js,!wasm,!test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation
|
||||
|
||||
#include <AppKit/AppKit.h>
|
||||
|
||||
bool isBundled();
|
||||
bool isDarkMode();
|
||||
void watchTheme();
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
if C.isDarkMode() {
|
||||
return theme.VariantDark
|
||||
}
|
||||
return theme.VariantLight
|
||||
}
|
||||
|
||||
func rootConfigDir() string {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
|
||||
desktopConfig := filepath.Join(filepath.Join(homeDir, "Library"), "Preferences")
|
||||
return filepath.Join(desktopConfig, "fyne")
|
||||
}
|
||||
|
||||
func (a *fyneApp) OpenURL(url *url.URL) error {
|
||||
cmd := a.exec("open", url.String())
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
//export themeChanged
|
||||
func themeChanged() {
|
||||
fyne.CurrentApp().Settings().(*settings).setupTheme()
|
||||
}
|
||||
|
||||
func watchTheme() {
|
||||
C.watchTheme()
|
||||
}
|
18
vendor/fyne.io/fyne/v2/app/app_desktop_darwin.m
generated
vendored
18
vendor/fyne.io/fyne/v2/app/app_desktop_darwin.m
generated
vendored
@@ -1,18 +0,0 @@
|
||||
//go:build !ci && !ios
|
||||
// +build !ci,!ios
|
||||
|
||||
extern void themeChanged();
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
bool isDarkMode() {
|
||||
NSString *style = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"];
|
||||
return [@"Dark" isEqualToString:style];
|
||||
}
|
||||
|
||||
void watchTheme() {
|
||||
[[NSDistributedNotificationCenter defaultCenter] addObserverForName:@"AppleInterfaceThemeChangedNotification" object:nil queue:nil
|
||||
usingBlock:^(NSNotification *note) {
|
||||
themeChanged(); // calls back into Go
|
||||
}];
|
||||
}
|
15
vendor/fyne.io/fyne/v2/app/app_gl.go
generated
vendored
15
vendor/fyne.io/fyne/v2/app/app_gl.go
generated
vendored
@@ -1,15 +0,0 @@
|
||||
//go:build !ci && !android && !ios && !mobile
|
||||
// +build !ci,!android,!ios,!mobile
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal/driver/glfw"
|
||||
)
|
||||
|
||||
// NewWithID returns a new app instance using the appropriate runtime driver.
|
||||
// The ID string should be globally unique to this app.
|
||||
func NewWithID(id string) fyne.App {
|
||||
return newAppWithDriver(glfw.NewGLDriver(), id)
|
||||
}
|
19
vendor/fyne.io/fyne/v2/app/app_goxjs.go
generated
vendored
19
vendor/fyne.io/fyne/v2/app/app_goxjs.go
generated
vendored
@@ -1,19 +0,0 @@
|
||||
//go:build !ci && (!android || !ios || !mobile) && (js || wasm || test_web_driver)
|
||||
// +build !ci
|
||||
// +build !android !ios !mobile
|
||||
// +build js wasm test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
func (app *fyneApp) SendNotification(_ *fyne.Notification) {
|
||||
// TODO #2735
|
||||
fyne.LogError("Sending notification is not supported yet.", nil)
|
||||
}
|
||||
|
||||
func rootConfigDir() string {
|
||||
return "/data/"
|
||||
}
|
25
vendor/fyne.io/fyne/v2/app/app_mobile.go
generated
vendored
25
vendor/fyne.io/fyne/v2/app/app_mobile.go
generated
vendored
@@ -1,25 +0,0 @@
|
||||
//go:build !ci && (android || ios || mobile)
|
||||
// +build !ci
|
||||
// +build android ios mobile
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal/driver/mobile"
|
||||
)
|
||||
|
||||
var systemTheme fyne.ThemeVariant
|
||||
|
||||
// NewWithID returns a new app instance using the appropriate runtime driver.
|
||||
// The ID string should be globally unique to this app.
|
||||
func NewWithID(id string) fyne.App {
|
||||
d := mobile.NewGoMobileDriver()
|
||||
a := newAppWithDriver(d, id)
|
||||
d.(mobile.ConfiguredDriver).SetOnConfigurationChanged(func(c *mobile.Configuration) {
|
||||
systemTheme = c.SystemTheme
|
||||
|
||||
a.Settings().(*settings).setupTheme()
|
||||
})
|
||||
return a
|
||||
}
|
131
vendor/fyne.io/fyne/v2/app/app_mobile_and.c
generated
vendored
131
vendor/fyne.io/fyne/v2/app/app_mobile_and.c
generated
vendored
@@ -1,131 +0,0 @@
|
||||
//go:build !ci && android
|
||||
// +build !ci,android
|
||||
|
||||
#include <android/log.h>
|
||||
#include <jni.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define LOG_FATAL(...) __android_log_print(ANDROID_LOG_FATAL, "Fyne", __VA_ARGS__)
|
||||
|
||||
static jclass find_class(JNIEnv *env, const char *class_name) {
|
||||
jclass clazz = (*env)->FindClass(env, class_name);
|
||||
if (clazz == NULL) {
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find %s", class_name);
|
||||
return NULL;
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
static jmethodID find_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
jmethodID m = (*env)->GetMethodID(env, clazz, name, sig);
|
||||
if (m == 0) {
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find method %s %s", name, sig);
|
||||
return 0;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
static jmethodID find_static_method(JNIEnv *env, jclass clazz, const char *name, const char *sig) {
|
||||
jmethodID m = (*env)->GetStaticMethodID(env, clazz, name, sig);
|
||||
if (m == 0) {
|
||||
(*env)->ExceptionClear(env);
|
||||
LOG_FATAL("cannot find method %s %s", name, sig);
|
||||
return 0;
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
jobject getSystemService(uintptr_t jni_env, uintptr_t ctx, char *service) {
|
||||
JNIEnv *env = (JNIEnv*)jni_env;
|
||||
jstring serviceStr = (*env)->NewStringUTF(env, service);
|
||||
|
||||
jclass ctxClass = (*env)->GetObjectClass(env, ctx);
|
||||
jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
|
||||
|
||||
return (jobject)(*env)->CallObjectMethod(env, ctx, getSystemService, serviceStr);
|
||||
}
|
||||
|
||||
int nextId = 1;
|
||||
|
||||
bool isOreoOrLater(JNIEnv *env) {
|
||||
jclass versionClass = find_class(env, "android/os/Build$VERSION" );
|
||||
jfieldID sdkIntFieldID = (*env)->GetStaticFieldID(env, versionClass, "SDK_INT", "I" );
|
||||
int sdkVersion = (*env)->GetStaticIntField(env, versionClass, sdkIntFieldID );
|
||||
|
||||
return sdkVersion >= 26; // O = Oreo, will not be defined for older builds
|
||||
}
|
||||
|
||||
jobject parseURL(uintptr_t jni_env, uintptr_t ctx, char* uriCstr) {
|
||||
JNIEnv *env = (JNIEnv*)jni_env;
|
||||
|
||||
jstring uriStr = (*env)->NewStringUTF(env, uriCstr);
|
||||
jclass uriClass = find_class(env, "android/net/Uri");
|
||||
jmethodID parse = find_static_method(env, uriClass, "parse", "(Ljava/lang/String;)Landroid/net/Uri;");
|
||||
|
||||
return (jobject)(*env)->CallStaticObjectMethod(env, uriClass, parse, uriStr);
|
||||
}
|
||||
|
||||
void openURL(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *url) {
|
||||
JNIEnv *env = (JNIEnv*)jni_env;
|
||||
jobject uri = parseURL(jni_env, ctx, url);
|
||||
|
||||
jclass intentClass = find_class(env, "android/content/Intent");
|
||||
jfieldID viewFieldID = (*env)->GetStaticFieldID(env, intentClass, "ACTION_VIEW", "Ljava/lang/String;" );
|
||||
jstring view = (*env)->GetStaticObjectField(env, intentClass, viewFieldID);
|
||||
|
||||
jmethodID constructor = find_method(env, intentClass, "<init>", "(Ljava/lang/String;Landroid/net/Uri;)V");
|
||||
jobject intent = (*env)->NewObject(env, intentClass, constructor, view, uri);
|
||||
|
||||
jclass contextClass = find_class(env, "android/content/Context");
|
||||
jmethodID start = find_method(env, contextClass, "startActivity", "(Landroid/content/Intent;)V");
|
||||
(*env)->CallVoidMethod(env, ctx, start, intent);
|
||||
}
|
||||
|
||||
void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *title, char *body) {
|
||||
JNIEnv *env = (JNIEnv*)jni_env;
|
||||
jstring titleStr = (*env)->NewStringUTF(env, title);
|
||||
jstring bodyStr = (*env)->NewStringUTF(env, body);
|
||||
|
||||
jclass cls = find_class(env, "android/app/Notification$Builder");
|
||||
jmethodID constructor = find_method(env, cls, "<init>", "(Landroid/content/Context;)V");
|
||||
jobject builder = (*env)->NewObject(env, cls, constructor, ctx);
|
||||
|
||||
jclass mgrCls = find_class(env, "android/app/NotificationManager");
|
||||
jobject mgr = getSystemService(env, ctx, "notification");
|
||||
|
||||
if (isOreoOrLater(env)) {
|
||||
jstring channelId = (*env)->NewStringUTF(env, "fyne-notif");
|
||||
jstring name = (*env)->NewStringUTF(env, "Fyne Notification");
|
||||
int importance = 4; // IMPORTANCE_HIGH
|
||||
|
||||
jclass chanCls = find_class(env, "android/app/NotificationChannel");
|
||||
jmethodID constructor = find_method(env, chanCls, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V");
|
||||
jobject channel = (*env)->NewObject(env, chanCls, constructor, channelId, name, importance);
|
||||
|
||||
jmethodID createChannel = find_method(env, mgrCls, "createNotificationChannel", "(Landroid/app/NotificationChannel;)V");
|
||||
(*env)->CallVoidMethod(env, mgr, createChannel, channel);
|
||||
|
||||
jmethodID setChannelId = find_method(env, cls, "setChannelId", "(Ljava/lang/String;)Landroid/app/Notification$Builder;");
|
||||
(*env)->CallObjectMethod(env, builder, setChannelId, channelId);
|
||||
}
|
||||
|
||||
jmethodID setContentTitle = find_method(env, cls, "setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;");
|
||||
(*env)->CallObjectMethod(env, builder, setContentTitle, titleStr);
|
||||
|
||||
jmethodID setContentText = find_method(env, cls, "setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;");
|
||||
(*env)->CallObjectMethod(env, builder, setContentText, bodyStr);
|
||||
|
||||
int iconID = 17629184; // constant of "unknown app icon"
|
||||
jmethodID setSmallIcon = find_method(env, cls, "setSmallIcon", "(I)Landroid/app/Notification$Builder;");
|
||||
(*env)->CallObjectMethod(env, builder, setSmallIcon, iconID);
|
||||
|
||||
jmethodID build = find_method(env, cls, "build", "()Landroid/app/Notification;");
|
||||
jobject notif = (*env)->CallObjectMethod(env, builder, build);
|
||||
|
||||
jmethodID notify = find_method(env, mgrCls, "notify", "(ILandroid/app/Notification;)V");
|
||||
(*env)->CallVoidMethod(env, mgr, notify, nextId, notif);
|
||||
nextId++;
|
||||
}
|
61
vendor/fyne.io/fyne/v2/app/app_mobile_and.go
generated
vendored
61
vendor/fyne.io/fyne/v2/app/app_mobile_and.go
generated
vendored
@@ -1,61 +0,0 @@
|
||||
//go:build !ci && android
|
||||
// +build !ci,android
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -landroid -llog
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
void openURL(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *url);
|
||||
void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *title, char *content);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal/driver/mobile/app"
|
||||
)
|
||||
|
||||
func (a *fyneApp) OpenURL(url *url.URL) error {
|
||||
urlStr := C.CString(url.String())
|
||||
defer C.free(unsafe.Pointer(urlStr))
|
||||
|
||||
app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
C.openURL(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), urlStr)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *fyneApp) SendNotification(n *fyne.Notification) {
|
||||
titleStr := C.CString(n.Title)
|
||||
defer C.free(unsafe.Pointer(titleStr))
|
||||
contentStr := C.CString(n.Content)
|
||||
defer C.free(unsafe.Pointer(contentStr))
|
||||
|
||||
app.RunOnJVM(func(vm, env, ctx uintptr) error {
|
||||
C.sendNotification(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), titleStr, contentStr)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
return systemTheme
|
||||
}
|
||||
|
||||
func rootConfigDir() string {
|
||||
filesDir := os.Getenv("FILESDIR")
|
||||
if filesDir == "" {
|
||||
log.Println("FILESDIR env was not set by android native code")
|
||||
return "/data/data" // probably won't work, but we can't make a better guess
|
||||
}
|
||||
|
||||
return filepath.Join(filesDir, "fyne")
|
||||
}
|
40
vendor/fyne.io/fyne/v2/app/app_mobile_ios.go
generated
vendored
40
vendor/fyne.io/fyne/v2/app/app_mobile_ios.go
generated
vendored
@@ -1,40 +0,0 @@
|
||||
//go:build !ci && ios
|
||||
// +build !ci,ios
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo CFLAGS: -x objective-c
|
||||
#cgo LDFLAGS: -framework Foundation -framework UIKit -framework UserNotifications
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
char *documentsPath(void);
|
||||
void openURL(char *urlStr);
|
||||
void sendNotification(char *title, char *content);
|
||||
*/
|
||||
import "C"
|
||||
import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
func rootConfigDir() string {
|
||||
root := C.documentsPath()
|
||||
return filepath.Join(C.GoString(root), "fyne")
|
||||
}
|
||||
|
||||
func (a *fyneApp) OpenURL(url *url.URL) error {
|
||||
urlStr := C.CString(url.String())
|
||||
C.openURL(urlStr)
|
||||
C.free(unsafe.Pointer(urlStr))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
return systemTheme
|
||||
}
|
16
vendor/fyne.io/fyne/v2/app/app_mobile_ios.m
generated
vendored
16
vendor/fyne.io/fyne/v2/app/app_mobile_ios.m
generated
vendored
@@ -1,16 +0,0 @@
|
||||
//go:build !ci && ios
|
||||
// +build !ci,ios
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
void openURL(char *urlStr) {
|
||||
UIApplication *app = [UIApplication sharedApplication];
|
||||
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlStr]];
|
||||
[app openURL:url options:@{} completionHandler:nil];
|
||||
}
|
||||
|
||||
char *documentsPath() {
|
||||
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
|
||||
NSString *path = paths.firstObject;
|
||||
return [path UTF8String];
|
||||
}
|
9
vendor/fyne.io/fyne/v2/app/app_notlegacy_darwin.go
generated
vendored
9
vendor/fyne.io/fyne/v2/app/app_notlegacy_darwin.go
generated
vendored
@@ -1,9 +0,0 @@
|
||||
//go:build !ci && !legacy && !js && !wasm && !test_web_driver
|
||||
// +build !ci,!legacy,!js,!wasm,!test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -framework Foundation -framework UserNotifications
|
||||
*/
|
||||
import "C"
|
20
vendor/fyne.io/fyne/v2/app/app_openurl_js.go
generated
vendored
20
vendor/fyne.io/fyne/v2/app/app_openurl_js.go
generated
vendored
@@ -1,20 +0,0 @@
|
||||
//go:build !ci && js && !wasm
|
||||
// +build !ci,js,!wasm
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"honnef.co/go/js/dom"
|
||||
)
|
||||
|
||||
func (app *fyneApp) OpenURL(url *url.URL) error {
|
||||
window := dom.GetWindow().Open(url.String(), "_blank", "")
|
||||
if window == nil {
|
||||
return fmt.Errorf("Unable to open a new window/tab for URL: %v.", url)
|
||||
}
|
||||
window.Focus()
|
||||
return nil
|
||||
}
|
19
vendor/fyne.io/fyne/v2/app/app_openurl_wasm.go
generated
vendored
19
vendor/fyne.io/fyne/v2/app/app_openurl_wasm.go
generated
vendored
@@ -1,19 +0,0 @@
|
||||
//go:build !ci && wasm
|
||||
// +build !ci,wasm
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
func (app *fyneApp) OpenURL(url *url.URL) error {
|
||||
window := js.Global().Call("open", url.String(), "_blank", "")
|
||||
if window.Equal(js.Null()) {
|
||||
return fmt.Errorf("Unable to open a new window/tab for URL: %v.", url)
|
||||
}
|
||||
window.Call("focus")
|
||||
return nil
|
||||
}
|
13
vendor/fyne.io/fyne/v2/app/app_openurl_web.go
generated
vendored
13
vendor/fyne.io/fyne/v2/app/app_openurl_web.go
generated
vendored
@@ -1,13 +0,0 @@
|
||||
//go:build !ci && !js && !wasm && test_web_driver
|
||||
// +build !ci,!js,!wasm,test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (app *fyneApp) OpenURL(url *url.URL) error {
|
||||
return errors.New("OpenURL is not supported with the test web driver.")
|
||||
}
|
32
vendor/fyne.io/fyne/v2/app/app_other.go
generated
vendored
32
vendor/fyne.io/fyne/v2/app/app_other.go
generated
vendored
@@ -1,32 +0,0 @@
|
||||
//go:build ci || (!linux && !darwin && !windows && !freebsd && !openbsd && !netbsd && !js && !wasm && !test_web_driver)
|
||||
// +build ci !linux,!darwin,!windows,!freebsd,!openbsd,!netbsd,!js,!wasm,!test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
return theme.VariantDark
|
||||
}
|
||||
|
||||
func rootConfigDir() string {
|
||||
return "/tmp/fyne-test/"
|
||||
}
|
||||
|
||||
func (a *fyneApp) OpenURL(_ *url.URL) error {
|
||||
return errors.New("Unable to open url for unknown operating system")
|
||||
}
|
||||
|
||||
func (a *fyneApp) SendNotification(_ *fyne.Notification) {
|
||||
fyne.LogError("Refusing to show notification for unknown operating system", nil)
|
||||
}
|
||||
|
||||
func watchTheme() {
|
||||
// no-op
|
||||
}
|
8
vendor/fyne.io/fyne/v2/app/app_release.go
generated
vendored
8
vendor/fyne.io/fyne/v2/app/app_release.go
generated
vendored
@@ -1,8 +0,0 @@
|
||||
//go:build release
|
||||
// +build release
|
||||
|
||||
package app
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
const buildMode = fyne.BuildRelease
|
16
vendor/fyne.io/fyne/v2/app/app_software.go
generated
vendored
16
vendor/fyne.io/fyne/v2/app/app_software.go
generated
vendored
@@ -1,16 +0,0 @@
|
||||
//go:build ci
|
||||
// +build ci
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal/painter/software"
|
||||
"fyne.io/fyne/v2/test"
|
||||
)
|
||||
|
||||
// NewWithID returns a new app instance using the test (headless) driver.
|
||||
// The ID string should be globally unique to this app.
|
||||
func NewWithID(id string) fyne.App {
|
||||
return newAppWithDriver(test.NewDriverWithPainter(software.NewPainter()), id)
|
||||
}
|
8
vendor/fyne.io/fyne/v2/app/app_standard.go
generated
vendored
8
vendor/fyne.io/fyne/v2/app/app_standard.go
generated
vendored
@@ -1,8 +0,0 @@
|
||||
//go:build !debug && !release
|
||||
// +build !debug,!release
|
||||
|
||||
package app
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
const buildMode = fyne.BuildStandard
|
29
vendor/fyne.io/fyne/v2/app/app_theme_js.go
generated
vendored
29
vendor/fyne.io/fyne/v2/app/app_theme_js.go
generated
vendored
@@ -1,29 +0,0 @@
|
||||
//go:build !ci && js && !wasm
|
||||
// +build !ci,js,!wasm
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
|
||||
"github.com/gopherjs/gopherjs/js"
|
||||
)
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
if matchMedia := js.Global.Call("matchMedia", "(prefers-color-scheme: dark)"); matchMedia != js.Undefined {
|
||||
if matches := matchMedia.Get("matches"); matches != js.Undefined && matches.Bool() {
|
||||
return theme.VariantDark
|
||||
}
|
||||
return theme.VariantLight
|
||||
}
|
||||
return theme.VariantDark
|
||||
}
|
||||
|
||||
func init() {
|
||||
if matchMedia := js.Global.Call("matchMedia", "(prefers-color-scheme: dark)"); matchMedia != js.Undefined {
|
||||
matchMedia.Call("addEventListener", "change", func(o *js.Object) {
|
||||
fyne.CurrentApp().Settings().(*settings).setupTheme()
|
||||
})
|
||||
}
|
||||
}
|
31
vendor/fyne.io/fyne/v2/app/app_theme_wasm.go
generated
vendored
31
vendor/fyne.io/fyne/v2/app/app_theme_wasm.go
generated
vendored
@@ -1,31 +0,0 @@
|
||||
//go:build !ci && wasm
|
||||
// +build !ci,wasm
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"syscall/js"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
matches := js.Global().Call("matchMedia", "(prefers-color-scheme: dark)")
|
||||
if matches.Truthy() {
|
||||
if matches.Get("matches").Bool() {
|
||||
return theme.VariantDark
|
||||
}
|
||||
return theme.VariantLight
|
||||
}
|
||||
return theme.VariantDark
|
||||
}
|
||||
|
||||
func init() {
|
||||
if matchMedia := js.Global().Call("matchMedia", "(prefers-color-scheme: dark)"); matchMedia.Truthy() {
|
||||
matchMedia.Call("addEventListener", "change", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||
fyne.CurrentApp().Settings().(*settings).setupTheme()
|
||||
return nil
|
||||
}))
|
||||
}
|
||||
}
|
13
vendor/fyne.io/fyne/v2/app/app_theme_web.go
generated
vendored
13
vendor/fyne.io/fyne/v2/app/app_theme_web.go
generated
vendored
@@ -1,13 +0,0 @@
|
||||
//go:build !ci && !js && !wasm && test_web_driver
|
||||
// +build !ci,!js,!wasm,test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
return theme.VariantDark
|
||||
}
|
126
vendor/fyne.io/fyne/v2/app/app_windows.go
generated
vendored
126
vendor/fyne.io/fyne/v2/app/app_windows.go
generated
vendored
@@ -1,126 +0,0 @@
|
||||
//go:build !ci && !js && !android && !ios && !wasm && !test_web_driver
|
||||
// +build !ci,!js,!android,!ios,!wasm,!test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
|
||||
"golang.org/x/sys/execabs"
|
||||
)
|
||||
|
||||
const notificationTemplate = `$title = "%s"
|
||||
$content = "%s"
|
||||
|
||||
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null
|
||||
$template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastText02)
|
||||
$toastXml = [xml] $template.GetXml()
|
||||
$toastXml.GetElementsByTagName("text")[0].AppendChild($toastXml.CreateTextNode($title)) > $null
|
||||
$toastXml.GetElementsByTagName("text")[1].AppendChild($toastXml.CreateTextNode($content)) > $null
|
||||
|
||||
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
|
||||
$xml.LoadXml($toastXml.OuterXml)
|
||||
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
|
||||
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("%s").Show($toast);`
|
||||
|
||||
func isDark() bool {
|
||||
k, err := registry.OpenKey(registry.CURRENT_USER, `SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize`, registry.QUERY_VALUE)
|
||||
if err != nil { // older version of Windows will not have this key
|
||||
return false
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
useLight, _, err := k.GetIntegerValue("AppsUseLightTheme")
|
||||
if err != nil { // older version of Windows will not have this value
|
||||
return false
|
||||
}
|
||||
|
||||
return useLight == 0
|
||||
}
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
if isDark() {
|
||||
return theme.VariantDark
|
||||
}
|
||||
return theme.VariantLight
|
||||
}
|
||||
|
||||
func rootConfigDir() string {
|
||||
homeDir, _ := os.UserHomeDir()
|
||||
|
||||
desktopConfig := filepath.Join(filepath.Join(homeDir, "AppData"), "Roaming")
|
||||
return filepath.Join(desktopConfig, "fyne")
|
||||
}
|
||||
|
||||
func (a *fyneApp) OpenURL(url *url.URL) error {
|
||||
cmd := a.exec("rundll32", "url.dll,FileProtocolHandler", url.String())
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
var scriptNum = 0
|
||||
|
||||
func (a *fyneApp) SendNotification(n *fyne.Notification) {
|
||||
title := escapeNotificationString(n.Title)
|
||||
content := escapeNotificationString(n.Content)
|
||||
appID := a.UniqueID()
|
||||
if appID == "" || strings.Index(appID, "missing-id") == 0 {
|
||||
appID = a.Metadata().Name
|
||||
}
|
||||
|
||||
script := fmt.Sprintf(notificationTemplate, title, content, appID)
|
||||
go runScript("notify", script)
|
||||
}
|
||||
|
||||
// SetSystemTrayMenu creates a system tray item and attaches the specified menu.
|
||||
// By default this will use the application icon.
|
||||
func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) {
|
||||
a.Driver().(systrayDriver).SetSystemTrayMenu(menu)
|
||||
}
|
||||
|
||||
// SetSystemTrayIcon sets a custom image for the system tray icon.
|
||||
// You should have previously called `SetSystemTrayMenu` to initialise the menu icon.
|
||||
func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) {
|
||||
a.Driver().(systrayDriver).SetSystemTrayIcon(icon)
|
||||
}
|
||||
|
||||
func escapeNotificationString(in string) string {
|
||||
noSlash := strings.ReplaceAll(in, "`", "``")
|
||||
return strings.ReplaceAll(noSlash, "\"", "`\"")
|
||||
}
|
||||
|
||||
func runScript(name, script string) {
|
||||
scriptNum++
|
||||
appID := fyne.CurrentApp().UniqueID()
|
||||
fileName := fmt.Sprintf("fyne-%s-%s-%d.ps1", appID, name, scriptNum)
|
||||
|
||||
tmpFilePath := filepath.Join(os.TempDir(), fileName)
|
||||
err := ioutil.WriteFile(tmpFilePath, []byte(script), 0600)
|
||||
if err != nil {
|
||||
fyne.LogError("Could not write script to show notification", err)
|
||||
return
|
||||
}
|
||||
defer os.Remove(tmpFilePath)
|
||||
|
||||
launch := "(Get-Content -Encoding UTF8 -Path " + tmpFilePath + " -Raw) | Invoke-Expression"
|
||||
cmd := execabs.Command("PowerShell", "-ExecutionPolicy", "Bypass", launch)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{HideWindow: true}
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
fyne.LogError("Failed to launch windows notify script", err)
|
||||
}
|
||||
}
|
||||
func watchTheme() {
|
||||
// TODO monitor the Windows theme
|
||||
}
|
122
vendor/fyne.io/fyne/v2/app/app_xdg.go
generated
vendored
122
vendor/fyne.io/fyne/v2/app/app_xdg.go
generated
vendored
@@ -1,122 +0,0 @@
|
||||
//go:build !ci && !js && !wasm && !test_web_driver && (linux || openbsd || freebsd || netbsd) && !android
|
||||
// +build !ci
|
||||
// +build !js
|
||||
// +build !wasm
|
||||
// +build !test_web_driver
|
||||
// +build linux openbsd freebsd netbsd
|
||||
// +build !android
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/godbus/dbus/v5"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
|
||||
func defaultVariant() fyne.ThemeVariant {
|
||||
return theme.VariantDark
|
||||
}
|
||||
|
||||
func (a *fyneApp) OpenURL(url *url.URL) error {
|
||||
cmd := a.exec("xdg-open", url.String())
|
||||
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||
return cmd.Start()
|
||||
}
|
||||
|
||||
func (a *fyneApp) SendNotification(n *fyne.Notification) {
|
||||
conn, err := dbus.SessionBus() // shared connection, don't close
|
||||
if err != nil {
|
||||
fyne.LogError("Unable to connect to session D-Bus", err)
|
||||
return
|
||||
}
|
||||
|
||||
appName := fyne.CurrentApp().UniqueID()
|
||||
appIcon := a.cachedIconPath()
|
||||
timeout := int32(0) // we don't support this yet
|
||||
|
||||
obj := conn.Object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
|
||||
call := obj.Call("org.freedesktop.Notifications.Notify", 0, appName, uint32(0),
|
||||
appIcon, n.Title, n.Content, []string{}, map[string]dbus.Variant{}, timeout)
|
||||
if call.Err != nil {
|
||||
fyne.LogError("Failed to send message to bus", call.Err)
|
||||
}
|
||||
}
|
||||
|
||||
func (a *fyneApp) saveIconToCache(dirPath, filePath string) error {
|
||||
err := os.MkdirAll(dirPath, 0700)
|
||||
if err != nil {
|
||||
fyne.LogError("Unable to create application cache directory", err)
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
fyne.LogError("Unable to create icon file", err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer file.Close()
|
||||
|
||||
if icon := a.Icon(); icon != nil {
|
||||
_, err = file.Write(icon.Content())
|
||||
if err != nil {
|
||||
fyne.LogError("Unable to write icon contents", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *fyneApp) cachedIconPath() string {
|
||||
if a.Icon() == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
dirPath := filepath.Join(rootCacheDir(), a.UniqueID())
|
||||
filePath := filepath.Join(dirPath, "icon.png")
|
||||
once.Do(func() {
|
||||
err := a.saveIconToCache(dirPath, filePath)
|
||||
if err != nil {
|
||||
filePath = ""
|
||||
}
|
||||
})
|
||||
|
||||
return filePath
|
||||
|
||||
}
|
||||
|
||||
// SetSystemTrayMenu creates a system tray item and attaches the specified menu.
|
||||
// By default this will use the application icon.
|
||||
func (a *fyneApp) SetSystemTrayMenu(menu *fyne.Menu) {
|
||||
a.Driver().(systrayDriver).SetSystemTrayMenu(menu)
|
||||
}
|
||||
|
||||
// SetSystemTrayIcon sets a custom image for the system tray icon.
|
||||
// You should have previously called `SetSystemTrayMenu` to initialise the menu icon.
|
||||
func (a *fyneApp) SetSystemTrayIcon(icon fyne.Resource) {
|
||||
a.Driver().(systrayDriver).SetSystemTrayIcon(icon)
|
||||
}
|
||||
|
||||
func rootConfigDir() string {
|
||||
desktopConfig, _ := os.UserConfigDir()
|
||||
return filepath.Join(desktopConfig, "fyne")
|
||||
}
|
||||
|
||||
func rootCacheDir() string {
|
||||
desktopCache, _ := os.UserCacheDir()
|
||||
return filepath.Join(desktopCache, "fyne")
|
||||
}
|
||||
|
||||
func watchTheme() {
|
||||
// no-op, not able to read linux theme in a standard way
|
||||
}
|
22
vendor/fyne.io/fyne/v2/app/meta.go
generated
vendored
22
vendor/fyne.io/fyne/v2/app/meta.go
generated
vendored
@@ -1,22 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
var meta = fyne.AppMetadata{
|
||||
ID: "",
|
||||
Name: "",
|
||||
Version: "0.0.1",
|
||||
Build: 1,
|
||||
}
|
||||
|
||||
// SetMetadata overrides the packaged application metadata.
|
||||
// This data can be used in many places like notifications and about screens.
|
||||
func SetMetadata(m fyne.AppMetadata) {
|
||||
meta = m
|
||||
}
|
||||
|
||||
func (a *fyneApp) Metadata() fyne.AppMetadata {
|
||||
return meta
|
||||
}
|
150
vendor/fyne.io/fyne/v2/app/preferences.go
generated
vendored
150
vendor/fyne.io/fyne/v2/app/preferences.go
generated
vendored
@@ -1,150 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal"
|
||||
)
|
||||
|
||||
type preferences struct {
|
||||
*internal.InMemoryPreferences
|
||||
|
||||
prefLock sync.RWMutex
|
||||
loadingInProgress bool
|
||||
savedRecently bool
|
||||
changedDuringSaving bool
|
||||
|
||||
app *fyneApp
|
||||
}
|
||||
|
||||
// Declare conformity with Preferences interface
|
||||
var _ fyne.Preferences = (*preferences)(nil)
|
||||
|
||||
func (p *preferences) resetSavedRecently() {
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 100) // writes are not always atomic. 10ms worked, 100 is safer.
|
||||
p.prefLock.Lock()
|
||||
p.savedRecently = false
|
||||
changedDuringSaving := p.changedDuringSaving
|
||||
p.changedDuringSaving = false
|
||||
p.prefLock.Unlock()
|
||||
|
||||
if changedDuringSaving {
|
||||
p.save()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (p *preferences) save() error {
|
||||
return p.saveToFile(p.storagePath())
|
||||
}
|
||||
|
||||
func (p *preferences) saveToFile(path string) error {
|
||||
p.prefLock.Lock()
|
||||
p.savedRecently = true
|
||||
p.prefLock.Unlock()
|
||||
defer p.resetSavedRecently()
|
||||
err := os.MkdirAll(filepath.Dir(path), 0700)
|
||||
if err != nil { // this is not an exists error according to docs
|
||||
return err
|
||||
}
|
||||
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
if !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
file, err = os.Open(path) // #nosec
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
defer file.Close()
|
||||
encode := json.NewEncoder(file)
|
||||
|
||||
p.InMemoryPreferences.ReadValues(func(values map[string]interface{}) {
|
||||
err = encode.Encode(&values)
|
||||
})
|
||||
|
||||
err2 := file.Sync()
|
||||
if err == nil {
|
||||
err = err2
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *preferences) load() {
|
||||
err := p.loadFromFile(p.storagePath())
|
||||
if err != nil {
|
||||
fyne.LogError("Preferences load error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *preferences) loadFromFile(path string) (err error) {
|
||||
file, err := os.Open(path) // #nosec
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if r := file.Close(); r != nil && err == nil {
|
||||
err = r
|
||||
}
|
||||
}()
|
||||
decode := json.NewDecoder(file)
|
||||
|
||||
p.prefLock.Lock()
|
||||
p.loadingInProgress = true
|
||||
p.prefLock.Unlock()
|
||||
|
||||
p.InMemoryPreferences.WriteValues(func(values map[string]interface{}) {
|
||||
err = decode.Decode(&values)
|
||||
})
|
||||
|
||||
p.prefLock.Lock()
|
||||
p.loadingInProgress = false
|
||||
p.prefLock.Unlock()
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func newPreferences(app *fyneApp) *preferences {
|
||||
p := &preferences{}
|
||||
p.app = app
|
||||
p.InMemoryPreferences = internal.NewInMemoryPreferences()
|
||||
|
||||
// don't load or watch if not setup
|
||||
if app.uniqueID == "" && app.Metadata().ID == "" {
|
||||
return p
|
||||
}
|
||||
|
||||
p.AddChangeListener(func() {
|
||||
p.prefLock.Lock()
|
||||
shouldIgnoreChange := p.savedRecently || p.loadingInProgress
|
||||
if p.savedRecently && !p.loadingInProgress {
|
||||
p.changedDuringSaving = true
|
||||
}
|
||||
p.prefLock.Unlock()
|
||||
|
||||
if shouldIgnoreChange { // callback after loading file, or too many updates in a row
|
||||
return
|
||||
}
|
||||
|
||||
err := p.save()
|
||||
if err != nil {
|
||||
fyne.LogError("Failed on saving preferences", err)
|
||||
}
|
||||
})
|
||||
p.watch()
|
||||
return p
|
||||
}
|
21
vendor/fyne.io/fyne/v2/app/preferences_android.go
generated
vendored
21
vendor/fyne.io/fyne/v2/app/preferences_android.go
generated
vendored
@@ -1,21 +0,0 @@
|
||||
//go:build android
|
||||
// +build android
|
||||
|
||||
package app
|
||||
|
||||
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(p.app.storageRoot(), "preferences.json")
|
||||
}
|
||||
|
||||
// storageRoot returns the location of the app storage
|
||||
func (a *fyneApp) storageRoot() string {
|
||||
return rootConfigDir() // we are in a sandbox, so no app ID added to this path
|
||||
}
|
||||
|
||||
func (p *preferences) watch() {
|
||||
// no-op on mobile
|
||||
}
|
24
vendor/fyne.io/fyne/v2/app/preferences_ios.go
generated
vendored
24
vendor/fyne.io/fyne/v2/app/preferences_ios.go
generated
vendored
@@ -1,24 +0,0 @@
|
||||
//go:build ios
|
||||
// +build ios
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
)
|
||||
import "C"
|
||||
|
||||
// storagePath returns the location of the settings storage
|
||||
func (p *preferences) storagePath() string {
|
||||
ret := filepath.Join(p.app.storageRoot(), "preferences.json")
|
||||
return ret
|
||||
}
|
||||
|
||||
// storageRoot returns the location of the app storage
|
||||
func (a *fyneApp) storageRoot() string {
|
||||
return rootConfigDir() // we are in a sandbox, so no app ID added to this path
|
||||
}
|
||||
|
||||
func (p *preferences) watch() {
|
||||
// no-op on mobile
|
||||
}
|
20
vendor/fyne.io/fyne/v2/app/preferences_mobile.go
generated
vendored
20
vendor/fyne.io/fyne/v2/app/preferences_mobile.go
generated
vendored
@@ -1,20 +0,0 @@
|
||||
//go:build mobile
|
||||
// +build mobile
|
||||
|
||||
package app
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
// storagePath returns the location of the settings storage
|
||||
func (p *preferences) storagePath() string {
|
||||
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())
|
||||
}
|
||||
|
||||
func (p *preferences) watch() {
|
||||
// no-op as we are in mobile simulation mode
|
||||
}
|
29
vendor/fyne.io/fyne/v2/app/preferences_other.go
generated
vendored
29
vendor/fyne.io/fyne/v2/app/preferences_other.go
generated
vendored
@@ -1,29 +0,0 @@
|
||||
//go:build !ios && !android && !mobile
|
||||
// +build !ios,!android,!mobile
|
||||
|
||||
package app
|
||||
|
||||
import "path/filepath"
|
||||
|
||||
// storagePath returns the location of the settings storage
|
||||
func (p *preferences) storagePath() string {
|
||||
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())
|
||||
}
|
||||
|
||||
func (p *preferences) watch() {
|
||||
watchFile(p.storagePath(), func() {
|
||||
p.prefLock.RLock()
|
||||
shouldIgnoreChange := p.savedRecently
|
||||
p.prefLock.RUnlock()
|
||||
if shouldIgnoreChange {
|
||||
return
|
||||
}
|
||||
|
||||
p.load()
|
||||
})
|
||||
}
|
159
vendor/fyne.io/fyne/v2/app/settings.go
generated
vendored
159
vendor/fyne.io/fyne/v2/app/settings.go
generated
vendored
@@ -1,159 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
// SettingsSchema is used for loading and storing global settings
|
||||
type SettingsSchema struct {
|
||||
// these items are used for global settings load
|
||||
ThemeName string `json:"theme"`
|
||||
Scale float32 `json:"scale"`
|
||||
PrimaryColor string `json:"primary_color"`
|
||||
}
|
||||
|
||||
// StoragePath returns the location of the settings storage
|
||||
func (sc *SettingsSchema) StoragePath() string {
|
||||
return filepath.Join(rootConfigDir(), "settings.json")
|
||||
}
|
||||
|
||||
// Declare conformity with Settings interface
|
||||
var _ fyne.Settings = (*settings)(nil)
|
||||
|
||||
type settings struct {
|
||||
propertyLock sync.RWMutex
|
||||
theme fyne.Theme
|
||||
themeSpecified bool
|
||||
variant fyne.ThemeVariant
|
||||
|
||||
changeListeners sync.Map // map[chan fyne.Settings]bool
|
||||
watcher interface{} // normally *fsnotify.Watcher or nil - avoid import in this file
|
||||
|
||||
schema SettingsSchema
|
||||
}
|
||||
|
||||
func (s *settings) BuildType() fyne.BuildType {
|
||||
return buildMode
|
||||
}
|
||||
|
||||
func (s *settings) PrimaryColor() string {
|
||||
s.propertyLock.RLock()
|
||||
defer s.propertyLock.RUnlock()
|
||||
return s.schema.PrimaryColor
|
||||
}
|
||||
|
||||
// OverrideTheme allows the settings app to temporarily preview different theme details.
|
||||
// Please make sure that you remember the original settings and call this again to revert the change.
|
||||
func (s *settings) OverrideTheme(theme fyne.Theme, name string) {
|
||||
s.propertyLock.Lock()
|
||||
defer s.propertyLock.Unlock()
|
||||
s.schema.PrimaryColor = name
|
||||
s.theme = theme
|
||||
}
|
||||
|
||||
func (s *settings) Theme() fyne.Theme {
|
||||
s.propertyLock.RLock()
|
||||
defer s.propertyLock.RUnlock()
|
||||
return s.theme
|
||||
}
|
||||
|
||||
func (s *settings) SetTheme(theme fyne.Theme) {
|
||||
s.themeSpecified = true
|
||||
s.applyTheme(theme, s.variant)
|
||||
}
|
||||
|
||||
func (s *settings) ThemeVariant() fyne.ThemeVariant {
|
||||
return s.variant
|
||||
}
|
||||
|
||||
func (s *settings) applyTheme(theme fyne.Theme, variant fyne.ThemeVariant) {
|
||||
s.propertyLock.Lock()
|
||||
defer s.propertyLock.Unlock()
|
||||
s.variant = variant
|
||||
s.theme = theme
|
||||
s.apply()
|
||||
}
|
||||
|
||||
func (s *settings) Scale() float32 {
|
||||
s.propertyLock.RLock()
|
||||
defer s.propertyLock.RUnlock()
|
||||
if s.schema.Scale < 0.0 {
|
||||
return 1.0 // catching any really old data still using the `-1` value for "auto" scale
|
||||
}
|
||||
return s.schema.Scale
|
||||
}
|
||||
|
||||
func (s *settings) AddChangeListener(listener chan fyne.Settings) {
|
||||
s.changeListeners.Store(listener, true) // the boolean is just a dummy value here.
|
||||
}
|
||||
|
||||
func (s *settings) apply() {
|
||||
s.changeListeners.Range(func(key, _ interface{}) bool {
|
||||
listener := key.(chan fyne.Settings)
|
||||
select {
|
||||
case listener <- s:
|
||||
default:
|
||||
l := listener
|
||||
go func() { l <- s }()
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (s *settings) fileChanged() {
|
||||
s.load()
|
||||
s.apply()
|
||||
}
|
||||
|
||||
func (s *settings) loadSystemTheme() fyne.Theme {
|
||||
path := filepath.Join(rootConfigDir(), "theme.json")
|
||||
data, err := fyne.LoadResourceFromPath(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
fyne.LogError("Failed to load user theme file: "+path, err)
|
||||
}
|
||||
return theme.DefaultTheme()
|
||||
}
|
||||
if data != nil && data.Content() != nil {
|
||||
th, err := theme.FromJSONReader(bytes.NewReader(data.Content()))
|
||||
if err == nil {
|
||||
return th
|
||||
}
|
||||
fyne.LogError("Failed to parse user theme file: "+path, err)
|
||||
}
|
||||
return theme.DefaultTheme()
|
||||
}
|
||||
|
||||
func (s *settings) setupTheme() {
|
||||
name := s.schema.ThemeName
|
||||
if env := os.Getenv("FYNE_THEME"); env != "" {
|
||||
name = env
|
||||
}
|
||||
|
||||
variant := defaultVariant()
|
||||
effectiveTheme := s.theme
|
||||
if !s.themeSpecified {
|
||||
effectiveTheme = s.loadSystemTheme()
|
||||
}
|
||||
switch name {
|
||||
case "light":
|
||||
variant = theme.VariantLight
|
||||
case "dark":
|
||||
variant = theme.VariantDark
|
||||
}
|
||||
|
||||
s.applyTheme(effectiveTheme, variant)
|
||||
}
|
||||
|
||||
func loadSettings() *settings {
|
||||
s := &settings{}
|
||||
s.load()
|
||||
|
||||
return s
|
||||
}
|
75
vendor/fyne.io/fyne/v2/app/settings_desktop.go
generated
vendored
75
vendor/fyne.io/fyne/v2/app/settings_desktop.go
generated
vendored
@@ -1,75 +0,0 @@
|
||||
//go:build !android && !ios && !mobile && !js && !wasm && !test_web_driver
|
||||
// +build !android,!ios,!mobile,!js,!wasm,!test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
)
|
||||
|
||||
func watchFileAddTarget(watcher *fsnotify.Watcher, path string) {
|
||||
dir := filepath.Dir(path)
|
||||
ensureDirExists(dir)
|
||||
|
||||
err := watcher.Add(dir)
|
||||
if err != nil {
|
||||
fyne.LogError("Settings watch error:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func ensureDirExists(dir string) {
|
||||
if stat, err := os.Stat(dir); err == nil && stat.IsDir() {
|
||||
return
|
||||
}
|
||||
|
||||
err := os.MkdirAll(dir, 0700)
|
||||
if err != nil {
|
||||
fyne.LogError("Unable to create settings storage:", err)
|
||||
}
|
||||
}
|
||||
|
||||
func watchFile(path string, callback func()) *fsnotify.Watcher {
|
||||
watcher, err := fsnotify.NewWatcher()
|
||||
if err != nil {
|
||||
fyne.LogError("Failed to watch settings file:", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
go func() {
|
||||
for event := range watcher.Events {
|
||||
if event.Op&fsnotify.Remove != 0 { // if it was deleted then watch again
|
||||
watcher.Remove(path) // fsnotify returns false positives, see https://github.com/fsnotify/fsnotify/issues/268
|
||||
|
||||
watchFileAddTarget(watcher, path)
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
err = watcher.Close()
|
||||
if err != nil {
|
||||
fyne.LogError("Settings un-watch error:", err)
|
||||
}
|
||||
}()
|
||||
|
||||
watchFileAddTarget(watcher, path)
|
||||
return watcher
|
||||
}
|
||||
|
||||
func (s *settings) watchSettings() {
|
||||
s.watcher = watchFile(s.schema.StoragePath(), s.fileChanged)
|
||||
|
||||
watchTheme()
|
||||
}
|
||||
|
||||
func (s *settings) stopWatching() {
|
||||
if s.watcher == nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.watcher.(*fsnotify.Watcher).Close() // fsnotify returns false positives, see https://github.com/fsnotify/fsnotify/issues/268
|
||||
}
|
34
vendor/fyne.io/fyne/v2/app/settings_file.go
generated
vendored
34
vendor/fyne.io/fyne/v2/app/settings_file.go
generated
vendored
@@ -1,34 +0,0 @@
|
||||
//go:build !js && !wasm && !test_web_driver
|
||||
// +build !js,!wasm,!test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
func (s *settings) load() {
|
||||
err := s.loadFromFile(s.schema.StoragePath())
|
||||
if err != nil && err != io.EOF { // we can get an EOF in windows settings writes
|
||||
fyne.LogError("Settings load error:", err)
|
||||
}
|
||||
|
||||
s.setupTheme()
|
||||
}
|
||||
|
||||
func (s *settings) loadFromFile(path string) error {
|
||||
file, err := os.Open(path) // #nosec
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
decode := json.NewDecoder(file)
|
||||
|
||||
return decode.Decode(&s.schema)
|
||||
}
|
24
vendor/fyne.io/fyne/v2/app/settings_goxjs.go
generated
vendored
24
vendor/fyne.io/fyne/v2/app/settings_goxjs.go
generated
vendored
@@ -1,24 +0,0 @@
|
||||
//go:build js || wasm || test_web_driver
|
||||
// +build js wasm test_web_driver
|
||||
|
||||
package app
|
||||
|
||||
// TODO: #2734
|
||||
|
||||
func (s *settings) load() {
|
||||
s.setupTheme()
|
||||
s.schema.Scale = 1
|
||||
}
|
||||
|
||||
func (s *settings) loadFromFile(path string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func watchFile(path string, callback func()) {
|
||||
}
|
||||
|
||||
func (s *settings) watchSettings() {
|
||||
}
|
||||
|
||||
func (s *settings) stopWatching() {
|
||||
}
|
12
vendor/fyne.io/fyne/v2/app/settings_mobile.go
generated
vendored
12
vendor/fyne.io/fyne/v2/app/settings_mobile.go
generated
vendored
@@ -1,12 +0,0 @@
|
||||
//go:build android || ios || mobile
|
||||
// +build android ios mobile
|
||||
|
||||
package app
|
||||
|
||||
func (s *settings) watchSettings() {
|
||||
// no-op on mobile
|
||||
}
|
||||
|
||||
func (s *settings) stopWatching() {
|
||||
// no-op on mobile
|
||||
}
|
27
vendor/fyne.io/fyne/v2/app/storage.go
generated
vendored
27
vendor/fyne.io/fyne/v2/app/storage.go
generated
vendored
@@ -1,27 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
)
|
||||
|
||||
type store struct {
|
||||
*internal.Docs
|
||||
a *fyneApp
|
||||
}
|
||||
|
||||
func (s *store) RootURI() fyne.URI {
|
||||
if s.a.UniqueID() == "" {
|
||||
fyne.LogError("Storage API requires a unique ID, use app.NewWithID()", nil)
|
||||
return storage.NewFileURI(os.TempDir())
|
||||
}
|
||||
|
||||
return storage.NewFileURI(s.a.storageRoot())
|
||||
}
|
||||
|
||||
func (s *store) docRootURI() (fyne.URI, error) {
|
||||
return storage.Child(s.RootURI(), "Documents")
|
||||
}
|
58
vendor/fyne.io/fyne/v2/canvas.go
generated
vendored
58
vendor/fyne.io/fyne/v2/canvas.go
generated
vendored
@@ -1,58 +0,0 @@
|
||||
package fyne
|
||||
|
||||
import "image"
|
||||
|
||||
// Canvas defines a graphical canvas to which a CanvasObject or Container can be added.
|
||||
// Each canvas has a scale which is automatically applied during the render process.
|
||||
type Canvas interface {
|
||||
Content() CanvasObject
|
||||
SetContent(CanvasObject)
|
||||
|
||||
Refresh(CanvasObject)
|
||||
|
||||
// Focus makes the provided item focused.
|
||||
// The item has to be added to the contents of the canvas before calling this.
|
||||
Focus(Focusable)
|
||||
// FocusNext focuses the next focusable item.
|
||||
// If no item is currently focused, the first focusable item is focused.
|
||||
// If the last focusable item is currently focused, the first focusable item is focused.
|
||||
//
|
||||
// Since: 2.0
|
||||
FocusNext()
|
||||
// FocusPrevious focuses the previous focusable item.
|
||||
// If no item is currently focused, the last focusable item is focused.
|
||||
// If the first focusable item is currently focused, the last focusable item is focused.
|
||||
//
|
||||
// Since: 2.0
|
||||
FocusPrevious()
|
||||
Unfocus()
|
||||
Focused() Focusable
|
||||
|
||||
// Size returns the current size of this canvas
|
||||
Size() Size
|
||||
// Scale returns the current scale (multiplication factor) this canvas uses to render
|
||||
// The pixel size of a CanvasObject can be found by multiplying by this value.
|
||||
Scale() float32
|
||||
|
||||
// Overlays returns the overlay stack.
|
||||
Overlays() OverlayStack
|
||||
|
||||
OnTypedRune() func(rune)
|
||||
SetOnTypedRune(func(rune))
|
||||
OnTypedKey() func(*KeyEvent)
|
||||
SetOnTypedKey(func(*KeyEvent))
|
||||
AddShortcut(shortcut Shortcut, handler func(shortcut Shortcut))
|
||||
RemoveShortcut(shortcut Shortcut)
|
||||
|
||||
Capture() image.Image
|
||||
|
||||
// PixelCoordinateForPosition returns the x and y pixel coordinate for a given position on this canvas.
|
||||
// This can be used to find absolute pixel positions or pixel offsets relative to an object top left.
|
||||
PixelCoordinateForPosition(Position) (int, int)
|
||||
|
||||
// InteractiveArea returns the position and size of the central interactive area.
|
||||
// Operating system elements may overlap the portions outside this area and widgets should avoid being outside.
|
||||
//
|
||||
// Since: 1.4
|
||||
InteractiveArea() (Position, Size)
|
||||
}
|
86
vendor/fyne.io/fyne/v2/canvas/animation.go
generated
vendored
86
vendor/fyne.io/fyne/v2/canvas/animation.go
generated
vendored
@@ -1,86 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// DurationStandard is the time a standard interface animation will run.
|
||||
//
|
||||
// Since: 2.0
|
||||
DurationStandard = time.Millisecond * 300
|
||||
// DurationShort is the time a subtle or small transition should use.
|
||||
//
|
||||
// Since: 2.0
|
||||
DurationShort = time.Millisecond * 150
|
||||
)
|
||||
|
||||
// NewColorRGBAAnimation sets up a new animation that will transition from the start to stop Color over
|
||||
// the specified Duration. The colour transition will move linearly through the RGB colour space.
|
||||
// The content of fn should apply the color values to an object and refresh it.
|
||||
// You should call Start() on the returned animation to start it.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewColorRGBAAnimation(start, stop color.Color, d time.Duration, fn func(color.Color)) *fyne.Animation {
|
||||
r1, g1, b1, a1 := start.RGBA()
|
||||
r2, g2, b2, a2 := stop.RGBA()
|
||||
|
||||
rStart := int(r1 >> 8)
|
||||
gStart := int(g1 >> 8)
|
||||
bStart := int(b1 >> 8)
|
||||
aStart := int(a1 >> 8)
|
||||
rDelta := float32(int(r2>>8) - rStart)
|
||||
gDelta := float32(int(g2>>8) - gStart)
|
||||
bDelta := float32(int(b2>>8) - bStart)
|
||||
aDelta := float32(int(a2>>8) - aStart)
|
||||
|
||||
return &fyne.Animation{
|
||||
Duration: d,
|
||||
Tick: func(done float32) {
|
||||
fn(color.RGBA{R: scaleChannel(rStart, rDelta, done), G: scaleChannel(gStart, gDelta, done),
|
||||
B: scaleChannel(bStart, bDelta, done), A: scaleChannel(aStart, aDelta, done)})
|
||||
}}
|
||||
}
|
||||
|
||||
// NewPositionAnimation sets up a new animation that will transition from the start to stop Position over
|
||||
// the specified Duration. The content of fn should apply the position value to an object for the change
|
||||
// to be visible. You should call Start() on the returned animation to start it.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewPositionAnimation(start, stop fyne.Position, d time.Duration, fn func(fyne.Position)) *fyne.Animation {
|
||||
xDelta := float32(stop.X - start.X)
|
||||
yDelta := float32(stop.Y - start.Y)
|
||||
|
||||
return &fyne.Animation{
|
||||
Duration: d,
|
||||
Tick: func(done float32) {
|
||||
fn(fyne.NewPos(scaleVal(start.X, xDelta, done), scaleVal(start.Y, yDelta, done)))
|
||||
}}
|
||||
}
|
||||
|
||||
// NewSizeAnimation sets up a new animation that will transition from the start to stop Size over
|
||||
// the specified Duration. The content of fn should apply the size value to an object for the change
|
||||
// to be visible. You should call Start() on the returned animation to start it.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewSizeAnimation(start, stop fyne.Size, d time.Duration, fn func(fyne.Size)) *fyne.Animation {
|
||||
widthDelta := float32(stop.Width - start.Width)
|
||||
heightDelta := float32(stop.Height - start.Height)
|
||||
|
||||
return &fyne.Animation{
|
||||
Duration: d,
|
||||
Tick: func(done float32) {
|
||||
fn(fyne.NewSize(scaleVal(start.Width, widthDelta, done), scaleVal(start.Height, heightDelta, done)))
|
||||
}}
|
||||
}
|
||||
|
||||
func scaleChannel(start int, diff, done float32) uint8 {
|
||||
return uint8(start + int(diff*done))
|
||||
}
|
||||
|
||||
func scaleVal(start float32, delta, done float32) float32 {
|
||||
return start + delta*done
|
||||
}
|
100
vendor/fyne.io/fyne/v2/canvas/base.go
generated
vendored
100
vendor/fyne.io/fyne/v2/canvas/base.go
generated
vendored
@@ -1,100 +0,0 @@
|
||||
// Package canvas contains all of the primitive CanvasObjects that make up a Fyne GUI.
|
||||
//
|
||||
// The types implemented in this package are used as building blocks in order
|
||||
// to build higher order functionality. These types are designed to be
|
||||
// non-interactive, by design. If additional functionality is required,
|
||||
// it's usually a sign that this type should be used as part of a custom
|
||||
// widget.
|
||||
package canvas // import "fyne.io/fyne/v2/canvas"
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
type baseObject struct {
|
||||
size fyne.Size // The current size of the canvas object
|
||||
position fyne.Position // The current position of the object
|
||||
Hidden bool // Is this object currently hidden
|
||||
|
||||
min fyne.Size // The minimum size this object can be
|
||||
|
||||
propertyLock sync.RWMutex
|
||||
}
|
||||
|
||||
// Hide will set this object to not be visible.
|
||||
func (o *baseObject) Hide() {
|
||||
o.propertyLock.Lock()
|
||||
defer o.propertyLock.Unlock()
|
||||
|
||||
o.Hidden = true
|
||||
}
|
||||
|
||||
// MinSize returns the specified minimum size, if set, or {1, 1} otherwise.
|
||||
func (o *baseObject) MinSize() fyne.Size {
|
||||
o.propertyLock.RLock()
|
||||
defer o.propertyLock.RUnlock()
|
||||
|
||||
if o.min.Width == 0 && o.min.Height == 0 {
|
||||
return fyne.NewSize(1, 1)
|
||||
}
|
||||
|
||||
return o.min
|
||||
}
|
||||
|
||||
// Move the object to a new position, relative to its parent.
|
||||
func (o *baseObject) Move(pos fyne.Position) {
|
||||
o.propertyLock.Lock()
|
||||
defer o.propertyLock.Unlock()
|
||||
|
||||
o.position = pos
|
||||
}
|
||||
|
||||
// Position gets the current position of this canvas object, relative to its parent.
|
||||
func (o *baseObject) Position() fyne.Position {
|
||||
o.propertyLock.RLock()
|
||||
defer o.propertyLock.RUnlock()
|
||||
|
||||
return o.position
|
||||
}
|
||||
|
||||
// Resize sets a new size for the canvas object.
|
||||
func (o *baseObject) Resize(size fyne.Size) {
|
||||
o.propertyLock.Lock()
|
||||
defer o.propertyLock.Unlock()
|
||||
|
||||
o.size = size
|
||||
}
|
||||
|
||||
// SetMinSize specifies the smallest size this object should be.
|
||||
func (o *baseObject) SetMinSize(size fyne.Size) {
|
||||
o.propertyLock.Lock()
|
||||
defer o.propertyLock.Unlock()
|
||||
|
||||
o.min = size
|
||||
}
|
||||
|
||||
// Show will set this object to be visible.
|
||||
func (o *baseObject) Show() {
|
||||
o.propertyLock.Lock()
|
||||
defer o.propertyLock.Unlock()
|
||||
|
||||
o.Hidden = false
|
||||
}
|
||||
|
||||
// Size returns the current size of this canvas object.
|
||||
func (o *baseObject) Size() fyne.Size {
|
||||
o.propertyLock.RLock()
|
||||
defer o.propertyLock.RUnlock()
|
||||
|
||||
return o.size
|
||||
}
|
||||
|
||||
// Visible returns true if this object is visible, false otherwise.
|
||||
func (o *baseObject) Visible() bool {
|
||||
o.propertyLock.RLock()
|
||||
defer o.propertyLock.RUnlock()
|
||||
|
||||
return !o.Hidden
|
||||
}
|
15
vendor/fyne.io/fyne/v2/canvas/canvas.go
generated
vendored
15
vendor/fyne.io/fyne/v2/canvas/canvas.go
generated
vendored
@@ -1,15 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
// Refresh instructs the containing canvas to refresh the specified obj.
|
||||
func Refresh(obj fyne.CanvasObject) {
|
||||
if fyne.CurrentApp() == nil || fyne.CurrentApp().Driver() == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c := fyne.CurrentApp().Driver().CanvasForObject(obj)
|
||||
if c != nil {
|
||||
c.Refresh(obj)
|
||||
}
|
||||
}
|
87
vendor/fyne.io/fyne/v2/canvas/circle.go
generated
vendored
87
vendor/fyne.io/fyne/v2/canvas/circle.go
generated
vendored
@@ -1,87 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// Declare conformity with CanvasObject interface
|
||||
var _ fyne.CanvasObject = (*Circle)(nil)
|
||||
|
||||
// Circle describes a colored circle primitive in a Fyne canvas
|
||||
type Circle struct {
|
||||
Position1 fyne.Position // The current top-left position of the Circle
|
||||
Position2 fyne.Position // The current bottomright position of the Circle
|
||||
Hidden bool // Is this circle currently hidden
|
||||
|
||||
FillColor color.Color // The circle fill color
|
||||
StrokeColor color.Color // The circle stroke color
|
||||
StrokeWidth float32 // The stroke width of the circle
|
||||
}
|
||||
|
||||
// NewCircle returns a new Circle instance
|
||||
func NewCircle(color color.Color) *Circle {
|
||||
return &Circle{
|
||||
FillColor: color,
|
||||
}
|
||||
}
|
||||
|
||||
// Hide will set this circle to not be visible
|
||||
func (c *Circle) Hide() {
|
||||
c.Hidden = true
|
||||
|
||||
c.Refresh()
|
||||
}
|
||||
|
||||
// MinSize for a Circle simply returns Size{1, 1} as there is no
|
||||
// explicit content
|
||||
func (c *Circle) MinSize() fyne.Size {
|
||||
return fyne.NewSize(1, 1)
|
||||
}
|
||||
|
||||
// Move the circle object to a new position, relative to its parent / canvas
|
||||
func (c *Circle) Move(pos fyne.Position) {
|
||||
size := c.Size()
|
||||
c.Position1 = pos
|
||||
c.Position2 = fyne.NewPos(c.Position1.X+size.Width, c.Position1.Y+size.Height)
|
||||
}
|
||||
|
||||
// Position gets the current top-left position of this circle object, relative to its parent / canvas
|
||||
func (c *Circle) Position() fyne.Position {
|
||||
return c.Position1
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (c *Circle) Refresh() {
|
||||
Refresh(c)
|
||||
}
|
||||
|
||||
// Resize sets a new bottom-right position for the circle object
|
||||
// If it has a stroke width this will cause it to Refresh.
|
||||
func (c *Circle) Resize(size fyne.Size) {
|
||||
if size == c.Size() {
|
||||
return
|
||||
}
|
||||
|
||||
c.Position2 = fyne.NewPos(c.Position1.X+size.Width, c.Position1.Y+size.Height)
|
||||
|
||||
Refresh(c)
|
||||
}
|
||||
|
||||
// Show will set this circle to be visible
|
||||
func (c *Circle) Show() {
|
||||
c.Hidden = false
|
||||
|
||||
c.Refresh()
|
||||
}
|
||||
|
||||
// Size returns the current size of bounding box for this circle object
|
||||
func (c *Circle) Size() fyne.Size {
|
||||
return fyne.NewSize(c.Position2.X-c.Position1.X, c.Position2.Y-c.Position1.Y)
|
||||
}
|
||||
|
||||
// Visible returns true if this circle is visible, false otherwise
|
||||
func (c *Circle) Visible() bool {
|
||||
return !c.Hidden
|
||||
}
|
182
vendor/fyne.io/fyne/v2/canvas/gradient.go
generated
vendored
182
vendor/fyne.io/fyne/v2/canvas/gradient.go
generated
vendored
@@ -1,182 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
)
|
||||
|
||||
// LinearGradient defines a Gradient travelling straight at a given angle.
|
||||
// The only supported values for the angle are `0.0` (vertical) and `90.0` (horizontal), currently.
|
||||
type LinearGradient struct {
|
||||
baseObject
|
||||
|
||||
StartColor color.Color // The beginning color of the gradient
|
||||
EndColor color.Color // The end color of the gradient
|
||||
Angle float64 // The angle of the gradient (0/180 for vertical; 90/270 for horizontal)
|
||||
}
|
||||
|
||||
// Generate calculates an image of the gradient with the specified width and height.
|
||||
func (g *LinearGradient) Generate(iw, ih int) image.Image {
|
||||
w, h := float64(iw), float64(ih)
|
||||
var generator func(x, y float64) float64
|
||||
switch g.Angle {
|
||||
case 90: // horizontal flipped
|
||||
generator = func(x, _ float64) float64 {
|
||||
return (w - x) / w
|
||||
}
|
||||
case 270: // horizontal
|
||||
generator = func(x, _ float64) float64 {
|
||||
return x / w
|
||||
}
|
||||
case 45: // diagonal negative flipped
|
||||
generator = func(x, y float64) float64 {
|
||||
return math.Abs((w - x + y) / (w + h)) // ((w+h)-(x+h-y)) / (w+h)
|
||||
}
|
||||
case 225: // diagonal negative
|
||||
generator = func(x, y float64) float64 {
|
||||
return math.Abs((x + h - y) / (w + h))
|
||||
}
|
||||
case 135: // diagonal positive flipped
|
||||
generator = func(x, y float64) float64 {
|
||||
return math.Abs((w + h - (x + y)) / (w + h))
|
||||
}
|
||||
case 315: // diagonal positive
|
||||
generator = func(x, y float64) float64 {
|
||||
return math.Abs((x + y) / (w + h))
|
||||
}
|
||||
case 180: // vertical flipped
|
||||
generator = func(_, y float64) float64 {
|
||||
return (h - y) / h
|
||||
}
|
||||
default: // vertical
|
||||
generator = func(_, y float64) float64 {
|
||||
return y / h
|
||||
}
|
||||
}
|
||||
return computeGradient(generator, iw, ih, g.StartColor, g.EndColor)
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (g *LinearGradient) Refresh() {
|
||||
Refresh(g)
|
||||
}
|
||||
|
||||
// RadialGradient defines a Gradient travelling radially from a center point outward.
|
||||
type RadialGradient struct {
|
||||
baseObject
|
||||
|
||||
StartColor color.Color // The beginning color of the gradient
|
||||
EndColor color.Color // The end color of the gradient
|
||||
// The offset of the center for generation of the gradient.
|
||||
// This is not a DP measure but relates to the width/height.
|
||||
// A value of 0.5 would move the center by the half width/height.
|
||||
CenterOffsetX, CenterOffsetY float64
|
||||
}
|
||||
|
||||
// Generate calculates an image of the gradient with the specified width and height.
|
||||
func (g *RadialGradient) Generate(iw, ih int) image.Image {
|
||||
w, h := float64(iw), float64(ih)
|
||||
// define center plus offset
|
||||
centerX := w/2 + w*g.CenterOffsetX
|
||||
centerY := h/2 + h*g.CenterOffsetY
|
||||
|
||||
// handle negative offsets
|
||||
var a, b float64
|
||||
if g.CenterOffsetX < 0 {
|
||||
a = w - centerX
|
||||
} else {
|
||||
a = centerX
|
||||
}
|
||||
if g.CenterOffsetY < 0 {
|
||||
b = h - centerY
|
||||
} else {
|
||||
b = centerY
|
||||
}
|
||||
|
||||
generator := func(x, y float64) float64 {
|
||||
// calculate distance from center for gradient multiplier
|
||||
dx, dy := centerX-x, centerY-y
|
||||
da := math.Sqrt(dx*dx + dy*dy*a*a/b/b)
|
||||
if da > a {
|
||||
return 1
|
||||
}
|
||||
return da / a
|
||||
}
|
||||
return computeGradient(generator, iw, ih, g.StartColor, g.EndColor)
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (g *RadialGradient) Refresh() {
|
||||
Refresh(g)
|
||||
}
|
||||
|
||||
func calculatePixel(d float64, startColor, endColor color.Color) color.Color {
|
||||
// fetch RGBA values
|
||||
aR, aG, aB, aA := startColor.RGBA()
|
||||
bR, bG, bB, bA := endColor.RGBA()
|
||||
|
||||
// Get difference
|
||||
dR := float64(bR) - float64(aR)
|
||||
dG := float64(bG) - float64(aG)
|
||||
dB := float64(bB) - float64(aB)
|
||||
dA := float64(bA) - float64(aA)
|
||||
|
||||
// Apply gradations
|
||||
pixel := &color.RGBA64{
|
||||
R: uint16(float64(aR) + d*dR),
|
||||
B: uint16(float64(aB) + d*dB),
|
||||
G: uint16(float64(aG) + d*dG),
|
||||
A: uint16(float64(aA) + d*dA),
|
||||
}
|
||||
|
||||
return pixel
|
||||
}
|
||||
|
||||
func computeGradient(generator func(x, y float64) float64, w, h int, startColor, endColor color.Color) image.Image {
|
||||
img := image.NewNRGBA(image.Rect(0, 0, w, h))
|
||||
|
||||
if startColor == nil && endColor == nil {
|
||||
return img
|
||||
} else if startColor == nil {
|
||||
startColor = color.Transparent
|
||||
} else if endColor == nil {
|
||||
endColor = color.Transparent
|
||||
}
|
||||
|
||||
for x := 0; x < w; x++ {
|
||||
for y := 0; y < h; y++ {
|
||||
distance := generator(float64(x)+0.5, float64(y)+0.5)
|
||||
img.Set(x, y, calculatePixel(distance, startColor, endColor))
|
||||
}
|
||||
}
|
||||
return img
|
||||
}
|
||||
|
||||
// NewHorizontalGradient creates a new horizontally travelling linear gradient.
|
||||
// The start color will be at the left of the gradient and the end color will be at the right.
|
||||
func NewHorizontalGradient(start, end color.Color) *LinearGradient {
|
||||
g := &LinearGradient{StartColor: start, EndColor: end}
|
||||
g.Angle = 270
|
||||
return g
|
||||
}
|
||||
|
||||
// NewLinearGradient creates a linear gradient at a the specified angle.
|
||||
// The angle parameter is the degree angle along which the gradient is calculated.
|
||||
// A NewHorizontalGradient uses 270 degrees and NewVerticalGradient is 0 degrees.
|
||||
func NewLinearGradient(start, end color.Color, angle float64) *LinearGradient {
|
||||
g := &LinearGradient{StartColor: start, EndColor: end}
|
||||
g.Angle = angle
|
||||
return g
|
||||
}
|
||||
|
||||
// NewRadialGradient creates a new radial gradient.
|
||||
func NewRadialGradient(start, end color.Color) *RadialGradient {
|
||||
return &RadialGradient{StartColor: start, EndColor: end}
|
||||
}
|
||||
|
||||
// NewVerticalGradient creates a new vertically travelling linear gradient.
|
||||
// The start color will be at the top of the gradient and the end color will be at the bottom.
|
||||
func NewVerticalGradient(start color.Color, end color.Color) *LinearGradient {
|
||||
return &LinearGradient{StartColor: start, EndColor: end}
|
||||
}
|
166
vendor/fyne.io/fyne/v2/canvas/image.go
generated
vendored
166
vendor/fyne.io/fyne/v2/canvas/image.go
generated
vendored
@@ -1,166 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"image"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
)
|
||||
|
||||
// ImageFill defines the different type of ways an image can stretch to fill its space.
|
||||
type ImageFill int
|
||||
|
||||
const (
|
||||
// ImageFillStretch will scale the image to match the Size() values.
|
||||
// This is the default and does not maintain aspect ratio.
|
||||
ImageFillStretch ImageFill = iota
|
||||
// ImageFillContain makes the image fit within the object Size(),
|
||||
// centrally and maintaining aspect ratio.
|
||||
// There may be transparent sections top and bottom or left and right.
|
||||
ImageFillContain // (Fit)
|
||||
// ImageFillOriginal ensures that the container grows to the pixel dimensions
|
||||
// required to fit the original image. The aspect of the image will be maintained so,
|
||||
// as with ImageFillContain there may be transparent areas around the image.
|
||||
// Note that the minSize may be smaller than the image dimensions if scale > 1.
|
||||
ImageFillOriginal
|
||||
)
|
||||
|
||||
// ImageScale defines the different scaling filters used to scaling images
|
||||
type ImageScale int32
|
||||
|
||||
const (
|
||||
// ImageScaleSmooth will scale the image using ApproxBiLinear filter (or GL equivalent)
|
||||
ImageScaleSmooth ImageScale = iota
|
||||
// ImageScalePixels will scale the image using NearestNeighbor filter (or GL equivalent)
|
||||
ImageScalePixels
|
||||
// ImageScaleFastest will scale the image using hardware GPU if available
|
||||
//
|
||||
// Since: 2.0
|
||||
ImageScaleFastest
|
||||
)
|
||||
|
||||
// Declare conformity with CanvasObject interface
|
||||
var _ fyne.CanvasObject = (*Image)(nil)
|
||||
|
||||
// Image describes a drawable image area that can render in a Fyne canvas
|
||||
// The image may be a vector or a bitmap representation and it will fill the area.
|
||||
// The fill mode can be changed by setting FillMode to a different ImageFill.
|
||||
type Image struct {
|
||||
baseObject
|
||||
|
||||
// one of the following sources will provide our image data
|
||||
File string // Load the image from a file
|
||||
Resource fyne.Resource // Load the image from an in-memory resource
|
||||
Image image.Image // Specify a loaded image to use in this canvas object
|
||||
|
||||
Translucency float64 // Set a translucency value > 0.0 to fade the image
|
||||
FillMode ImageFill // Specify how the image should expand to fill or fit the available space
|
||||
ScaleMode ImageScale // Specify the type of scaling interpolation applied to the image
|
||||
|
||||
}
|
||||
|
||||
// Alpha is a convenience function that returns the alpha value for an image
|
||||
// based on its Translucency value. The result is 1.0 - Translucency.
|
||||
func (i *Image) Alpha() float64 {
|
||||
return 1.0 - i.Translucency
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if i.FillMode == ImageFillOriginal && i.size.Height > 2 { // don't refresh original scale images after first draw
|
||||
return
|
||||
}
|
||||
|
||||
i.baseObject.Resize(s)
|
||||
|
||||
Refresh(i)
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (i *Image) Refresh() {
|
||||
Refresh(i)
|
||||
}
|
||||
|
||||
// NewImageFromFile creates a new image from a local file.
|
||||
// Images returned from this method will scale to fit the canvas object.
|
||||
// The method for scaling can be set using the Fill field.
|
||||
func NewImageFromFile(file string) *Image {
|
||||
return &Image{
|
||||
File: file,
|
||||
}
|
||||
}
|
||||
|
||||
// NewImageFromURI creates a new image from named resource.
|
||||
// File URIs will read the file path and other schemes will download the data into a resource.
|
||||
// HTTP and HTTPs URIs will use the GET method by default to request the resource.
|
||||
// Images returned from this method will scale to fit the canvas object.
|
||||
// The method for scaling can be set using the Fill field.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewImageFromURI(uri fyne.URI) *Image {
|
||||
if uri.Scheme() == "file" && len(uri.String()) > 7 {
|
||||
return &Image{
|
||||
File: uri.String()[7:],
|
||||
}
|
||||
}
|
||||
|
||||
var read io.ReadCloser
|
||||
|
||||
read, err := storage.Reader(uri) // attempt unknown / http file type
|
||||
if err != nil {
|
||||
fyne.LogError("Failed to open image URI", err)
|
||||
return &Image{}
|
||||
}
|
||||
|
||||
defer read.Close()
|
||||
return NewImageFromReader(read, filepath.Base(uri.String()))
|
||||
}
|
||||
|
||||
// NewImageFromReader creates a new image from a data stream.
|
||||
// The name parameter is required to uniquely identify this image (for caching etc).
|
||||
// If the image in this io.Reader is an SVG, the name should end ".svg".
|
||||
// Images returned from this method will scale to fit the canvas object.
|
||||
// The method for scaling can be set using the Fill field.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewImageFromReader(read io.Reader, name string) *Image {
|
||||
data, err := ioutil.ReadAll(read)
|
||||
if err != nil {
|
||||
fyne.LogError("Unable to read image data", err)
|
||||
return nil
|
||||
}
|
||||
res := &fyne.StaticResource{
|
||||
StaticName: name,
|
||||
StaticContent: data,
|
||||
}
|
||||
|
||||
return &Image{
|
||||
Resource: res,
|
||||
}
|
||||
}
|
||||
|
||||
// NewImageFromResource creates a new image by loading the specified resource.
|
||||
// Images returned from this method will scale to fit the canvas object.
|
||||
// The method for scaling can be set using the Fill field.
|
||||
func NewImageFromResource(res fyne.Resource) *Image {
|
||||
return &Image{
|
||||
Resource: res,
|
||||
}
|
||||
}
|
||||
|
||||
// NewImageFromImage returns a new Image instance that is rendered from the Go
|
||||
// image.Image passed in.
|
||||
// Images returned from this method will scale to fit the canvas object.
|
||||
// The method for scaling can be set using the Fill field.
|
||||
func NewImageFromImage(img image.Image) *Image {
|
||||
return &Image{
|
||||
Image: img,
|
||||
}
|
||||
}
|
101
vendor/fyne.io/fyne/v2/canvas/line.go
generated
vendored
101
vendor/fyne.io/fyne/v2/canvas/line.go
generated
vendored
@@ -1,101 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// Declare conformity with CanvasObject interface
|
||||
var _ fyne.CanvasObject = (*Line)(nil)
|
||||
|
||||
// Line describes a colored line primitive in a Fyne canvas.
|
||||
// Lines are special as they can have a negative width or height to indicate
|
||||
// an inverse slope (i.e. slope up vs down).
|
||||
type Line struct {
|
||||
Position1 fyne.Position // The current top-left position of the Line
|
||||
Position2 fyne.Position // The current bottomright position of the Line
|
||||
Hidden bool // Is this Line currently hidden
|
||||
|
||||
StrokeColor color.Color // The line stroke color
|
||||
StrokeWidth float32 // The stroke width of the line
|
||||
}
|
||||
|
||||
// Size returns the current size of bounding box for this line object
|
||||
func (l *Line) Size() fyne.Size {
|
||||
return fyne.NewSize(float32(math.Abs(float64(l.Position2.X)-float64(l.Position1.X))),
|
||||
float32(math.Abs(float64(l.Position2.Y)-float64(l.Position1.Y))))
|
||||
}
|
||||
|
||||
// Resize sets a new bottom-right position for the line object and it will then be refreshed.
|
||||
func (l *Line) Resize(size fyne.Size) {
|
||||
if size == l.Size() {
|
||||
return
|
||||
}
|
||||
|
||||
if l.Position1.X <= l.Position2.X {
|
||||
l.Position2.X = l.Position1.X + size.Width
|
||||
} else {
|
||||
l.Position1.X = l.Position2.X + size.Width
|
||||
}
|
||||
if l.Position1.Y <= l.Position2.Y {
|
||||
l.Position2.Y = l.Position1.Y + size.Height
|
||||
} else {
|
||||
l.Position1.Y = l.Position2.Y + size.Height
|
||||
}
|
||||
Refresh(l)
|
||||
}
|
||||
|
||||
// Position gets the current top-left position of this line object, relative to its parent / canvas
|
||||
func (l *Line) Position() fyne.Position {
|
||||
return fyne.NewPos(fyne.Min(l.Position1.X, l.Position2.X), fyne.Min(l.Position1.Y, l.Position2.Y))
|
||||
}
|
||||
|
||||
// Move the line object to a new position, relative to its parent / canvas
|
||||
func (l *Line) Move(pos fyne.Position) {
|
||||
oldPos := l.Position()
|
||||
deltaX := pos.X - oldPos.X
|
||||
deltaY := pos.Y - oldPos.Y
|
||||
|
||||
l.Position1 = l.Position1.Add(fyne.NewPos(deltaX, deltaY))
|
||||
l.Position2 = l.Position2.Add(fyne.NewPos(deltaX, deltaY))
|
||||
}
|
||||
|
||||
// MinSize for a Line simply returns Size{1, 1} as there is no
|
||||
// explicit content
|
||||
func (l *Line) MinSize() fyne.Size {
|
||||
return fyne.NewSize(1, 1)
|
||||
}
|
||||
|
||||
// Visible returns true if this line// Show will set this circle to be visible is visible, false otherwise
|
||||
func (l *Line) Visible() bool {
|
||||
return !l.Hidden
|
||||
}
|
||||
|
||||
// Show will set this line to be visible
|
||||
func (l *Line) Show() {
|
||||
l.Hidden = false
|
||||
|
||||
l.Refresh()
|
||||
}
|
||||
|
||||
// Hide will set this line to not be visible
|
||||
func (l *Line) Hide() {
|
||||
l.Hidden = true
|
||||
|
||||
l.Refresh()
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (l *Line) Refresh() {
|
||||
Refresh(l)
|
||||
}
|
||||
|
||||
// NewLine returns a new Line instance
|
||||
func NewLine(color color.Color) *Line {
|
||||
return &Line{
|
||||
StrokeColor: color,
|
||||
StrokeWidth: 1,
|
||||
}
|
||||
}
|
182
vendor/fyne.io/fyne/v2/canvas/raster.go
generated
vendored
182
vendor/fyne.io/fyne/v2/canvas/raster.go
generated
vendored
@@ -1,182 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// Declare conformity with CanvasObject interface
|
||||
var _ fyne.CanvasObject = (*Raster)(nil)
|
||||
|
||||
// Raster describes a raster image area that can render in a Fyne canvas
|
||||
type Raster struct {
|
||||
baseObject
|
||||
|
||||
// Render the raster image from code
|
||||
Generator func(w, h int) image.Image
|
||||
|
||||
// Set a translucency value > 0.0 to fade the raster
|
||||
Translucency float64
|
||||
// Specify the type of scaling interpolation applied to the raster if it is not full-size
|
||||
// Since: 1.4.1
|
||||
ScaleMode ImageScale
|
||||
}
|
||||
|
||||
// Alpha is a convenience function that returns the alpha value for a raster
|
||||
// based on its Translucency value. The result is 1.0 - Translucency.
|
||||
func (r *Raster) Alpha() float64 {
|
||||
return 1.0 - r.Translucency
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (r *Raster) Refresh() {
|
||||
Refresh(r)
|
||||
}
|
||||
|
||||
// NewRaster returns a new Image instance that is rendered dynamically using
|
||||
// the specified generate function.
|
||||
// Images returned from this method should draw dynamically to fill the width
|
||||
// and height parameters passed to pixelColor.
|
||||
func NewRaster(generate func(w, h int) image.Image) *Raster {
|
||||
return &Raster{Generator: generate}
|
||||
}
|
||||
|
||||
type pixelRaster struct {
|
||||
r *Raster
|
||||
|
||||
img draw.Image
|
||||
}
|
||||
|
||||
// NewRasterWithPixels returns a new Image instance that is rendered dynamically
|
||||
// by iterating over the specified pixelColor function for each x, y pixel.
|
||||
// Images returned from this method should draw dynamically to fill the width
|
||||
// and height parameters passed to pixelColor.
|
||||
func NewRasterWithPixels(pixelColor func(x, y, w, h int) color.Color) *Raster {
|
||||
pix := &pixelRaster{}
|
||||
pix.r = &Raster{
|
||||
Generator: func(w, h int) image.Image {
|
||||
if pix.img == nil || pix.img.Bounds().Size().X != w || pix.img.Bounds().Size().Y != h {
|
||||
// raster first pixel, figure out color type
|
||||
var dst draw.Image
|
||||
rect := image.Rect(0, 0, w, h)
|
||||
switch pixelColor(0, 0, w, h).(type) {
|
||||
case color.Alpha:
|
||||
dst = image.NewAlpha(rect)
|
||||
case color.Alpha16:
|
||||
dst = image.NewAlpha16(rect)
|
||||
case color.CMYK:
|
||||
dst = image.NewCMYK(rect)
|
||||
case color.Gray:
|
||||
dst = image.NewGray(rect)
|
||||
case color.Gray16:
|
||||
dst = image.NewGray16(rect)
|
||||
case color.NRGBA:
|
||||
dst = image.NewNRGBA(rect)
|
||||
case color.NRGBA64:
|
||||
dst = image.NewNRGBA64(rect)
|
||||
case color.RGBA:
|
||||
dst = image.NewRGBA(rect)
|
||||
case color.RGBA64:
|
||||
dst = image.NewRGBA64(rect)
|
||||
default:
|
||||
dst = image.NewRGBA(rect)
|
||||
}
|
||||
pix.img = dst
|
||||
}
|
||||
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
pix.img.Set(x, y, pixelColor(x, y, w, h))
|
||||
}
|
||||
}
|
||||
|
||||
return pix.img
|
||||
},
|
||||
}
|
||||
return pix.r
|
||||
}
|
||||
|
||||
type subImg interface {
|
||||
SubImage(r image.Rectangle) image.Image
|
||||
}
|
||||
|
||||
// NewRasterFromImage returns a new Raster instance that is rendered from the Go
|
||||
// image.Image passed in.
|
||||
// Rasters returned from this method will map pixel for pixel to the screen
|
||||
// starting img.Bounds().Min pixels from the top left of the canvas object.
|
||||
// Truncates rather than scales the image.
|
||||
// If smaller than the target space, the image will be padded with zero-pixels to the target size.
|
||||
func NewRasterFromImage(img image.Image) *Raster {
|
||||
return &Raster{
|
||||
Generator: func(w int, h int) image.Image {
|
||||
bounds := img.Bounds()
|
||||
|
||||
rect := image.Rect(0, 0, w, h)
|
||||
|
||||
switch {
|
||||
case w == bounds.Max.X && h == bounds.Max.Y:
|
||||
return img
|
||||
case w >= bounds.Max.X && h >= bounds.Max.Y:
|
||||
// try quickly truncating
|
||||
if sub, ok := img.(subImg); ok {
|
||||
return sub.SubImage(image.Rectangle{
|
||||
Min: bounds.Min,
|
||||
Max: image.Point{
|
||||
X: bounds.Min.X + w,
|
||||
Y: bounds.Min.Y + h,
|
||||
},
|
||||
})
|
||||
}
|
||||
default:
|
||||
if !rect.Overlaps(bounds) {
|
||||
return image.NewUniform(color.RGBA{})
|
||||
}
|
||||
bounds = bounds.Intersect(rect)
|
||||
}
|
||||
|
||||
// respect the user's pixel format (if possible)
|
||||
var dst draw.Image
|
||||
switch i := img.(type) {
|
||||
case (*image.Alpha):
|
||||
dst = image.NewAlpha(rect)
|
||||
case (*image.Alpha16):
|
||||
dst = image.NewAlpha16(rect)
|
||||
case (*image.CMYK):
|
||||
dst = image.NewCMYK(rect)
|
||||
case (*image.Gray):
|
||||
dst = image.NewGray(rect)
|
||||
case (*image.Gray16):
|
||||
dst = image.NewGray16(rect)
|
||||
case (*image.NRGBA):
|
||||
dst = image.NewNRGBA(rect)
|
||||
case (*image.NRGBA64):
|
||||
dst = image.NewNRGBA64(rect)
|
||||
case (*image.Paletted):
|
||||
dst = image.NewPaletted(rect, i.Palette)
|
||||
case (*image.RGBA):
|
||||
dst = image.NewRGBA(rect)
|
||||
case (*image.RGBA64):
|
||||
dst = image.NewRGBA64(rect)
|
||||
default:
|
||||
dst = image.NewRGBA(rect)
|
||||
}
|
||||
|
||||
draw.Draw(dst, bounds, img, bounds.Min, draw.Over)
|
||||
return dst
|
||||
},
|
||||
}
|
||||
}
|
46
vendor/fyne.io/fyne/v2/canvas/rectangle.go
generated
vendored
46
vendor/fyne.io/fyne/v2/canvas/rectangle.go
generated
vendored
@@ -1,46 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// Declare conformity with CanvasObject interface
|
||||
var _ fyne.CanvasObject = (*Rectangle)(nil)
|
||||
|
||||
// Rectangle describes a colored rectangle primitive in a Fyne canvas
|
||||
type Rectangle struct {
|
||||
baseObject
|
||||
|
||||
FillColor color.Color // The rectangle fill color
|
||||
StrokeColor color.Color // The rectangle stroke color
|
||||
StrokeWidth float32 // The stroke width of the rectangle
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (r *Rectangle) Refresh() {
|
||||
Refresh(r)
|
||||
}
|
||||
|
||||
// Resize on a rectangle updates the new size of this object.
|
||||
// If it has a stroke width this will cause it to Refresh.
|
||||
func (r *Rectangle) Resize(s fyne.Size) {
|
||||
if s == r.Size() {
|
||||
return
|
||||
}
|
||||
|
||||
r.baseObject.Resize(s)
|
||||
if r.StrokeWidth == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
Refresh(r)
|
||||
}
|
||||
|
||||
// NewRectangle returns a new Rectangle instance
|
||||
func NewRectangle(color color.Color) *Rectangle {
|
||||
return &Rectangle{
|
||||
FillColor: color,
|
||||
}
|
||||
}
|
52
vendor/fyne.io/fyne/v2/canvas/text.go
generated
vendored
52
vendor/fyne.io/fyne/v2/canvas/text.go
generated
vendored
@@ -1,52 +0,0 @@
|
||||
package canvas
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// Declare conformity with CanvasObject interface
|
||||
var _ fyne.CanvasObject = (*Text)(nil)
|
||||
|
||||
// Text describes a text primitive in a Fyne canvas.
|
||||
// A text object can have a style set which will apply to the whole string.
|
||||
// No formatting or text parsing will be performed
|
||||
type Text struct {
|
||||
baseObject
|
||||
Alignment fyne.TextAlign // The alignment of the text content
|
||||
|
||||
Color color.Color // The main text draw color
|
||||
Text string // The string content of this Text
|
||||
TextSize float32 // Size of the text - if the Canvas scale is 1.0 this will be equivalent to point size
|
||||
TextStyle fyne.TextStyle // The style of the text content
|
||||
}
|
||||
|
||||
// MinSize returns the minimum size of this text object based on its font size and content.
|
||||
// This is normally determined by the render implementation.
|
||||
func (t *Text) MinSize() fyne.Size {
|
||||
return fyne.MeasureText(t.Text, t.TextSize, t.TextStyle)
|
||||
}
|
||||
|
||||
// SetMinSize has no effect as the smallest size this canvas object can be is based on its font size and content.
|
||||
func (t *Text) SetMinSize(size fyne.Size) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (t *Text) Refresh() {
|
||||
Refresh(t)
|
||||
}
|
||||
|
||||
// NewText returns a new Text implementation
|
||||
func NewText(text string, color color.Color) *Text {
|
||||
size := float32(0)
|
||||
if fyne.CurrentApp() != nil { // nil app possible if app not started
|
||||
size = fyne.CurrentApp().Settings().Theme().Size("text") // manually name the size to avoid import loop
|
||||
}
|
||||
return &Text{
|
||||
Color: color,
|
||||
Text: text,
|
||||
TextSize: size,
|
||||
}
|
||||
}
|
107
vendor/fyne.io/fyne/v2/canvasobject.go
generated
vendored
107
vendor/fyne.io/fyne/v2/canvasobject.go
generated
vendored
@@ -1,107 +0,0 @@
|
||||
package fyne
|
||||
|
||||
// CanvasObject describes any graphical object that can be added to a canvas.
|
||||
// Objects have a size and position that can be controlled through this API.
|
||||
// MinSize is used to determine the minimum size which this object should be displayed.
|
||||
// An object will be visible by default but can be hidden with Hide() and re-shown with Show().
|
||||
//
|
||||
// Note: If this object is controlled as part of a Layout you should not call
|
||||
// Resize(Size) or Move(Position).
|
||||
type CanvasObject interface {
|
||||
// geometry
|
||||
|
||||
// MinSize returns the minimum size this object needs to be drawn.
|
||||
MinSize() Size
|
||||
// Move moves this object to the given position relative to its parent.
|
||||
// This should only be called if your object is not in a container with a layout manager.
|
||||
Move(Position)
|
||||
// Position returns the current position of the object relative to its parent.
|
||||
Position() Position
|
||||
// Resize resizes this object to the given size.
|
||||
// This should only be called if your object is not in a container with a layout manager.
|
||||
Resize(Size)
|
||||
// Size returns the current size of this object.
|
||||
Size() Size
|
||||
|
||||
// visibility
|
||||
|
||||
// Hide hides this object.
|
||||
Hide()
|
||||
// Visible returns whether this object is visible or not.
|
||||
Visible() bool
|
||||
// Show shows this object.
|
||||
Show()
|
||||
|
||||
// Refresh must be called if this object should be redrawn because its inner state changed.
|
||||
Refresh()
|
||||
}
|
||||
|
||||
// Disableable describes any CanvasObject that can be disabled.
|
||||
// This is primarily used with objects that also implement the Tappable interface.
|
||||
type Disableable interface {
|
||||
Enable()
|
||||
Disable()
|
||||
Disabled() bool
|
||||
}
|
||||
|
||||
// DoubleTappable describes any CanvasObject that can also be double tapped.
|
||||
type DoubleTappable interface {
|
||||
DoubleTapped(*PointEvent)
|
||||
}
|
||||
|
||||
// Draggable indicates that a CanvasObject can be dragged.
|
||||
// This is used for any item that the user has indicated should be moved across the screen.
|
||||
type Draggable interface {
|
||||
Dragged(*DragEvent)
|
||||
DragEnd()
|
||||
}
|
||||
|
||||
// Focusable describes any CanvasObject that can respond to being focused.
|
||||
// It will receive the FocusGained and FocusLost events appropriately.
|
||||
// When focused it will also have TypedRune called as text is input and
|
||||
// TypedKey called when other keys are pressed.
|
||||
//
|
||||
// Note: You must not change canvas state (including overlays or focus) in FocusGained or FocusLost
|
||||
// or you would end up with a dead-lock.
|
||||
type Focusable interface {
|
||||
// FocusGained is a hook called by the focus handling logic after this object gained the focus.
|
||||
FocusGained()
|
||||
// FocusLost is a hook called by the focus handling logic after this object lost the focus.
|
||||
FocusLost()
|
||||
|
||||
// TypedRune is a hook called by the input handling logic on text input events if this object is focused.
|
||||
TypedRune(rune)
|
||||
// TypedKey is a hook called by the input handling logic on key events if this object is focused.
|
||||
TypedKey(*KeyEvent)
|
||||
}
|
||||
|
||||
// Scrollable describes any CanvasObject that can also be scrolled.
|
||||
// This is mostly used to implement the widget.ScrollContainer.
|
||||
type Scrollable interface {
|
||||
Scrolled(*ScrollEvent)
|
||||
}
|
||||
|
||||
// SecondaryTappable describes a CanvasObject that can be right-clicked or long-tapped.
|
||||
type SecondaryTappable interface {
|
||||
TappedSecondary(*PointEvent)
|
||||
}
|
||||
|
||||
// Shortcutable describes any CanvasObject that can respond to shortcut commands (quit, cut, copy, and paste).
|
||||
type Shortcutable interface {
|
||||
TypedShortcut(Shortcut)
|
||||
}
|
||||
|
||||
// Tabbable describes any object that needs to accept the Tab key presses.
|
||||
//
|
||||
// Since: 2.1
|
||||
type Tabbable interface {
|
||||
// AcceptsTab() is a hook called by the key press handling logic.
|
||||
// If it returns true then the Tab key events will be sent using TypedKey.
|
||||
AcceptsTab() bool
|
||||
}
|
||||
|
||||
// Tappable describes any CanvasObject that can also be tapped.
|
||||
// This should be implemented by buttons etc that wish to handle pointer interactions.
|
||||
type Tappable interface {
|
||||
Tapped(*PointEvent)
|
||||
}
|
9
vendor/fyne.io/fyne/v2/clipboard.go
generated
vendored
9
vendor/fyne.io/fyne/v2/clipboard.go
generated
vendored
@@ -1,9 +0,0 @@
|
||||
package fyne
|
||||
|
||||
// Clipboard represents the system clipboard interface
|
||||
type Clipboard interface {
|
||||
// Content returns the clipboard content
|
||||
Content() string
|
||||
// SetContent sets the clipboard content
|
||||
SetContent(content string)
|
||||
}
|
187
vendor/fyne.io/fyne/v2/container.go
generated
vendored
187
vendor/fyne.io/fyne/v2/container.go
generated
vendored
@@ -1,187 +0,0 @@
|
||||
package fyne
|
||||
|
||||
// Declare conformity to CanvasObject
|
||||
var _ CanvasObject = (*Container)(nil)
|
||||
|
||||
// Container is a CanvasObject that contains a collection of child objects.
|
||||
// The layout of the children is set by the specified Layout.
|
||||
type Container struct {
|
||||
size Size // The current size of the Container
|
||||
position Position // The current position of the Container
|
||||
Hidden bool // Is this Container hidden
|
||||
|
||||
Layout Layout // The Layout algorithm for arranging child CanvasObjects
|
||||
Objects []CanvasObject // The set of CanvasObjects this container holds
|
||||
}
|
||||
|
||||
// NewContainer returns a new Container instance holding the specified CanvasObjects.
|
||||
//
|
||||
// Deprecated: Use container.NewWithoutLayout() to create a container that uses manual layout.
|
||||
func NewContainer(objects ...CanvasObject) *Container {
|
||||
return NewContainerWithoutLayout(objects...)
|
||||
}
|
||||
|
||||
// NewContainerWithoutLayout returns a new Container instance holding the specified
|
||||
// CanvasObjects that are manually arranged.
|
||||
//
|
||||
// Deprecated: Use container.NewWithoutLayout() instead
|
||||
func NewContainerWithoutLayout(objects ...CanvasObject) *Container {
|
||||
ret := &Container{
|
||||
Objects: objects,
|
||||
}
|
||||
|
||||
ret.size = ret.MinSize()
|
||||
return ret
|
||||
}
|
||||
|
||||
// NewContainerWithLayout returns a new Container instance holding the specified
|
||||
// CanvasObjects which will be laid out according to the specified Layout.
|
||||
//
|
||||
// Deprecated: Use container.New() instead
|
||||
func NewContainerWithLayout(layout Layout, objects ...CanvasObject) *Container {
|
||||
ret := &Container{
|
||||
Objects: objects,
|
||||
Layout: layout,
|
||||
}
|
||||
|
||||
ret.size = layout.MinSize(objects)
|
||||
ret.layout()
|
||||
return ret
|
||||
}
|
||||
|
||||
// Add appends the specified object to the items this container manages.
|
||||
//
|
||||
// Since: 1.4
|
||||
func (c *Container) Add(add CanvasObject) {
|
||||
if add == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.Objects = append(c.Objects, add)
|
||||
c.layout()
|
||||
}
|
||||
|
||||
// AddObject adds another CanvasObject to the set this Container holds.
|
||||
//
|
||||
// Deprecated: Use replacement Add() function
|
||||
func (c *Container) AddObject(o CanvasObject) {
|
||||
c.Add(o)
|
||||
}
|
||||
|
||||
// Hide sets this container, and all its children, to be not visible.
|
||||
func (c *Container) Hide() {
|
||||
if c.Hidden {
|
||||
return
|
||||
}
|
||||
|
||||
c.Hidden = true
|
||||
}
|
||||
|
||||
// MinSize calculates the minimum size of a Container.
|
||||
// This is delegated to the Layout, if specified, otherwise it will mimic MaxLayout.
|
||||
func (c *Container) MinSize() Size {
|
||||
if c.Layout != nil {
|
||||
return c.Layout.MinSize(c.Objects)
|
||||
}
|
||||
|
||||
minSize := NewSize(1, 1)
|
||||
for _, child := range c.Objects {
|
||||
minSize = minSize.Max(child.MinSize())
|
||||
}
|
||||
|
||||
return minSize
|
||||
}
|
||||
|
||||
// Move the container (and all its children) to a new position, relative to its parent.
|
||||
func (c *Container) Move(pos Position) {
|
||||
c.position = pos
|
||||
}
|
||||
|
||||
// Position gets the current position of this Container, relative to its parent.
|
||||
func (c *Container) Position() Position {
|
||||
return c.position
|
||||
}
|
||||
|
||||
// Refresh causes this object to be redrawn in it's current state
|
||||
func (c *Container) Refresh() {
|
||||
c.layout()
|
||||
|
||||
for _, child := range c.Objects {
|
||||
child.Refresh()
|
||||
}
|
||||
|
||||
// this is basically just canvas.Refresh(c) without the package loop
|
||||
o := CurrentApp().Driver().CanvasForObject(c)
|
||||
if o == nil {
|
||||
return
|
||||
}
|
||||
o.Refresh(c)
|
||||
}
|
||||
|
||||
// Remove updates the contents of this container to no longer include the specified object.
|
||||
// This method is not intended to be used inside a loop, to remove all the elements.
|
||||
// It is much more efficient to call RemoveAll() instead.
|
||||
func (c *Container) Remove(rem CanvasObject) {
|
||||
if len(c.Objects) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
for i, o := range c.Objects {
|
||||
if o != rem {
|
||||
continue
|
||||
}
|
||||
|
||||
removed := make([]CanvasObject, len(c.Objects)-1)
|
||||
copy(removed, c.Objects[:i])
|
||||
copy(removed[i:], c.Objects[i+1:])
|
||||
|
||||
c.Objects = removed
|
||||
c.layout()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// RemoveAll updates the contents of this container to no longer include any objects.
|
||||
//
|
||||
// Since: 2.2
|
||||
func (c *Container) RemoveAll() {
|
||||
c.Objects = nil
|
||||
c.layout()
|
||||
}
|
||||
|
||||
// Resize sets a new size for the Container.
|
||||
func (c *Container) Resize(size Size) {
|
||||
if c.size == size {
|
||||
return
|
||||
}
|
||||
|
||||
c.size = size
|
||||
c.layout()
|
||||
}
|
||||
|
||||
// Show sets this container, and all its children, to be visible.
|
||||
func (c *Container) Show() {
|
||||
if !c.Hidden {
|
||||
return
|
||||
}
|
||||
|
||||
c.Hidden = false
|
||||
}
|
||||
|
||||
// Size returns the current size of this container.
|
||||
func (c *Container) Size() Size {
|
||||
return c.size
|
||||
}
|
||||
|
||||
// Visible returns true if the container is currently visible, false otherwise.
|
||||
func (c *Container) Visible() bool {
|
||||
return !c.Hidden
|
||||
}
|
||||
|
||||
func (c *Container) layout() {
|
||||
if c.Layout == nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.Layout.Layout(c.Objects, c.size)
|
||||
}
|
433
vendor/fyne.io/fyne/v2/container/apptabs.go
generated
vendored
433
vendor/fyne.io/fyne/v2/container/apptabs.go
generated
vendored
@@ -1,433 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// Declare conformity with Widget interface.
|
||||
var _ fyne.Widget = (*AppTabs)(nil)
|
||||
|
||||
// AppTabs container is used to split your application into various different areas identified by tabs.
|
||||
// The tabs contain text and/or an icon and allow the user to switch between the content specified in each TabItem.
|
||||
// Each item is represented by a button at the edge of the container.
|
||||
//
|
||||
// Since: 1.4
|
||||
type AppTabs struct {
|
||||
widget.BaseWidget
|
||||
|
||||
Items []*TabItem
|
||||
|
||||
// Deprecated: Use `OnSelected func(*TabItem)` instead.
|
||||
OnChanged func(*TabItem)
|
||||
OnSelected func(*TabItem)
|
||||
OnUnselected func(*TabItem)
|
||||
|
||||
current int
|
||||
location TabLocation
|
||||
isTransitioning bool
|
||||
|
||||
popUpMenu *widget.PopUpMenu
|
||||
}
|
||||
|
||||
// NewAppTabs creates a new tab container that allows the user to choose between different areas of an app.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewAppTabs(items ...*TabItem) *AppTabs {
|
||||
tabs := &AppTabs{}
|
||||
tabs.BaseWidget.ExtendBaseWidget(tabs)
|
||||
tabs.SetItems(items)
|
||||
return tabs
|
||||
}
|
||||
|
||||
// CreateRenderer is a private method to Fyne which links this widget to its renderer
|
||||
//
|
||||
// Implements: fyne.Widget
|
||||
func (t *AppTabs) CreateRenderer() fyne.WidgetRenderer {
|
||||
t.BaseWidget.ExtendBaseWidget(t)
|
||||
r := &appTabsRenderer{
|
||||
baseTabsRenderer: baseTabsRenderer{
|
||||
bar: &fyne.Container{},
|
||||
divider: canvas.NewRectangle(theme.ShadowColor()),
|
||||
indicator: canvas.NewRectangle(theme.PrimaryColor()),
|
||||
buttonCache: make(map[*TabItem]*tabButton),
|
||||
},
|
||||
appTabs: t,
|
||||
}
|
||||
r.action = r.buildOverflowTabsButton()
|
||||
|
||||
// Initially setup the tab bar to only show one tab, all others will be in overflow.
|
||||
// When the widget is laid out, and we know the size, the tab bar will be updated to show as many as can fit.
|
||||
r.updateTabs(1)
|
||||
r.updateIndicator(false)
|
||||
r.applyTheme(t)
|
||||
return r
|
||||
}
|
||||
|
||||
// Append adds a new TabItem to the end of the tab bar.
|
||||
func (t *AppTabs) Append(item *TabItem) {
|
||||
t.SetItems(append(t.Items, item))
|
||||
}
|
||||
|
||||
// CurrentTab returns the currently selected TabItem.
|
||||
//
|
||||
// Deprecated: Use `AppTabs.Selected() *TabItem` instead.
|
||||
func (t *AppTabs) CurrentTab() *TabItem {
|
||||
if t.current < 0 || t.current >= len(t.Items) {
|
||||
return nil
|
||||
}
|
||||
return t.Items[t.current]
|
||||
}
|
||||
|
||||
// CurrentTabIndex returns the index of the currently selected TabItem.
|
||||
//
|
||||
// Deprecated: Use `AppTabs.SelectedIndex() int` instead.
|
||||
func (t *AppTabs) CurrentTabIndex() int {
|
||||
return t.current
|
||||
}
|
||||
|
||||
// ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality.
|
||||
//
|
||||
// Deprecated: Support for extending containers is being removed
|
||||
func (t *AppTabs) ExtendBaseWidget(wid fyne.Widget) {
|
||||
t.BaseWidget.ExtendBaseWidget(wid)
|
||||
}
|
||||
|
||||
// Hide hides the widget.
|
||||
//
|
||||
// Implements: fyne.CanvasObject
|
||||
func (t *AppTabs) Hide() {
|
||||
if t.popUpMenu != nil {
|
||||
t.popUpMenu.Hide()
|
||||
t.popUpMenu = nil
|
||||
}
|
||||
t.BaseWidget.Hide()
|
||||
}
|
||||
|
||||
// MinSize returns the size that this widget should not shrink below
|
||||
//
|
||||
// Implements: fyne.CanvasObject
|
||||
func (t *AppTabs) MinSize() fyne.Size {
|
||||
t.BaseWidget.ExtendBaseWidget(t)
|
||||
return t.BaseWidget.MinSize()
|
||||
}
|
||||
|
||||
// Remove tab by value.
|
||||
func (t *AppTabs) Remove(item *TabItem) {
|
||||
removeItem(t, item)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// RemoveIndex removes tab by index.
|
||||
func (t *AppTabs) RemoveIndex(index int) {
|
||||
removeIndex(t, index)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// Select sets the specified TabItem to be selected and its content visible.
|
||||
func (t *AppTabs) Select(item *TabItem) {
|
||||
selectItem(t, item)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// SelectIndex sets the TabItem at the specific index to be selected and its content visible.
|
||||
func (t *AppTabs) SelectIndex(index int) {
|
||||
selectIndex(t, index)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// SelectTab sets the specified TabItem to be selected and its content visible.
|
||||
//
|
||||
// Deprecated: Use `AppTabs.Select(*TabItem)` instead.
|
||||
func (t *AppTabs) SelectTab(item *TabItem) {
|
||||
for i, child := range t.Items {
|
||||
if child == item {
|
||||
t.SelectTabIndex(i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SelectTabIndex sets the TabItem at the specific index to be selected and its content visible.
|
||||
//
|
||||
// Deprecated: Use `AppTabs.SelectIndex(int)` instead.
|
||||
func (t *AppTabs) SelectTabIndex(index int) {
|
||||
if index < 0 || index >= len(t.Items) || t.current == index {
|
||||
return
|
||||
}
|
||||
t.current = index
|
||||
t.Refresh()
|
||||
|
||||
if t.OnChanged != nil {
|
||||
t.OnChanged(t.Items[t.current])
|
||||
}
|
||||
}
|
||||
|
||||
// Selected returns the currently selected TabItem.
|
||||
func (t *AppTabs) Selected() *TabItem {
|
||||
return selected(t)
|
||||
}
|
||||
|
||||
// SelectedIndex returns the index of the currently selected TabItem.
|
||||
func (t *AppTabs) SelectedIndex() int {
|
||||
return t.current
|
||||
}
|
||||
|
||||
// SetItems sets the containers items and refreshes.
|
||||
func (t *AppTabs) SetItems(items []*TabItem) {
|
||||
setItems(t, items)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// SetTabLocation sets the location of the tab bar
|
||||
func (t *AppTabs) SetTabLocation(l TabLocation) {
|
||||
t.location = tabsAdjustedLocation(l)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// Show this widget, if it was previously hidden
|
||||
//
|
||||
// Implements: fyne.CanvasObject
|
||||
func (t *AppTabs) Show() {
|
||||
t.BaseWidget.Show()
|
||||
t.SelectIndex(t.current)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
func (t *AppTabs) onUnselected() func(*TabItem) {
|
||||
return t.OnUnselected
|
||||
}
|
||||
|
||||
func (t *AppTabs) onSelected() func(*TabItem) {
|
||||
return func(tab *TabItem) {
|
||||
if f := t.OnChanged; f != nil {
|
||||
f(tab)
|
||||
}
|
||||
if f := t.OnSelected; f != nil {
|
||||
f(tab)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *AppTabs) items() []*TabItem {
|
||||
return t.Items
|
||||
}
|
||||
|
||||
func (t *AppTabs) selected() int {
|
||||
return t.current
|
||||
}
|
||||
|
||||
func (t *AppTabs) setItems(items []*TabItem) {
|
||||
t.Items = items
|
||||
}
|
||||
|
||||
func (t *AppTabs) setSelected(selected int) {
|
||||
t.current = selected
|
||||
}
|
||||
|
||||
func (t *AppTabs) setTransitioning(transitioning bool) {
|
||||
t.isTransitioning = transitioning
|
||||
}
|
||||
|
||||
func (t *AppTabs) tabLocation() TabLocation {
|
||||
return t.location
|
||||
}
|
||||
|
||||
func (t *AppTabs) transitioning() bool {
|
||||
return t.isTransitioning
|
||||
}
|
||||
|
||||
// Declare conformity with WidgetRenderer interface.
|
||||
var _ fyne.WidgetRenderer = (*appTabsRenderer)(nil)
|
||||
|
||||
type appTabsRenderer struct {
|
||||
baseTabsRenderer
|
||||
appTabs *AppTabs
|
||||
}
|
||||
|
||||
func (r *appTabsRenderer) Layout(size fyne.Size) {
|
||||
// Try render as many tabs as will fit, others will appear in the overflow
|
||||
for i := len(r.appTabs.Items); i > 0; i-- {
|
||||
r.updateTabs(i)
|
||||
barMin := r.bar.MinSize()
|
||||
if r.appTabs.location == TabLocationLeading || r.appTabs.location == TabLocationTrailing {
|
||||
if barMin.Height <= size.Height {
|
||||
// Tab bar is short enough to fit
|
||||
break
|
||||
}
|
||||
} else {
|
||||
if barMin.Width <= size.Width {
|
||||
// Tab bar is thin enough to fit
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.layout(r.appTabs, size)
|
||||
r.updateIndicator(r.appTabs.transitioning())
|
||||
if r.appTabs.transitioning() {
|
||||
r.appTabs.setTransitioning(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *appTabsRenderer) MinSize() fyne.Size {
|
||||
return r.minSize(r.appTabs)
|
||||
}
|
||||
|
||||
func (r *appTabsRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects(r.appTabs)
|
||||
}
|
||||
|
||||
func (r *appTabsRenderer) Refresh() {
|
||||
r.Layout(r.appTabs.Size())
|
||||
|
||||
r.refresh(r.appTabs)
|
||||
|
||||
canvas.Refresh(r.appTabs)
|
||||
}
|
||||
|
||||
func (r *appTabsRenderer) buildOverflowTabsButton() (overflow *widget.Button) {
|
||||
overflow = &widget.Button{Icon: moreIcon(r.appTabs), Importance: widget.LowImportance, OnTapped: func() {
|
||||
// Show pop up containing all tabs which did not fit in the tab bar
|
||||
|
||||
itemLen, objLen := len(r.appTabs.Items), len(r.bar.Objects[0].(*fyne.Container).Objects)
|
||||
items := make([]*fyne.MenuItem, 0, itemLen-objLen)
|
||||
for i := objLen; i < itemLen; i++ {
|
||||
index := i // capture
|
||||
// FIXME MenuItem doesn't support icons (#1752)
|
||||
// FIXME MenuItem can't show if it is the currently selected tab (#1753)
|
||||
items = append(items, fyne.NewMenuItem(r.appTabs.Items[i].Text, func() {
|
||||
r.appTabs.SelectIndex(index)
|
||||
if r.appTabs.popUpMenu != nil {
|
||||
r.appTabs.popUpMenu.Hide()
|
||||
r.appTabs.popUpMenu = nil
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
r.appTabs.popUpMenu = buildPopUpMenu(r.appTabs, overflow, items)
|
||||
}}
|
||||
|
||||
return overflow
|
||||
}
|
||||
|
||||
func (r *appTabsRenderer) buildTabButtons(count int) *fyne.Container {
|
||||
buttons := &fyne.Container{}
|
||||
|
||||
var iconPos buttonIconPosition
|
||||
if fyne.CurrentDevice().IsMobile() {
|
||||
cells := count
|
||||
if cells == 0 {
|
||||
cells = 1
|
||||
}
|
||||
if r.appTabs.location == TabLocationTop || r.appTabs.location == TabLocationBottom {
|
||||
buttons.Layout = layout.NewGridLayoutWithColumns(cells)
|
||||
} else {
|
||||
buttons.Layout = layout.NewGridLayoutWithRows(cells)
|
||||
}
|
||||
iconPos = buttonIconTop
|
||||
} else if r.appTabs.location == TabLocationLeading || r.appTabs.location == TabLocationTrailing {
|
||||
buttons.Layout = layout.NewVBoxLayout()
|
||||
iconPos = buttonIconTop
|
||||
} else {
|
||||
buttons.Layout = layout.NewHBoxLayout()
|
||||
iconPos = buttonIconInline
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
item := r.appTabs.Items[i]
|
||||
button, ok := r.buttonCache[item]
|
||||
if !ok {
|
||||
button = &tabButton{
|
||||
onTapped: func() { r.appTabs.Select(item) },
|
||||
}
|
||||
r.buttonCache[item] = button
|
||||
}
|
||||
button.icon = item.Icon
|
||||
button.iconPosition = iconPos
|
||||
if i == r.appTabs.current {
|
||||
button.importance = widget.HighImportance
|
||||
} else {
|
||||
button.importance = widget.MediumImportance
|
||||
}
|
||||
button.text = item.Text
|
||||
button.textAlignment = fyne.TextAlignCenter
|
||||
button.Refresh()
|
||||
buttons.Objects = append(buttons.Objects, button)
|
||||
}
|
||||
return buttons
|
||||
}
|
||||
|
||||
func (r *appTabsRenderer) updateIndicator(animate bool) {
|
||||
if r.appTabs.current < 0 {
|
||||
r.indicator.Hide()
|
||||
return
|
||||
}
|
||||
|
||||
var selectedPos fyne.Position
|
||||
var selectedSize fyne.Size
|
||||
|
||||
buttons := r.bar.Objects[0].(*fyne.Container).Objects
|
||||
if r.appTabs.current >= len(buttons) {
|
||||
if a := r.action; a != nil {
|
||||
selectedPos = a.Position()
|
||||
selectedSize = a.Size()
|
||||
}
|
||||
} else {
|
||||
selected := buttons[r.appTabs.current]
|
||||
selectedPos = selected.Position()
|
||||
selectedSize = selected.Size()
|
||||
}
|
||||
|
||||
var indicatorPos fyne.Position
|
||||
var indicatorSize fyne.Size
|
||||
|
||||
switch r.appTabs.location {
|
||||
case TabLocationTop:
|
||||
indicatorPos = fyne.NewPos(selectedPos.X, r.bar.MinSize().Height)
|
||||
indicatorSize = fyne.NewSize(selectedSize.Width, theme.Padding())
|
||||
case TabLocationLeading:
|
||||
indicatorPos = fyne.NewPos(r.bar.MinSize().Width, selectedPos.Y)
|
||||
indicatorSize = fyne.NewSize(theme.Padding(), selectedSize.Height)
|
||||
case TabLocationBottom:
|
||||
indicatorPos = fyne.NewPos(selectedPos.X, r.bar.Position().Y-theme.Padding())
|
||||
indicatorSize = fyne.NewSize(selectedSize.Width, theme.Padding())
|
||||
case TabLocationTrailing:
|
||||
indicatorPos = fyne.NewPos(r.bar.Position().X-theme.Padding(), selectedPos.Y)
|
||||
indicatorSize = fyne.NewSize(theme.Padding(), selectedSize.Height)
|
||||
}
|
||||
|
||||
r.moveIndicator(indicatorPos, indicatorSize, animate)
|
||||
}
|
||||
|
||||
func (r *appTabsRenderer) updateTabs(max int) {
|
||||
tabCount := len(r.appTabs.Items)
|
||||
|
||||
// Set overflow action
|
||||
if tabCount <= max {
|
||||
r.action.Hide()
|
||||
r.bar.Layout = layout.NewMaxLayout()
|
||||
} else {
|
||||
tabCount = max
|
||||
r.action.Show()
|
||||
|
||||
// Set layout of tab bar containing tab buttons and overflow action
|
||||
if r.appTabs.location == TabLocationLeading || r.appTabs.location == TabLocationTrailing {
|
||||
r.bar.Layout = layout.NewBorderLayout(nil, r.action, nil, nil)
|
||||
} else {
|
||||
r.bar.Layout = layout.NewBorderLayout(nil, nil, nil, r.action)
|
||||
}
|
||||
}
|
||||
|
||||
buttons := r.buildTabButtons(tabCount)
|
||||
|
||||
r.bar.Objects = []fyne.CanvasObject{buttons}
|
||||
if a := r.action; a != nil {
|
||||
r.bar.Objects = append(r.bar.Objects, a)
|
||||
}
|
||||
|
||||
r.bar.Refresh()
|
||||
}
|
20
vendor/fyne.io/fyne/v2/container/container.go
generated
vendored
20
vendor/fyne.io/fyne/v2/container/container.go
generated
vendored
@@ -1,20 +0,0 @@
|
||||
// Package container provides containers that are used to lay out and organise applications.
|
||||
package container
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// New returns a new Container instance holding the specified CanvasObjects which will be laid out according to the specified Layout.
|
||||
//
|
||||
// Since: 2.0
|
||||
func New(layout fyne.Layout, objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return fyne.NewContainerWithLayout(layout, objects...)
|
||||
}
|
||||
|
||||
// NewWithoutLayout returns a new Container instance holding the specified CanvasObjects that are manually arranged.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewWithoutLayout(objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return fyne.NewContainerWithoutLayout(objects...)
|
||||
}
|
452
vendor/fyne.io/fyne/v2/container/doctabs.go
generated
vendored
452
vendor/fyne.io/fyne/v2/container/doctabs.go
generated
vendored
@@ -1,452 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// Declare conformity with Widget interface.
|
||||
var _ fyne.Widget = (*DocTabs)(nil)
|
||||
|
||||
// DocTabs container is used to display various pieces of content identified by tabs.
|
||||
// The tabs contain text and/or an icon and allow the user to switch between the content specified in each TabItem.
|
||||
// Each item is represented by a button at the edge of the container.
|
||||
//
|
||||
// Since: 2.1
|
||||
type DocTabs struct {
|
||||
widget.BaseWidget
|
||||
|
||||
Items []*TabItem
|
||||
|
||||
CreateTab func() *TabItem
|
||||
CloseIntercept func(*TabItem)
|
||||
OnClosed func(*TabItem)
|
||||
OnSelected func(*TabItem)
|
||||
OnUnselected func(*TabItem)
|
||||
|
||||
current int
|
||||
location TabLocation
|
||||
isTransitioning bool
|
||||
|
||||
popUpMenu *widget.PopUpMenu
|
||||
}
|
||||
|
||||
// NewDocTabs creates a new tab container that allows the user to choose between various pieces of content.
|
||||
//
|
||||
// Since: 2.1
|
||||
func NewDocTabs(items ...*TabItem) *DocTabs {
|
||||
tabs := &DocTabs{}
|
||||
tabs.ExtendBaseWidget(tabs)
|
||||
tabs.SetItems(items)
|
||||
return tabs
|
||||
}
|
||||
|
||||
// Append adds a new TabItem to the end of the tab bar.
|
||||
func (t *DocTabs) Append(item *TabItem) {
|
||||
t.SetItems(append(t.Items, item))
|
||||
}
|
||||
|
||||
// CreateRenderer is a private method to Fyne which links this widget to its renderer
|
||||
//
|
||||
// Implements: fyne.Widget
|
||||
func (t *DocTabs) CreateRenderer() fyne.WidgetRenderer {
|
||||
t.ExtendBaseWidget(t)
|
||||
r := &docTabsRenderer{
|
||||
baseTabsRenderer: baseTabsRenderer{
|
||||
bar: &fyne.Container{},
|
||||
divider: canvas.NewRectangle(theme.ShadowColor()),
|
||||
indicator: canvas.NewRectangle(theme.PrimaryColor()),
|
||||
buttonCache: make(map[*TabItem]*tabButton),
|
||||
},
|
||||
docTabs: t,
|
||||
scroller: NewScroll(&fyne.Container{}),
|
||||
}
|
||||
r.action = r.buildAllTabsButton()
|
||||
r.create = r.buildCreateTabsButton()
|
||||
r.box = NewHBox(r.create, r.action)
|
||||
r.scroller.OnScrolled = func(offset fyne.Position) {
|
||||
r.updateIndicator(false)
|
||||
}
|
||||
r.updateAllTabs()
|
||||
r.updateCreateTab()
|
||||
r.updateTabs()
|
||||
r.updateIndicator(false)
|
||||
r.applyTheme(t)
|
||||
return r
|
||||
}
|
||||
|
||||
// Hide hides the widget.
|
||||
//
|
||||
// Implements: fyne.CanvasObject
|
||||
func (t *DocTabs) Hide() {
|
||||
if t.popUpMenu != nil {
|
||||
t.popUpMenu.Hide()
|
||||
t.popUpMenu = nil
|
||||
}
|
||||
t.BaseWidget.Hide()
|
||||
}
|
||||
|
||||
// MinSize returns the size that this widget should not shrink below
|
||||
//
|
||||
// Implements: fyne.CanvasObject
|
||||
func (t *DocTabs) MinSize() fyne.Size {
|
||||
t.ExtendBaseWidget(t)
|
||||
return t.BaseWidget.MinSize()
|
||||
}
|
||||
|
||||
// Remove tab by value.
|
||||
func (t *DocTabs) Remove(item *TabItem) {
|
||||
removeItem(t, item)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// RemoveIndex removes tab by index.
|
||||
func (t *DocTabs) RemoveIndex(index int) {
|
||||
removeIndex(t, index)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// Select sets the specified TabItem to be selected and its content visible.
|
||||
func (t *DocTabs) Select(item *TabItem) {
|
||||
selectItem(t, item)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// SelectIndex sets the TabItem at the specific index to be selected and its content visible.
|
||||
func (t *DocTabs) SelectIndex(index int) {
|
||||
selectIndex(t, index)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// Selected returns the currently selected TabItem.
|
||||
func (t *DocTabs) Selected() *TabItem {
|
||||
return selected(t)
|
||||
}
|
||||
|
||||
// SelectedIndex returns the index of the currently selected TabItem.
|
||||
func (t *DocTabs) SelectedIndex() int {
|
||||
return t.current
|
||||
}
|
||||
|
||||
// SetItems sets the containers items and refreshes.
|
||||
func (t *DocTabs) SetItems(items []*TabItem) {
|
||||
setItems(t, items)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// SetTabLocation sets the location of the tab bar
|
||||
func (t *DocTabs) SetTabLocation(l TabLocation) {
|
||||
t.location = tabsAdjustedLocation(l)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
// Show this widget, if it was previously hidden
|
||||
//
|
||||
// Implements: fyne.CanvasObject
|
||||
func (t *DocTabs) Show() {
|
||||
t.BaseWidget.Show()
|
||||
t.SelectIndex(t.current)
|
||||
t.Refresh()
|
||||
}
|
||||
|
||||
func (t *DocTabs) close(item *TabItem) {
|
||||
if f := t.CloseIntercept; f != nil {
|
||||
f(item)
|
||||
} else {
|
||||
t.Remove(item)
|
||||
if f := t.OnClosed; f != nil {
|
||||
f(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *DocTabs) onUnselected() func(*TabItem) {
|
||||
return t.OnUnselected
|
||||
}
|
||||
|
||||
func (t *DocTabs) onSelected() func(*TabItem) {
|
||||
return t.OnSelected
|
||||
}
|
||||
|
||||
func (t *DocTabs) items() []*TabItem {
|
||||
return t.Items
|
||||
}
|
||||
|
||||
func (t *DocTabs) selected() int {
|
||||
return t.current
|
||||
}
|
||||
|
||||
func (t *DocTabs) setItems(items []*TabItem) {
|
||||
t.Items = items
|
||||
}
|
||||
|
||||
func (t *DocTabs) setSelected(selected int) {
|
||||
t.current = selected
|
||||
}
|
||||
|
||||
func (t *DocTabs) setTransitioning(transitioning bool) {
|
||||
t.isTransitioning = transitioning
|
||||
}
|
||||
|
||||
func (t *DocTabs) tabLocation() TabLocation {
|
||||
return t.location
|
||||
}
|
||||
|
||||
func (t *DocTabs) transitioning() bool {
|
||||
return t.isTransitioning
|
||||
}
|
||||
|
||||
// Declare conformity with WidgetRenderer interface.
|
||||
var _ fyne.WidgetRenderer = (*docTabsRenderer)(nil)
|
||||
|
||||
type docTabsRenderer struct {
|
||||
baseTabsRenderer
|
||||
docTabs *DocTabs
|
||||
scroller *Scroll
|
||||
box *fyne.Container
|
||||
create *widget.Button
|
||||
lastSelected int
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) Layout(size fyne.Size) {
|
||||
r.updateAllTabs()
|
||||
r.updateCreateTab()
|
||||
r.updateTabs()
|
||||
r.layout(r.docTabs, size)
|
||||
r.updateIndicator(r.docTabs.transitioning())
|
||||
if r.docTabs.transitioning() {
|
||||
r.docTabs.setTransitioning(false)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) MinSize() fyne.Size {
|
||||
return r.minSize(r.docTabs)
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects(r.docTabs)
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) Refresh() {
|
||||
r.Layout(r.docTabs.Size())
|
||||
|
||||
if c := r.docTabs.current; c != r.lastSelected {
|
||||
if c >= 0 && c < len(r.docTabs.Items) {
|
||||
r.scrollToSelected()
|
||||
}
|
||||
r.lastSelected = c
|
||||
}
|
||||
|
||||
r.refresh(r.docTabs)
|
||||
|
||||
canvas.Refresh(r.docTabs)
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) buildAllTabsButton() (all *widget.Button) {
|
||||
all = &widget.Button{Importance: widget.LowImportance, OnTapped: func() {
|
||||
// Show pop up containing all tabs
|
||||
|
||||
items := make([]*fyne.MenuItem, len(r.docTabs.Items))
|
||||
for i := 0; i < len(r.docTabs.Items); i++ {
|
||||
index := i // capture
|
||||
// FIXME MenuItem doesn't support icons (#1752)
|
||||
items[i] = fyne.NewMenuItem(r.docTabs.Items[i].Text, func() {
|
||||
r.docTabs.SelectIndex(index)
|
||||
if r.docTabs.popUpMenu != nil {
|
||||
r.docTabs.popUpMenu.Hide()
|
||||
r.docTabs.popUpMenu = nil
|
||||
}
|
||||
})
|
||||
items[i].Checked = index == r.docTabs.current
|
||||
}
|
||||
|
||||
r.docTabs.popUpMenu = buildPopUpMenu(r.docTabs, all, items)
|
||||
}}
|
||||
|
||||
return all
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) buildCreateTabsButton() *widget.Button {
|
||||
create := widget.NewButton("", func() {
|
||||
if f := r.docTabs.CreateTab; f != nil {
|
||||
if tab := f(); tab != nil {
|
||||
r.docTabs.Append(tab)
|
||||
r.docTabs.SelectIndex(len(r.docTabs.Items) - 1)
|
||||
}
|
||||
}
|
||||
})
|
||||
create.Importance = widget.LowImportance
|
||||
return create
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) buildTabButtons(count int, buttons *fyne.Container) {
|
||||
buttons.Objects = nil
|
||||
|
||||
var iconPos buttonIconPosition
|
||||
if fyne.CurrentDevice().IsMobile() {
|
||||
cells := count
|
||||
if cells == 0 {
|
||||
cells = 1
|
||||
}
|
||||
if r.docTabs.location == TabLocationTop || r.docTabs.location == TabLocationBottom {
|
||||
buttons.Layout = layout.NewGridLayoutWithColumns(cells)
|
||||
} else {
|
||||
buttons.Layout = layout.NewGridLayoutWithRows(cells)
|
||||
}
|
||||
iconPos = buttonIconTop
|
||||
} else if r.docTabs.location == TabLocationLeading || r.docTabs.location == TabLocationTrailing {
|
||||
buttons.Layout = layout.NewVBoxLayout()
|
||||
iconPos = buttonIconTop
|
||||
} else {
|
||||
buttons.Layout = layout.NewHBoxLayout()
|
||||
iconPos = buttonIconInline
|
||||
}
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
item := r.docTabs.Items[i]
|
||||
button, ok := r.buttonCache[item]
|
||||
if !ok {
|
||||
button = &tabButton{
|
||||
onTapped: func() { r.docTabs.Select(item) },
|
||||
onClosed: func() { r.docTabs.close(item) },
|
||||
}
|
||||
r.buttonCache[item] = button
|
||||
}
|
||||
|
||||
button.icon = item.Icon
|
||||
button.iconPosition = iconPos
|
||||
if i == r.docTabs.current {
|
||||
button.importance = widget.HighImportance
|
||||
} else {
|
||||
button.importance = widget.MediumImportance
|
||||
}
|
||||
button.text = item.Text
|
||||
button.textAlignment = fyne.TextAlignLeading
|
||||
button.Refresh()
|
||||
buttons.Objects = append(buttons.Objects, button)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) scrollToSelected() {
|
||||
buttons := r.scroller.Content.(*fyne.Container)
|
||||
button := buttons.Objects[r.docTabs.current]
|
||||
pos := button.Position()
|
||||
size := button.Size()
|
||||
offset := r.scroller.Offset
|
||||
viewport := r.scroller.Size()
|
||||
if r.docTabs.location == TabLocationLeading || r.docTabs.location == TabLocationTrailing {
|
||||
if pos.Y < offset.Y {
|
||||
offset.Y = pos.Y
|
||||
} else if pos.Y+size.Height > offset.Y+viewport.Height {
|
||||
offset.Y = pos.Y + size.Height - viewport.Height
|
||||
}
|
||||
} else {
|
||||
if pos.X < offset.X {
|
||||
offset.X = pos.X
|
||||
} else if pos.X+size.Width > offset.X+viewport.Width {
|
||||
offset.X = pos.X + size.Width - viewport.Width
|
||||
}
|
||||
}
|
||||
r.scroller.Offset = offset
|
||||
r.updateIndicator(false)
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) updateIndicator(animate bool) {
|
||||
if r.docTabs.current < 0 {
|
||||
r.indicator.FillColor = color.Transparent
|
||||
r.indicator.Refresh()
|
||||
return
|
||||
}
|
||||
|
||||
var selectedPos fyne.Position
|
||||
var selectedSize fyne.Size
|
||||
|
||||
buttons := r.scroller.Content.(*fyne.Container).Objects
|
||||
|
||||
if r.docTabs.current >= len(buttons) {
|
||||
if a := r.action; a != nil {
|
||||
selectedPos = a.Position()
|
||||
selectedSize = a.Size()
|
||||
}
|
||||
} else {
|
||||
selected := buttons[r.docTabs.current]
|
||||
selectedPos = selected.Position()
|
||||
selectedSize = selected.Size()
|
||||
}
|
||||
|
||||
scrollOffset := r.scroller.Offset
|
||||
scrollSize := r.scroller.Size()
|
||||
|
||||
var indicatorPos fyne.Position
|
||||
var indicatorSize fyne.Size
|
||||
|
||||
switch r.docTabs.location {
|
||||
case TabLocationTop:
|
||||
indicatorPos = fyne.NewPos(selectedPos.X-scrollOffset.X, r.bar.MinSize().Height)
|
||||
indicatorSize = fyne.NewSize(fyne.Min(selectedSize.Width, scrollSize.Width-indicatorPos.X), theme.Padding())
|
||||
case TabLocationLeading:
|
||||
indicatorPos = fyne.NewPos(r.bar.MinSize().Width, selectedPos.Y-scrollOffset.Y)
|
||||
indicatorSize = fyne.NewSize(theme.Padding(), fyne.Min(selectedSize.Height, scrollSize.Height-indicatorPos.Y))
|
||||
case TabLocationBottom:
|
||||
indicatorPos = fyne.NewPos(selectedPos.X-scrollOffset.X, r.bar.Position().Y-theme.Padding())
|
||||
indicatorSize = fyne.NewSize(fyne.Min(selectedSize.Width, scrollSize.Width-indicatorPos.X), theme.Padding())
|
||||
case TabLocationTrailing:
|
||||
indicatorPos = fyne.NewPos(r.bar.Position().X-theme.Padding(), selectedPos.Y-scrollOffset.Y)
|
||||
indicatorSize = fyne.NewSize(theme.Padding(), fyne.Min(selectedSize.Height, scrollSize.Height-indicatorPos.Y))
|
||||
}
|
||||
|
||||
if indicatorPos.X < 0 {
|
||||
indicatorSize.Width = indicatorSize.Width + indicatorPos.X
|
||||
indicatorPos.X = 0
|
||||
}
|
||||
if indicatorPos.Y < 0 {
|
||||
indicatorSize.Height = indicatorSize.Height + indicatorPos.Y
|
||||
indicatorPos.Y = 0
|
||||
}
|
||||
if indicatorSize.Width < 0 || indicatorSize.Height < 0 {
|
||||
r.indicator.FillColor = color.Transparent
|
||||
r.indicator.Refresh()
|
||||
return
|
||||
}
|
||||
|
||||
r.moveIndicator(indicatorPos, indicatorSize, animate)
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) updateAllTabs() {
|
||||
if len(r.docTabs.Items) > 0 {
|
||||
r.action.Show()
|
||||
} else {
|
||||
r.action.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) updateCreateTab() {
|
||||
if r.docTabs.CreateTab != nil {
|
||||
r.create.SetIcon(theme.ContentAddIcon())
|
||||
r.create.Show()
|
||||
} else {
|
||||
r.create.Hide()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *docTabsRenderer) updateTabs() {
|
||||
tabCount := len(r.docTabs.Items)
|
||||
r.buildTabButtons(tabCount, r.scroller.Content.(*fyne.Container))
|
||||
|
||||
// Set layout of tab bar containing tab buttons and overflow action
|
||||
if r.docTabs.location == TabLocationLeading || r.docTabs.location == TabLocationTrailing {
|
||||
r.bar.Layout = layout.NewBorderLayout(nil, r.box, nil, nil)
|
||||
r.scroller.Direction = ScrollVerticalOnly
|
||||
} else {
|
||||
r.bar.Layout = layout.NewBorderLayout(nil, nil, nil, r.box)
|
||||
r.scroller.Direction = ScrollHorizontalOnly
|
||||
}
|
||||
|
||||
r.bar.Objects = []fyne.CanvasObject{r.scroller, r.box}
|
||||
r.bar.Refresh()
|
||||
}
|
99
vendor/fyne.io/fyne/v2/container/layouts.go
generated
vendored
99
vendor/fyne.io/fyne/v2/container/layouts.go
generated
vendored
@@ -1,99 +0,0 @@
|
||||
package container // import "fyne.io/fyne/v2/container"
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
)
|
||||
|
||||
// NewAdaptiveGrid creates a new container with the specified objects and using the grid layout.
|
||||
// When in a horizontal arrangement the rowcols parameter will specify the column count, when in vertical
|
||||
// it will specify the rows. On mobile this will dynamically refresh when device is rotated.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewAdaptiveGrid(rowcols int, objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewAdaptiveGridLayout(rowcols), objects...)
|
||||
}
|
||||
|
||||
// NewBorder creates a new container with the specified objects and using the border layout.
|
||||
// The top, bottom, left and right parameters specify the items that should be placed around edges,
|
||||
// the remaining elements will be in the center. Nil can be used to an edge if it should not be filled.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewBorder(top, bottom, left, right fyne.CanvasObject, objects ...fyne.CanvasObject) *fyne.Container {
|
||||
all := objects
|
||||
if top != nil {
|
||||
all = append(all, top)
|
||||
}
|
||||
if bottom != nil {
|
||||
all = append(all, bottom)
|
||||
}
|
||||
if left != nil {
|
||||
all = append(all, left)
|
||||
}
|
||||
if right != nil {
|
||||
all = append(all, right)
|
||||
}
|
||||
return New(layout.NewBorderLayout(top, bottom, left, right), all...)
|
||||
}
|
||||
|
||||
// NewCenter creates a new container with the specified objects centered in the available space.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewCenter(objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewCenterLayout(), objects...)
|
||||
}
|
||||
|
||||
// NewGridWithColumns creates a new container with the specified objects and using the grid layout with
|
||||
// a specified number of columns. The number of rows will depend on how many children are in the container.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewGridWithColumns(cols int, objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewGridLayoutWithColumns(cols), objects...)
|
||||
}
|
||||
|
||||
// NewGridWithRows creates a new container with the specified objects and using the grid layout with
|
||||
// a specified number of rows. The number of columns will depend on how many children are in the container.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewGridWithRows(rows int, objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewGridLayoutWithRows(rows), objects...)
|
||||
}
|
||||
|
||||
// NewGridWrap creates a new container with the specified objects and using the gridwrap layout.
|
||||
// Every element will be resized to the size parameter and the content will arrange along a row and flow to a
|
||||
// new row if the elements don't fit.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewGridWrap(size fyne.Size, objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewGridWrapLayout(size), objects...)
|
||||
}
|
||||
|
||||
// NewHBox creates a new container with the specified objects and using the HBox layout.
|
||||
// The objects will be placed in the container from left to right.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewHBox(objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewHBoxLayout(), objects...)
|
||||
}
|
||||
|
||||
// NewMax creates a new container with the specified objects filling the available space.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewMax(objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewMaxLayout(), objects...)
|
||||
}
|
||||
|
||||
// NewPadded creates a new container with the specified objects inset by standard padding size.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewPadded(objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewPaddedLayout(), objects...)
|
||||
}
|
||||
|
||||
// NewVBox creates a new container with the specified objects and using the VBox layout.
|
||||
// The objects will be stacked in the container from top to bottom.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewVBox(objects ...fyne.CanvasObject) *fyne.Container {
|
||||
return New(layout.NewVBoxLayout(), objects...)
|
||||
}
|
55
vendor/fyne.io/fyne/v2/container/scroll.go
generated
vendored
55
vendor/fyne.io/fyne/v2/container/scroll.go
generated
vendored
@@ -1,55 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal/widget"
|
||||
)
|
||||
|
||||
// Scroll defines a container that is smaller than the Content.
|
||||
// The Offset is used to determine the position of the child widgets within the container.
|
||||
//
|
||||
// Since: 1.4
|
||||
type Scroll = widget.Scroll
|
||||
|
||||
// ScrollDirection represents the directions in which a Scroll container can scroll its child content.
|
||||
//
|
||||
// Since: 1.4
|
||||
type ScrollDirection = widget.ScrollDirection
|
||||
|
||||
// Constants for valid values of ScrollDirection.
|
||||
const (
|
||||
// ScrollBoth supports horizontal and vertical scrolling.
|
||||
ScrollBoth ScrollDirection = widget.ScrollBoth
|
||||
// ScrollHorizontalOnly specifies the scrolling should only happen left to right.
|
||||
ScrollHorizontalOnly = widget.ScrollHorizontalOnly
|
||||
// ScrollVerticalOnly specifies the scrolling should only happen top to bottom.
|
||||
ScrollVerticalOnly = widget.ScrollVerticalOnly
|
||||
// ScrollNone turns off scrolling for this container.
|
||||
//
|
||||
// Since: 2.1
|
||||
ScrollNone = widget.ScrollNone
|
||||
)
|
||||
|
||||
// NewScroll creates a scrollable parent wrapping the specified content.
|
||||
// Note that this may cause the MinSize to be smaller than that of the passed object.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewScroll(content fyne.CanvasObject) *Scroll {
|
||||
return widget.NewScroll(content)
|
||||
}
|
||||
|
||||
// NewHScroll create a scrollable parent wrapping the specified content.
|
||||
// Note that this may cause the MinSize.Width to be smaller than that of the passed object.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewHScroll(content fyne.CanvasObject) *Scroll {
|
||||
return widget.NewHScroll(content)
|
||||
}
|
||||
|
||||
// NewVScroll a scrollable parent wrapping the specified content.
|
||||
// Note that this may cause the MinSize.Height to be smaller than that of the passed object.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewVScroll(content fyne.CanvasObject) *Scroll {
|
||||
return widget.NewVScroll(content)
|
||||
}
|
330
vendor/fyne.io/fyne/v2/container/split.go
generated
vendored
330
vendor/fyne.io/fyne/v2/container/split.go
generated
vendored
@@ -1,330 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// Declare conformity with CanvasObject interface
|
||||
var _ fyne.CanvasObject = (*Split)(nil)
|
||||
|
||||
// Split defines a container whose size is split between two children.
|
||||
//
|
||||
// Since: 1.4
|
||||
type Split struct {
|
||||
widget.BaseWidget
|
||||
Offset float64
|
||||
Horizontal bool
|
||||
Leading fyne.CanvasObject
|
||||
Trailing fyne.CanvasObject
|
||||
}
|
||||
|
||||
// NewHSplit creates a horizontally arranged container with the specified leading and trailing elements.
|
||||
// A vertical split bar that can be dragged will be added between the elements.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewHSplit(leading, trailing fyne.CanvasObject) *Split {
|
||||
return newSplitContainer(true, leading, trailing)
|
||||
}
|
||||
|
||||
// NewVSplit creates a vertically arranged container with the specified top and bottom elements.
|
||||
// A horizontal split bar that can be dragged will be added between the elements.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewVSplit(top, bottom fyne.CanvasObject) *Split {
|
||||
return newSplitContainer(false, top, bottom)
|
||||
}
|
||||
|
||||
func newSplitContainer(horizontal bool, leading, trailing fyne.CanvasObject) *Split {
|
||||
s := &Split{
|
||||
Offset: 0.5, // Sensible default, can be overridden with SetOffset
|
||||
Horizontal: horizontal,
|
||||
Leading: leading,
|
||||
Trailing: trailing,
|
||||
}
|
||||
s.BaseWidget.ExtendBaseWidget(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// CreateRenderer is a private method to Fyne which links this widget to its renderer
|
||||
func (s *Split) CreateRenderer() fyne.WidgetRenderer {
|
||||
s.BaseWidget.ExtendBaseWidget(s)
|
||||
d := newDivider(s)
|
||||
return &splitContainerRenderer{
|
||||
split: s,
|
||||
divider: d,
|
||||
objects: []fyne.CanvasObject{s.Leading, d, s.Trailing},
|
||||
}
|
||||
}
|
||||
|
||||
// ExtendBaseWidget is used by an extending widget to make use of BaseWidget functionality.
|
||||
//
|
||||
// Deprecated: Support for extending containers is being removed
|
||||
func (s *Split) ExtendBaseWidget(wid fyne.Widget) {
|
||||
s.BaseWidget.ExtendBaseWidget(wid)
|
||||
}
|
||||
|
||||
// SetOffset sets the offset (0.0 to 1.0) of the Split divider.
|
||||
// 0.0 - Leading is min size, Trailing uses all remaining space.
|
||||
// 0.5 - Leading & Trailing equally share the available space.
|
||||
// 1.0 - Trailing is min size, Leading uses all remaining space.
|
||||
func (s *Split) SetOffset(offset float64) {
|
||||
if s.Offset == offset {
|
||||
return
|
||||
}
|
||||
s.Offset = offset
|
||||
s.Refresh()
|
||||
}
|
||||
|
||||
var _ fyne.WidgetRenderer = (*splitContainerRenderer)(nil)
|
||||
|
||||
type splitContainerRenderer struct {
|
||||
split *Split
|
||||
divider *divider
|
||||
objects []fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (r *splitContainerRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (r *splitContainerRenderer) Layout(size fyne.Size) {
|
||||
var dividerPos, leadingPos, trailingPos fyne.Position
|
||||
var dividerSize, leadingSize, trailingSize fyne.Size
|
||||
|
||||
if r.split.Horizontal {
|
||||
lw, tw := r.computeSplitLengths(size.Width, r.split.Leading.MinSize().Width, r.split.Trailing.MinSize().Width)
|
||||
leadingPos.X = 0
|
||||
leadingSize.Width = lw
|
||||
leadingSize.Height = size.Height
|
||||
dividerPos.X = lw
|
||||
dividerSize.Width = dividerThickness()
|
||||
dividerSize.Height = size.Height
|
||||
trailingPos.X = lw + dividerSize.Width
|
||||
trailingSize.Width = tw
|
||||
trailingSize.Height = size.Height
|
||||
} else {
|
||||
lh, th := r.computeSplitLengths(size.Height, r.split.Leading.MinSize().Height, r.split.Trailing.MinSize().Height)
|
||||
leadingPos.Y = 0
|
||||
leadingSize.Width = size.Width
|
||||
leadingSize.Height = lh
|
||||
dividerPos.Y = lh
|
||||
dividerSize.Width = size.Width
|
||||
dividerSize.Height = dividerThickness()
|
||||
trailingPos.Y = lh + dividerSize.Height
|
||||
trailingSize.Width = size.Width
|
||||
trailingSize.Height = th
|
||||
}
|
||||
|
||||
r.divider.Move(dividerPos)
|
||||
r.divider.Resize(dividerSize)
|
||||
r.split.Leading.Move(leadingPos)
|
||||
r.split.Leading.Resize(leadingSize)
|
||||
r.split.Trailing.Move(trailingPos)
|
||||
r.split.Trailing.Resize(trailingSize)
|
||||
canvas.Refresh(r.divider)
|
||||
canvas.Refresh(r.split.Leading)
|
||||
canvas.Refresh(r.split.Trailing)
|
||||
}
|
||||
|
||||
func (r *splitContainerRenderer) MinSize() fyne.Size {
|
||||
s := fyne.NewSize(0, 0)
|
||||
for _, o := range r.objects {
|
||||
min := o.MinSize()
|
||||
if r.split.Horizontal {
|
||||
s.Width += min.Width
|
||||
s.Height = fyne.Max(s.Height, min.Height)
|
||||
} else {
|
||||
s.Width = fyne.Max(s.Width, min.Width)
|
||||
s.Height += min.Height
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (r *splitContainerRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects
|
||||
}
|
||||
|
||||
func (r *splitContainerRenderer) Refresh() {
|
||||
r.objects[0] = r.split.Leading
|
||||
// [1] is divider which doesn't change
|
||||
r.objects[2] = r.split.Trailing
|
||||
r.Layout(r.split.Size())
|
||||
canvas.Refresh(r.split)
|
||||
}
|
||||
|
||||
func (r *splitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (float32, float32) {
|
||||
available := float64(total - dividerThickness())
|
||||
if available <= 0 {
|
||||
return 0, 0
|
||||
}
|
||||
ld := float64(lMin)
|
||||
tr := float64(tMin)
|
||||
offset := r.split.Offset
|
||||
|
||||
min := ld / available
|
||||
max := 1 - tr/available
|
||||
if min <= max {
|
||||
if offset < min {
|
||||
offset = min
|
||||
}
|
||||
if offset > max {
|
||||
offset = max
|
||||
}
|
||||
} else {
|
||||
offset = ld / (ld + tr)
|
||||
}
|
||||
|
||||
ld = offset * available
|
||||
tr = available - ld
|
||||
return float32(ld), float32(tr)
|
||||
}
|
||||
|
||||
// Declare conformity with interfaces
|
||||
var _ fyne.CanvasObject = (*divider)(nil)
|
||||
var _ fyne.Draggable = (*divider)(nil)
|
||||
var _ desktop.Cursorable = (*divider)(nil)
|
||||
var _ desktop.Hoverable = (*divider)(nil)
|
||||
|
||||
type divider struct {
|
||||
widget.BaseWidget
|
||||
split *Split
|
||||
hovered bool
|
||||
}
|
||||
|
||||
func newDivider(split *Split) *divider {
|
||||
d := ÷r{
|
||||
split: split,
|
||||
}
|
||||
d.ExtendBaseWidget(d)
|
||||
return d
|
||||
}
|
||||
|
||||
// CreateRenderer is a private method to Fyne which links this widget to its renderer
|
||||
func (d *divider) CreateRenderer() fyne.WidgetRenderer {
|
||||
d.ExtendBaseWidget(d)
|
||||
background := canvas.NewRectangle(theme.ShadowColor())
|
||||
foreground := canvas.NewRectangle(theme.ForegroundColor())
|
||||
return ÷rRenderer{
|
||||
divider: d,
|
||||
background: background,
|
||||
foreground: foreground,
|
||||
objects: []fyne.CanvasObject{background, foreground},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *divider) Cursor() desktop.Cursor {
|
||||
if d.split.Horizontal {
|
||||
return desktop.HResizeCursor
|
||||
}
|
||||
return desktop.VResizeCursor
|
||||
}
|
||||
|
||||
func (d *divider) DragEnd() {
|
||||
}
|
||||
|
||||
func (d *divider) Dragged(event *fyne.DragEvent) {
|
||||
offset := d.split.Offset
|
||||
if d.split.Horizontal {
|
||||
if leadingRatio := float64(d.split.Leading.Size().Width) / float64(d.split.Size().Width); offset < leadingRatio {
|
||||
offset = leadingRatio
|
||||
}
|
||||
if trailingRatio := 1. - (float64(d.split.Trailing.Size().Width) / float64(d.split.Size().Width)); offset > trailingRatio {
|
||||
offset = trailingRatio
|
||||
}
|
||||
offset += float64(event.Dragged.DX) / float64(d.split.Size().Width)
|
||||
} else {
|
||||
if leadingRatio := float64(d.split.Leading.Size().Height) / float64(d.split.Size().Height); offset < leadingRatio {
|
||||
offset = leadingRatio
|
||||
}
|
||||
if trailingRatio := 1. - (float64(d.split.Trailing.Size().Height) / float64(d.split.Size().Height)); offset > trailingRatio {
|
||||
offset = trailingRatio
|
||||
}
|
||||
offset += float64(event.Dragged.DY) / float64(d.split.Size().Height)
|
||||
}
|
||||
d.split.SetOffset(offset)
|
||||
}
|
||||
|
||||
func (d *divider) MouseIn(event *desktop.MouseEvent) {
|
||||
d.hovered = true
|
||||
d.split.Refresh()
|
||||
}
|
||||
|
||||
func (d *divider) MouseMoved(event *desktop.MouseEvent) {}
|
||||
|
||||
func (d *divider) MouseOut() {
|
||||
d.hovered = false
|
||||
d.split.Refresh()
|
||||
}
|
||||
|
||||
var _ fyne.WidgetRenderer = (*dividerRenderer)(nil)
|
||||
|
||||
type dividerRenderer struct {
|
||||
divider *divider
|
||||
background *canvas.Rectangle
|
||||
foreground *canvas.Rectangle
|
||||
objects []fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (r *dividerRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (r *dividerRenderer) Layout(size fyne.Size) {
|
||||
r.background.Resize(size)
|
||||
var x, y, w, h float32
|
||||
if r.divider.split.Horizontal {
|
||||
x = (dividerThickness() - handleThickness()) / 2
|
||||
y = (size.Height - handleLength()) / 2
|
||||
w = handleThickness()
|
||||
h = handleLength()
|
||||
} else {
|
||||
x = (size.Width - handleLength()) / 2
|
||||
y = (dividerThickness() - handleThickness()) / 2
|
||||
w = handleLength()
|
||||
h = handleThickness()
|
||||
}
|
||||
r.foreground.Move(fyne.NewPos(x, y))
|
||||
r.foreground.Resize(fyne.NewSize(w, h))
|
||||
}
|
||||
|
||||
func (r *dividerRenderer) MinSize() fyne.Size {
|
||||
if r.divider.split.Horizontal {
|
||||
return fyne.NewSize(dividerThickness(), dividerLength())
|
||||
}
|
||||
return fyne.NewSize(dividerLength(), dividerThickness())
|
||||
}
|
||||
|
||||
func (r *dividerRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects
|
||||
}
|
||||
|
||||
func (r *dividerRenderer) Refresh() {
|
||||
if r.divider.hovered {
|
||||
r.background.FillColor = theme.HoverColor()
|
||||
} else {
|
||||
r.background.FillColor = theme.ShadowColor()
|
||||
}
|
||||
r.background.Refresh()
|
||||
r.foreground.FillColor = theme.ForegroundColor()
|
||||
r.foreground.Refresh()
|
||||
r.Layout(r.divider.Size())
|
||||
}
|
||||
|
||||
func dividerThickness() float32 {
|
||||
return theme.Padding() * 2
|
||||
}
|
||||
|
||||
func dividerLength() float32 {
|
||||
return theme.Padding() * 6
|
||||
}
|
||||
|
||||
func handleThickness() float32 {
|
||||
return theme.Padding() / 2
|
||||
}
|
||||
|
||||
func handleLength() float32 {
|
||||
return theme.Padding() * 4
|
||||
}
|
732
vendor/fyne.io/fyne/v2/container/tabs.go
generated
vendored
732
vendor/fyne.io/fyne/v2/container/tabs.go
generated
vendored
@@ -1,732 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/internal"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// TabItem represents a single view in a tab view.
|
||||
// The Text and Icon are used for the tab button and the Content is shown when the corresponding tab is active.
|
||||
//
|
||||
// Since: 1.4
|
||||
type TabItem struct {
|
||||
Text string
|
||||
Icon fyne.Resource
|
||||
Content fyne.CanvasObject
|
||||
}
|
||||
|
||||
// TabLocation is the location where the tabs of a tab container should be rendered
|
||||
//
|
||||
// Since: 1.4
|
||||
type TabLocation int
|
||||
|
||||
// TabLocation values
|
||||
const (
|
||||
TabLocationTop TabLocation = iota
|
||||
TabLocationLeading
|
||||
TabLocationBottom
|
||||
TabLocationTrailing
|
||||
)
|
||||
|
||||
// NewTabItem creates a new item for a tabbed widget - each item specifies the content and a label for its tab.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewTabItem(text string, content fyne.CanvasObject) *TabItem {
|
||||
return &TabItem{Text: text, Content: content}
|
||||
}
|
||||
|
||||
// NewTabItemWithIcon creates a new item for a tabbed widget - each item specifies the content and a label with an icon for its tab.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewTabItemWithIcon(text string, icon fyne.Resource, content fyne.CanvasObject) *TabItem {
|
||||
return &TabItem{Text: text, Icon: icon, Content: content}
|
||||
}
|
||||
|
||||
type baseTabs interface {
|
||||
onUnselected() func(*TabItem)
|
||||
onSelected() func(*TabItem)
|
||||
|
||||
items() []*TabItem
|
||||
setItems([]*TabItem)
|
||||
|
||||
selected() int
|
||||
setSelected(int)
|
||||
|
||||
tabLocation() TabLocation
|
||||
|
||||
transitioning() bool
|
||||
setTransitioning(bool)
|
||||
}
|
||||
|
||||
func tabsAdjustedLocation(l TabLocation) TabLocation {
|
||||
// Mobile has limited screen space, so don't put app tab bar on long edges
|
||||
if d := fyne.CurrentDevice(); d.IsMobile() {
|
||||
if o := d.Orientation(); fyne.IsVertical(o) {
|
||||
if l == TabLocationLeading {
|
||||
return TabLocationTop
|
||||
} else if l == TabLocationTrailing {
|
||||
return TabLocationBottom
|
||||
}
|
||||
} else {
|
||||
if l == TabLocationTop {
|
||||
return TabLocationLeading
|
||||
} else if l == TabLocationBottom {
|
||||
return TabLocationTrailing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func buildPopUpMenu(t baseTabs, button *widget.Button, items []*fyne.MenuItem) *widget.PopUpMenu {
|
||||
d := fyne.CurrentApp().Driver()
|
||||
c := d.CanvasForObject(button)
|
||||
popUpMenu := widget.NewPopUpMenu(fyne.NewMenu("", items...), c)
|
||||
buttonPos := d.AbsolutePositionForObject(button)
|
||||
buttonSize := button.Size()
|
||||
popUpMin := popUpMenu.MinSize()
|
||||
var popUpPos fyne.Position
|
||||
switch t.tabLocation() {
|
||||
case TabLocationLeading:
|
||||
popUpPos.X = buttonPos.X + buttonSize.Width
|
||||
popUpPos.Y = buttonPos.Y + buttonSize.Height - popUpMin.Height
|
||||
case TabLocationTrailing:
|
||||
popUpPos.X = buttonPos.X - popUpMin.Width
|
||||
popUpPos.Y = buttonPos.Y + buttonSize.Height - popUpMin.Height
|
||||
case TabLocationTop:
|
||||
popUpPos.X = buttonPos.X + buttonSize.Width - popUpMin.Width
|
||||
popUpPos.Y = buttonPos.Y + buttonSize.Height
|
||||
case TabLocationBottom:
|
||||
popUpPos.X = buttonPos.X + buttonSize.Width - popUpMin.Width
|
||||
popUpPos.Y = buttonPos.Y - popUpMin.Height
|
||||
}
|
||||
if popUpPos.X < 0 {
|
||||
popUpPos.X = 0
|
||||
}
|
||||
if popUpPos.Y < 0 {
|
||||
popUpPos.Y = 0
|
||||
}
|
||||
popUpMenu.ShowAtPosition(popUpPos)
|
||||
return popUpMenu
|
||||
}
|
||||
|
||||
func removeIndex(t baseTabs, index int) {
|
||||
items := t.items()
|
||||
if index < 0 || index >= len(items) {
|
||||
return
|
||||
}
|
||||
setItems(t, append(items[:index], items[index+1:]...))
|
||||
if s := t.selected(); index < s {
|
||||
t.setSelected(s - 1)
|
||||
}
|
||||
}
|
||||
|
||||
func removeItem(t baseTabs, item *TabItem) {
|
||||
for index, existingItem := range t.items() {
|
||||
if existingItem == item {
|
||||
removeIndex(t, index)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func selected(t baseTabs) *TabItem {
|
||||
selected := t.selected()
|
||||
items := t.items()
|
||||
if selected < 0 || selected >= len(items) {
|
||||
return nil
|
||||
}
|
||||
return items[selected]
|
||||
}
|
||||
|
||||
func selectIndex(t baseTabs, index int) {
|
||||
selected := t.selected()
|
||||
|
||||
if selected == index {
|
||||
// No change, so do nothing
|
||||
return
|
||||
}
|
||||
|
||||
items := t.items()
|
||||
|
||||
if f := t.onUnselected(); f != nil && selected >= 0 && selected < len(items) {
|
||||
// Notification of unselected
|
||||
f(items[selected])
|
||||
}
|
||||
|
||||
if index < 0 || index >= len(items) {
|
||||
// Out of bounds, so do nothing
|
||||
return
|
||||
}
|
||||
|
||||
t.setTransitioning(true)
|
||||
t.setSelected(index)
|
||||
|
||||
if f := t.onSelected(); f != nil {
|
||||
// Notification of selected
|
||||
f(items[index])
|
||||
}
|
||||
}
|
||||
|
||||
func selectItem(t baseTabs, item *TabItem) {
|
||||
for i, child := range t.items() {
|
||||
if child == item {
|
||||
selectIndex(t, i)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setItems(t baseTabs, items []*TabItem) {
|
||||
if mismatchedTabItems(items) {
|
||||
internal.LogHint("Tab items should all have the same type of content (text, icons or both)")
|
||||
}
|
||||
t.setItems(items)
|
||||
selected := t.selected()
|
||||
count := len(items)
|
||||
switch {
|
||||
case count == 0:
|
||||
// No items available to be selected
|
||||
selectIndex(t, -1) // Unsure OnUnselected gets called if applicable
|
||||
t.setSelected(-1)
|
||||
case selected < 0:
|
||||
// Current is first tab item
|
||||
selectIndex(t, 0)
|
||||
case selected >= count:
|
||||
// Current doesn't exist, select last tab
|
||||
selectIndex(t, count-1)
|
||||
}
|
||||
}
|
||||
|
||||
type baseTabsRenderer struct {
|
||||
positionAnimation, sizeAnimation *fyne.Animation
|
||||
|
||||
lastIndicatorMutex sync.RWMutex
|
||||
lastIndicatorPos fyne.Position
|
||||
lastIndicatorSize fyne.Size
|
||||
lastIndicatorHidden bool
|
||||
|
||||
action *widget.Button
|
||||
bar *fyne.Container
|
||||
divider, indicator *canvas.Rectangle
|
||||
|
||||
buttonCache map[*TabItem]*tabButton
|
||||
}
|
||||
|
||||
func (r *baseTabsRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (r *baseTabsRenderer) applyTheme(t baseTabs) {
|
||||
if r.action != nil {
|
||||
r.action.SetIcon(moreIcon(t))
|
||||
}
|
||||
r.divider.FillColor = theme.ShadowColor()
|
||||
r.indicator.FillColor = theme.PrimaryColor()
|
||||
}
|
||||
|
||||
func (r *baseTabsRenderer) layout(t baseTabs, size fyne.Size) {
|
||||
var (
|
||||
barPos, dividerPos, contentPos fyne.Position
|
||||
barSize, dividerSize, contentSize fyne.Size
|
||||
)
|
||||
|
||||
barMin := r.bar.MinSize()
|
||||
|
||||
switch t.tabLocation() {
|
||||
case TabLocationTop:
|
||||
barHeight := barMin.Height
|
||||
barPos = fyne.NewPos(0, 0)
|
||||
barSize = fyne.NewSize(size.Width, barHeight)
|
||||
dividerPos = fyne.NewPos(0, barHeight)
|
||||
dividerSize = fyne.NewSize(size.Width, theme.Padding())
|
||||
contentPos = fyne.NewPos(0, barHeight+theme.Padding())
|
||||
contentSize = fyne.NewSize(size.Width, size.Height-barHeight-theme.Padding())
|
||||
case TabLocationLeading:
|
||||
barWidth := barMin.Width
|
||||
barPos = fyne.NewPos(0, 0)
|
||||
barSize = fyne.NewSize(barWidth, size.Height)
|
||||
dividerPos = fyne.NewPos(barWidth, 0)
|
||||
dividerSize = fyne.NewSize(theme.Padding(), size.Height)
|
||||
contentPos = fyne.NewPos(barWidth+theme.Padding(), 0)
|
||||
contentSize = fyne.NewSize(size.Width-barWidth-theme.Padding(), size.Height)
|
||||
case TabLocationBottom:
|
||||
barHeight := barMin.Height
|
||||
barPos = fyne.NewPos(0, size.Height-barHeight)
|
||||
barSize = fyne.NewSize(size.Width, barHeight)
|
||||
dividerPos = fyne.NewPos(0, size.Height-barHeight-theme.Padding())
|
||||
dividerSize = fyne.NewSize(size.Width, theme.Padding())
|
||||
contentPos = fyne.NewPos(0, 0)
|
||||
contentSize = fyne.NewSize(size.Width, size.Height-barHeight-theme.Padding())
|
||||
case TabLocationTrailing:
|
||||
barWidth := barMin.Width
|
||||
barPos = fyne.NewPos(size.Width-barWidth, 0)
|
||||
barSize = fyne.NewSize(barWidth, size.Height)
|
||||
dividerPos = fyne.NewPos(size.Width-barWidth-theme.Padding(), 0)
|
||||
dividerSize = fyne.NewSize(theme.Padding(), size.Height)
|
||||
contentPos = fyne.NewPos(0, 0)
|
||||
contentSize = fyne.NewSize(size.Width-barWidth-theme.Padding(), size.Height)
|
||||
}
|
||||
|
||||
r.bar.Move(barPos)
|
||||
r.bar.Resize(barSize)
|
||||
r.divider.Move(dividerPos)
|
||||
r.divider.Resize(dividerSize)
|
||||
selected := t.selected()
|
||||
for i, ti := range t.items() {
|
||||
if i == selected {
|
||||
ti.Content.Move(contentPos)
|
||||
ti.Content.Resize(contentSize)
|
||||
ti.Content.Show()
|
||||
} else {
|
||||
ti.Content.Hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *baseTabsRenderer) minSize(t baseTabs) fyne.Size {
|
||||
barMin := r.bar.MinSize()
|
||||
|
||||
contentMin := fyne.NewSize(0, 0)
|
||||
for _, content := range t.items() {
|
||||
contentMin = contentMin.Max(content.Content.MinSize())
|
||||
}
|
||||
|
||||
switch t.tabLocation() {
|
||||
case TabLocationLeading, TabLocationTrailing:
|
||||
return fyne.NewSize(barMin.Width+contentMin.Width+theme.Padding(), contentMin.Height)
|
||||
default:
|
||||
return fyne.NewSize(contentMin.Width, barMin.Height+contentMin.Height+theme.Padding())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *baseTabsRenderer) moveIndicator(pos fyne.Position, siz fyne.Size, animate bool) {
|
||||
r.lastIndicatorMutex.RLock()
|
||||
isSameState := r.lastIndicatorPos.Subtract(pos).IsZero() && r.lastIndicatorSize.Subtract(siz).IsZero() &&
|
||||
r.lastIndicatorHidden == r.indicator.Hidden
|
||||
r.lastIndicatorMutex.RUnlock()
|
||||
if isSameState {
|
||||
return
|
||||
}
|
||||
|
||||
if r.positionAnimation != nil {
|
||||
r.positionAnimation.Stop()
|
||||
r.positionAnimation = nil
|
||||
}
|
||||
if r.sizeAnimation != nil {
|
||||
r.sizeAnimation.Stop()
|
||||
r.sizeAnimation = nil
|
||||
}
|
||||
|
||||
r.indicator.FillColor = theme.PrimaryColor()
|
||||
if r.indicator.Position().IsZero() {
|
||||
r.indicator.Move(pos)
|
||||
r.indicator.Resize(siz)
|
||||
r.indicator.Refresh()
|
||||
return
|
||||
}
|
||||
|
||||
r.lastIndicatorMutex.Lock()
|
||||
r.lastIndicatorPos = pos
|
||||
r.lastIndicatorSize = siz
|
||||
r.lastIndicatorHidden = r.indicator.Hidden
|
||||
r.lastIndicatorMutex.Unlock()
|
||||
|
||||
if animate {
|
||||
r.positionAnimation = canvas.NewPositionAnimation(r.indicator.Position(), pos, canvas.DurationShort, func(p fyne.Position) {
|
||||
r.indicator.Move(p)
|
||||
r.indicator.Refresh()
|
||||
if pos == p {
|
||||
r.positionAnimation.Stop()
|
||||
r.positionAnimation = nil
|
||||
}
|
||||
})
|
||||
r.sizeAnimation = canvas.NewSizeAnimation(r.indicator.Size(), siz, canvas.DurationShort, func(s fyne.Size) {
|
||||
r.indicator.Resize(s)
|
||||
r.indicator.Refresh()
|
||||
if siz == s {
|
||||
r.sizeAnimation.Stop()
|
||||
r.sizeAnimation = nil
|
||||
}
|
||||
})
|
||||
|
||||
r.positionAnimation.Start()
|
||||
r.sizeAnimation.Start()
|
||||
} else {
|
||||
r.indicator.Move(pos)
|
||||
r.indicator.Resize(siz)
|
||||
r.indicator.Refresh()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *baseTabsRenderer) objects(t baseTabs) []fyne.CanvasObject {
|
||||
objects := []fyne.CanvasObject{r.bar, r.divider, r.indicator}
|
||||
if i, is := t.selected(), t.items(); i >= 0 && i < len(is) {
|
||||
objects = append(objects, is[i].Content)
|
||||
}
|
||||
return objects
|
||||
}
|
||||
|
||||
func (r *baseTabsRenderer) refresh(t baseTabs) {
|
||||
r.applyTheme(t)
|
||||
|
||||
r.bar.Refresh()
|
||||
r.divider.Refresh()
|
||||
r.indicator.Refresh()
|
||||
}
|
||||
|
||||
type buttonIconPosition int
|
||||
|
||||
const (
|
||||
buttonIconInline buttonIconPosition = iota
|
||||
buttonIconTop
|
||||
)
|
||||
|
||||
var _ fyne.Widget = (*tabButton)(nil)
|
||||
var _ fyne.Tappable = (*tabButton)(nil)
|
||||
var _ desktop.Hoverable = (*tabButton)(nil)
|
||||
|
||||
type tabButton struct {
|
||||
widget.BaseWidget
|
||||
hovered bool
|
||||
icon fyne.Resource
|
||||
iconPosition buttonIconPosition
|
||||
importance widget.ButtonImportance
|
||||
onTapped func()
|
||||
onClosed func()
|
||||
text string
|
||||
textAlignment fyne.TextAlign
|
||||
}
|
||||
|
||||
func (b *tabButton) CreateRenderer() fyne.WidgetRenderer {
|
||||
b.ExtendBaseWidget(b)
|
||||
background := canvas.NewRectangle(theme.HoverColor())
|
||||
background.Hide()
|
||||
icon := canvas.NewImageFromResource(b.icon)
|
||||
if b.icon == nil {
|
||||
icon.Hide()
|
||||
}
|
||||
|
||||
label := canvas.NewText(b.text, theme.ForegroundColor())
|
||||
label.TextStyle.Bold = true
|
||||
|
||||
close := &tabCloseButton{
|
||||
parent: b,
|
||||
onTapped: func() {
|
||||
if f := b.onClosed; f != nil {
|
||||
f()
|
||||
}
|
||||
},
|
||||
}
|
||||
close.ExtendBaseWidget(close)
|
||||
close.Hide()
|
||||
|
||||
objects := []fyne.CanvasObject{background, label, close, icon}
|
||||
r := &tabButtonRenderer{
|
||||
button: b,
|
||||
background: background,
|
||||
icon: icon,
|
||||
label: label,
|
||||
close: close,
|
||||
objects: objects,
|
||||
}
|
||||
r.Refresh()
|
||||
return r
|
||||
}
|
||||
|
||||
func (b *tabButton) MinSize() fyne.Size {
|
||||
b.ExtendBaseWidget(b)
|
||||
return b.BaseWidget.MinSize()
|
||||
}
|
||||
|
||||
func (b *tabButton) MouseIn(*desktop.MouseEvent) {
|
||||
b.hovered = true
|
||||
b.Refresh()
|
||||
}
|
||||
|
||||
func (b *tabButton) MouseMoved(*desktop.MouseEvent) {
|
||||
}
|
||||
|
||||
func (b *tabButton) MouseOut() {
|
||||
b.hovered = false
|
||||
b.Refresh()
|
||||
}
|
||||
|
||||
func (b *tabButton) Tapped(*fyne.PointEvent) {
|
||||
b.onTapped()
|
||||
}
|
||||
|
||||
type tabButtonRenderer struct {
|
||||
button *tabButton
|
||||
background *canvas.Rectangle
|
||||
icon *canvas.Image
|
||||
label *canvas.Text
|
||||
close *tabCloseButton
|
||||
objects []fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (r *tabButtonRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (r *tabButtonRenderer) Layout(size fyne.Size) {
|
||||
r.background.Resize(size)
|
||||
padding := r.padding()
|
||||
innerSize := size.Subtract(padding)
|
||||
innerOffset := fyne.NewPos(padding.Width/2, padding.Height/2)
|
||||
labelShift := float32(0)
|
||||
if r.icon.Visible() {
|
||||
var iconOffset fyne.Position
|
||||
if r.button.iconPosition == buttonIconTop {
|
||||
iconOffset = fyne.NewPos((innerSize.Width-r.iconSize())/2, 0)
|
||||
} else {
|
||||
iconOffset = fyne.NewPos(0, (innerSize.Height-r.iconSize())/2)
|
||||
}
|
||||
r.icon.Resize(fyne.NewSize(r.iconSize(), r.iconSize()))
|
||||
r.icon.Move(innerOffset.Add(iconOffset))
|
||||
labelShift = r.iconSize() + theme.Padding()
|
||||
}
|
||||
if r.label.Text != "" {
|
||||
var labelOffset fyne.Position
|
||||
var labelSize fyne.Size
|
||||
if r.button.iconPosition == buttonIconTop {
|
||||
labelOffset = fyne.NewPos(0, labelShift)
|
||||
labelSize = fyne.NewSize(innerSize.Width, r.label.MinSize().Height)
|
||||
} else {
|
||||
labelOffset = fyne.NewPos(labelShift, 0)
|
||||
labelSize = fyne.NewSize(innerSize.Width-labelShift, innerSize.Height)
|
||||
}
|
||||
r.label.Resize(labelSize)
|
||||
r.label.Move(innerOffset.Add(labelOffset))
|
||||
}
|
||||
r.close.Move(fyne.NewPos(size.Width-theme.IconInlineSize()-theme.Padding(), (size.Height-theme.IconInlineSize())/2))
|
||||
r.close.Resize(fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize()))
|
||||
}
|
||||
|
||||
func (r *tabButtonRenderer) MinSize() fyne.Size {
|
||||
var contentWidth, contentHeight float32
|
||||
textSize := r.label.MinSize()
|
||||
if r.button.iconPosition == buttonIconTop {
|
||||
contentWidth = fyne.Max(textSize.Width, r.iconSize())
|
||||
if r.icon.Visible() {
|
||||
contentHeight += r.iconSize()
|
||||
}
|
||||
if r.label.Text != "" {
|
||||
if r.icon.Visible() {
|
||||
contentHeight += theme.Padding()
|
||||
}
|
||||
contentHeight += textSize.Height
|
||||
}
|
||||
} else {
|
||||
contentHeight = fyne.Max(textSize.Height, r.iconSize())
|
||||
if r.icon.Visible() {
|
||||
contentWidth += r.iconSize()
|
||||
}
|
||||
if r.label.Text != "" {
|
||||
if r.icon.Visible() {
|
||||
contentWidth += theme.Padding()
|
||||
}
|
||||
contentWidth += textSize.Width
|
||||
}
|
||||
}
|
||||
if r.button.onClosed != nil {
|
||||
contentWidth += theme.IconInlineSize() + theme.Padding()
|
||||
contentHeight = fyne.Max(contentHeight, theme.IconInlineSize())
|
||||
}
|
||||
return fyne.NewSize(contentWidth, contentHeight).Add(r.padding())
|
||||
}
|
||||
|
||||
func (r *tabButtonRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects
|
||||
}
|
||||
|
||||
func (r *tabButtonRenderer) Refresh() {
|
||||
if r.button.hovered {
|
||||
r.background.FillColor = theme.HoverColor()
|
||||
r.background.Show()
|
||||
} else {
|
||||
r.background.Hide()
|
||||
}
|
||||
r.background.Refresh()
|
||||
|
||||
r.label.Text = r.button.text
|
||||
r.label.Alignment = r.button.textAlignment
|
||||
if r.button.importance == widget.HighImportance {
|
||||
r.label.Color = theme.PrimaryColor()
|
||||
} else {
|
||||
r.label.Color = theme.ForegroundColor()
|
||||
}
|
||||
r.label.TextSize = theme.TextSize()
|
||||
if r.button.text == "" {
|
||||
r.label.Hide()
|
||||
} else {
|
||||
r.label.Show()
|
||||
}
|
||||
|
||||
r.icon.Resource = r.button.icon
|
||||
if r.icon.Resource != nil {
|
||||
r.icon.Show()
|
||||
switch res := r.icon.Resource.(type) {
|
||||
case *theme.ThemedResource:
|
||||
if r.button.importance == widget.HighImportance {
|
||||
r.icon.Resource = theme.NewPrimaryThemedResource(res)
|
||||
r.icon.Refresh()
|
||||
}
|
||||
case *theme.PrimaryThemedResource:
|
||||
if r.button.importance != widget.HighImportance {
|
||||
r.icon.Resource = res.Original()
|
||||
r.icon.Refresh()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
r.icon.Hide()
|
||||
}
|
||||
|
||||
if d := fyne.CurrentDevice(); r.button.onClosed != nil && (d.IsMobile() || r.button.hovered || r.close.hovered) {
|
||||
r.close.Show()
|
||||
} else {
|
||||
r.close.Hide()
|
||||
}
|
||||
r.close.Refresh()
|
||||
|
||||
canvas.Refresh(r.button)
|
||||
}
|
||||
|
||||
func (r *tabButtonRenderer) iconSize() float32 {
|
||||
switch r.button.iconPosition {
|
||||
case buttonIconTop:
|
||||
return 2 * theme.IconInlineSize()
|
||||
default:
|
||||
return theme.IconInlineSize()
|
||||
}
|
||||
}
|
||||
|
||||
func (r *tabButtonRenderer) padding() fyne.Size {
|
||||
if r.label.Text != "" && r.button.iconPosition == buttonIconInline {
|
||||
return fyne.NewSize(theme.Padding()*4, theme.Padding()*4)
|
||||
}
|
||||
return fyne.NewSize(theme.Padding()*2, theme.Padding()*4)
|
||||
}
|
||||
|
||||
var _ fyne.Widget = (*tabCloseButton)(nil)
|
||||
var _ fyne.Tappable = (*tabCloseButton)(nil)
|
||||
var _ desktop.Hoverable = (*tabCloseButton)(nil)
|
||||
|
||||
type tabCloseButton struct {
|
||||
widget.BaseWidget
|
||||
parent *tabButton
|
||||
hovered bool
|
||||
onTapped func()
|
||||
}
|
||||
|
||||
func (b *tabCloseButton) CreateRenderer() fyne.WidgetRenderer {
|
||||
b.ExtendBaseWidget(b)
|
||||
background := canvas.NewRectangle(theme.HoverColor())
|
||||
background.Hide()
|
||||
icon := canvas.NewImageFromResource(theme.CancelIcon())
|
||||
|
||||
r := &tabCloseButtonRenderer{
|
||||
button: b,
|
||||
background: background,
|
||||
icon: icon,
|
||||
objects: []fyne.CanvasObject{background, icon},
|
||||
}
|
||||
r.Refresh()
|
||||
return r
|
||||
}
|
||||
|
||||
func (b *tabCloseButton) MinSize() fyne.Size {
|
||||
b.ExtendBaseWidget(b)
|
||||
return b.BaseWidget.MinSize()
|
||||
}
|
||||
|
||||
func (b *tabCloseButton) MouseIn(*desktop.MouseEvent) {
|
||||
b.hovered = true
|
||||
b.parent.Refresh()
|
||||
}
|
||||
|
||||
func (b *tabCloseButton) MouseMoved(*desktop.MouseEvent) {
|
||||
}
|
||||
|
||||
func (b *tabCloseButton) MouseOut() {
|
||||
b.hovered = false
|
||||
b.parent.Refresh()
|
||||
}
|
||||
|
||||
func (b *tabCloseButton) Tapped(*fyne.PointEvent) {
|
||||
b.onTapped()
|
||||
}
|
||||
|
||||
type tabCloseButtonRenderer struct {
|
||||
button *tabCloseButton
|
||||
background *canvas.Rectangle
|
||||
icon *canvas.Image
|
||||
objects []fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (r *tabCloseButtonRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (r *tabCloseButtonRenderer) Layout(size fyne.Size) {
|
||||
r.background.Resize(size)
|
||||
r.icon.Resize(size)
|
||||
}
|
||||
|
||||
func (r *tabCloseButtonRenderer) MinSize() fyne.Size {
|
||||
return fyne.NewSize(theme.IconInlineSize(), theme.IconInlineSize())
|
||||
}
|
||||
|
||||
func (r *tabCloseButtonRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects
|
||||
}
|
||||
|
||||
func (r *tabCloseButtonRenderer) Refresh() {
|
||||
if r.button.hovered {
|
||||
r.background.FillColor = theme.HoverColor()
|
||||
r.background.Show()
|
||||
} else {
|
||||
r.background.Hide()
|
||||
}
|
||||
r.background.Refresh()
|
||||
switch res := r.icon.Resource.(type) {
|
||||
case *theme.ThemedResource:
|
||||
if r.button.parent.importance == widget.HighImportance {
|
||||
r.icon.Resource = theme.NewPrimaryThemedResource(res)
|
||||
}
|
||||
case *theme.PrimaryThemedResource:
|
||||
if r.button.parent.importance != widget.HighImportance {
|
||||
r.icon.Resource = res.Original()
|
||||
}
|
||||
}
|
||||
r.icon.Refresh()
|
||||
}
|
||||
|
||||
func mismatchedTabItems(items []*TabItem) bool {
|
||||
var hasText, hasIcon bool
|
||||
for _, tab := range items {
|
||||
hasText = hasText || tab.Text != ""
|
||||
hasIcon = hasIcon || tab.Icon != nil
|
||||
}
|
||||
|
||||
mismatch := false
|
||||
for _, tab := range items {
|
||||
if (hasText && tab.Text == "") || (hasIcon && tab.Icon == nil) {
|
||||
mismatch = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return mismatch
|
||||
}
|
||||
|
||||
func moreIcon(t baseTabs) fyne.Resource {
|
||||
if l := t.tabLocation(); l == TabLocationLeading || l == TabLocationTrailing {
|
||||
return theme.MoreVerticalIcon()
|
||||
}
|
||||
return theme.MoreHorizontalIcon()
|
||||
}
|
178
vendor/fyne.io/fyne/v2/data/binding/binding.go
generated
vendored
178
vendor/fyne.io/fyne/v2/data/binding/binding.go
generated
vendored
@@ -1,178 +0,0 @@
|
||||
//go:generate go run gen.go
|
||||
|
||||
// Package binding provides support for binding data to widgets.
|
||||
package binding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
errKeyNotFound = errors.New("key not found")
|
||||
errOutOfBounds = errors.New("index out of bounds")
|
||||
errParseFailed = errors.New("format did not match 1 value")
|
||||
|
||||
// As an optimisation we connect any listeners asking for the same key, so that there is only 1 per preference item.
|
||||
prefBinds = newPreferencesMap()
|
||||
)
|
||||
|
||||
// DataItem is the base interface for all bindable data items.
|
||||
//
|
||||
// Since: 2.0
|
||||
type DataItem interface {
|
||||
// AddListener attaches a new change listener to this DataItem.
|
||||
// Listeners are called each time the data inside this DataItem changes.
|
||||
// Additionally the listener will be triggered upon successful connection to get the current value.
|
||||
AddListener(DataListener)
|
||||
// RemoveListener will detach the specified change listener from the DataItem.
|
||||
// Disconnected listener will no longer be triggered when changes occur.
|
||||
RemoveListener(DataListener)
|
||||
}
|
||||
|
||||
// DataListener is any object that can register for changes in a bindable DataItem.
|
||||
// See NewDataListener to define a new listener using just an inline function.
|
||||
//
|
||||
// Since: 2.0
|
||||
type DataListener interface {
|
||||
DataChanged()
|
||||
}
|
||||
|
||||
// NewDataListener is a helper function that creates a new listener type from a simple callback function.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewDataListener(fn func()) DataListener {
|
||||
return &listener{fn}
|
||||
}
|
||||
|
||||
type listener struct {
|
||||
callback func()
|
||||
}
|
||||
|
||||
func (l *listener) DataChanged() {
|
||||
l.callback()
|
||||
}
|
||||
|
||||
type base struct {
|
||||
listeners sync.Map // map[DataListener]bool
|
||||
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
// AddListener allows a data listener to be informed of changes to this item.
|
||||
func (b *base) AddListener(l DataListener) {
|
||||
b.listeners.Store(l, true)
|
||||
queueItem(l.DataChanged)
|
||||
}
|
||||
|
||||
// RemoveListener should be called if the listener is no longer interested in being informed of data change events.
|
||||
func (b *base) RemoveListener(l DataListener) {
|
||||
b.listeners.Delete(l)
|
||||
}
|
||||
|
||||
func (b *base) trigger() {
|
||||
b.listeners.Range(func(key, _ interface{}) bool {
|
||||
queueItem(key.(DataListener).DataChanged)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Untyped supports binding a interface{} value.
|
||||
//
|
||||
// Since: 2.1
|
||||
type Untyped interface {
|
||||
DataItem
|
||||
Get() (interface{}, error)
|
||||
Set(interface{}) error
|
||||
}
|
||||
|
||||
// NewUntyped returns a bindable interface{} value that is managed internally.
|
||||
//
|
||||
// Since: 2.1
|
||||
func NewUntyped() Untyped {
|
||||
var blank interface{} = nil
|
||||
v := &blank
|
||||
return &boundUntyped{val: reflect.ValueOf(v).Elem()}
|
||||
}
|
||||
|
||||
type boundUntyped struct {
|
||||
base
|
||||
|
||||
val reflect.Value
|
||||
}
|
||||
|
||||
func (b *boundUntyped) Get() (interface{}, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
return b.val.Interface(), nil
|
||||
}
|
||||
|
||||
func (b *boundUntyped) Set(val interface{}) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.val.Interface() == val {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.val.Set(reflect.ValueOf(val))
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ExternalUntyped supports binding a interface{} value to an external value.
|
||||
//
|
||||
// Since: 2.1
|
||||
type ExternalUntyped interface {
|
||||
Untyped
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// BindUntyped returns a bindable interface{} value that is bound to an external type.
|
||||
// The parameter must be a pointer to the type you wish to bind.
|
||||
//
|
||||
// Since: 2.1
|
||||
func BindUntyped(v interface{}) ExternalUntyped {
|
||||
t := reflect.TypeOf(v)
|
||||
if t.Kind() != reflect.Ptr {
|
||||
fyne.LogError("Invalid type passed to BindUntyped, must be a pointer", nil)
|
||||
v = nil
|
||||
}
|
||||
|
||||
if v == nil {
|
||||
var blank interface{}
|
||||
v = &blank // never allow a nil value pointer
|
||||
}
|
||||
|
||||
b := &boundExternalUntyped{}
|
||||
b.val = reflect.ValueOf(v).Elem()
|
||||
b.old = b.val.Interface()
|
||||
return b
|
||||
}
|
||||
|
||||
type boundExternalUntyped struct {
|
||||
boundUntyped
|
||||
|
||||
old interface{}
|
||||
}
|
||||
|
||||
func (b *boundExternalUntyped) Set(val interface{}) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.old == val {
|
||||
return nil
|
||||
}
|
||||
b.val.Set(reflect.ValueOf(val))
|
||||
b.old = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boundExternalUntyped) Reload() error {
|
||||
return b.Set(b.val.Interface())
|
||||
}
|
647
vendor/fyne.io/fyne/v2/data/binding/binditems.go
generated
vendored
647
vendor/fyne.io/fyne/v2/data/binding/binditems.go
generated
vendored
@@ -1,647 +0,0 @@
|
||||
// auto-generated
|
||||
// **** THIS FILE IS AUTO-GENERATED, PLEASE DO NOT EDIT IT **** //
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// Bool supports binding a bool value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type Bool interface {
|
||||
DataItem
|
||||
Get() (bool, error)
|
||||
Set(bool) error
|
||||
}
|
||||
|
||||
// ExternalBool supports binding a bool value to an external value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type ExternalBool interface {
|
||||
Bool
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// NewBool returns a bindable bool value that is managed internally.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewBool() Bool {
|
||||
var blank bool = false
|
||||
return &boundBool{val: &blank}
|
||||
}
|
||||
|
||||
// BindBool returns a new bindable value that controls the contents of the provided bool variable.
|
||||
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindBool(v *bool) ExternalBool {
|
||||
if v == nil {
|
||||
var blank bool = false
|
||||
v = &blank // never allow a nil value pointer
|
||||
}
|
||||
b := &boundExternalBool{}
|
||||
b.val = v
|
||||
b.old = *v
|
||||
return b
|
||||
}
|
||||
|
||||
type boundBool struct {
|
||||
base
|
||||
|
||||
val *bool
|
||||
}
|
||||
|
||||
func (b *boundBool) Get() (bool, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.val == nil {
|
||||
return false, nil
|
||||
}
|
||||
return *b.val, nil
|
||||
}
|
||||
|
||||
func (b *boundBool) Set(val bool) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if *b.val == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type boundExternalBool struct {
|
||||
boundBool
|
||||
|
||||
old bool
|
||||
}
|
||||
|
||||
func (b *boundExternalBool) Set(val bool) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.old == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
b.old = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boundExternalBool) Reload() error {
|
||||
return b.Set(*b.val)
|
||||
}
|
||||
|
||||
// Bytes supports binding a []byte value.
|
||||
//
|
||||
// Since: 2.2
|
||||
type Bytes interface {
|
||||
DataItem
|
||||
Get() ([]byte, error)
|
||||
Set([]byte) error
|
||||
}
|
||||
|
||||
// ExternalBytes supports binding a []byte value to an external value.
|
||||
//
|
||||
// Since: 2.2
|
||||
type ExternalBytes interface {
|
||||
Bytes
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// NewBytes returns a bindable []byte value that is managed internally.
|
||||
//
|
||||
// Since: 2.2
|
||||
func NewBytes() Bytes {
|
||||
var blank []byte = nil
|
||||
return &boundBytes{val: &blank}
|
||||
}
|
||||
|
||||
// BindBytes returns a new bindable value that controls the contents of the provided []byte variable.
|
||||
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
|
||||
//
|
||||
// Since: 2.2
|
||||
func BindBytes(v *[]byte) ExternalBytes {
|
||||
if v == nil {
|
||||
var blank []byte = nil
|
||||
v = &blank // never allow a nil value pointer
|
||||
}
|
||||
b := &boundExternalBytes{}
|
||||
b.val = v
|
||||
b.old = *v
|
||||
return b
|
||||
}
|
||||
|
||||
type boundBytes struct {
|
||||
base
|
||||
|
||||
val *[]byte
|
||||
}
|
||||
|
||||
func (b *boundBytes) Get() ([]byte, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.val == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return *b.val, nil
|
||||
}
|
||||
|
||||
func (b *boundBytes) Set(val []byte) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if bytes.Equal(*b.val, val) {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type boundExternalBytes struct {
|
||||
boundBytes
|
||||
|
||||
old []byte
|
||||
}
|
||||
|
||||
func (b *boundExternalBytes) Set(val []byte) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if bytes.Equal(b.old, val) {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
b.old = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boundExternalBytes) Reload() error {
|
||||
return b.Set(*b.val)
|
||||
}
|
||||
|
||||
// Float supports binding a float64 value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type Float interface {
|
||||
DataItem
|
||||
Get() (float64, error)
|
||||
Set(float64) error
|
||||
}
|
||||
|
||||
// ExternalFloat supports binding a float64 value to an external value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type ExternalFloat interface {
|
||||
Float
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// NewFloat returns a bindable float64 value that is managed internally.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewFloat() Float {
|
||||
var blank float64 = 0.0
|
||||
return &boundFloat{val: &blank}
|
||||
}
|
||||
|
||||
// BindFloat returns a new bindable value that controls the contents of the provided float64 variable.
|
||||
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindFloat(v *float64) ExternalFloat {
|
||||
if v == nil {
|
||||
var blank float64 = 0.0
|
||||
v = &blank // never allow a nil value pointer
|
||||
}
|
||||
b := &boundExternalFloat{}
|
||||
b.val = v
|
||||
b.old = *v
|
||||
return b
|
||||
}
|
||||
|
||||
type boundFloat struct {
|
||||
base
|
||||
|
||||
val *float64
|
||||
}
|
||||
|
||||
func (b *boundFloat) Get() (float64, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.val == nil {
|
||||
return 0.0, nil
|
||||
}
|
||||
return *b.val, nil
|
||||
}
|
||||
|
||||
func (b *boundFloat) Set(val float64) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if *b.val == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type boundExternalFloat struct {
|
||||
boundFloat
|
||||
|
||||
old float64
|
||||
}
|
||||
|
||||
func (b *boundExternalFloat) Set(val float64) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.old == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
b.old = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boundExternalFloat) Reload() error {
|
||||
return b.Set(*b.val)
|
||||
}
|
||||
|
||||
// Int supports binding a int value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type Int interface {
|
||||
DataItem
|
||||
Get() (int, error)
|
||||
Set(int) error
|
||||
}
|
||||
|
||||
// ExternalInt supports binding a int value to an external value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type ExternalInt interface {
|
||||
Int
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// NewInt returns a bindable int value that is managed internally.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewInt() Int {
|
||||
var blank int = 0
|
||||
return &boundInt{val: &blank}
|
||||
}
|
||||
|
||||
// BindInt returns a new bindable value that controls the contents of the provided int variable.
|
||||
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindInt(v *int) ExternalInt {
|
||||
if v == nil {
|
||||
var blank int = 0
|
||||
v = &blank // never allow a nil value pointer
|
||||
}
|
||||
b := &boundExternalInt{}
|
||||
b.val = v
|
||||
b.old = *v
|
||||
return b
|
||||
}
|
||||
|
||||
type boundInt struct {
|
||||
base
|
||||
|
||||
val *int
|
||||
}
|
||||
|
||||
func (b *boundInt) Get() (int, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.val == nil {
|
||||
return 0, nil
|
||||
}
|
||||
return *b.val, nil
|
||||
}
|
||||
|
||||
func (b *boundInt) Set(val int) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if *b.val == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type boundExternalInt struct {
|
||||
boundInt
|
||||
|
||||
old int
|
||||
}
|
||||
|
||||
func (b *boundExternalInt) Set(val int) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.old == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
b.old = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boundExternalInt) Reload() error {
|
||||
return b.Set(*b.val)
|
||||
}
|
||||
|
||||
// Rune supports binding a rune value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type Rune interface {
|
||||
DataItem
|
||||
Get() (rune, error)
|
||||
Set(rune) error
|
||||
}
|
||||
|
||||
// ExternalRune supports binding a rune value to an external value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type ExternalRune interface {
|
||||
Rune
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// NewRune returns a bindable rune value that is managed internally.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewRune() Rune {
|
||||
var blank rune = rune(0)
|
||||
return &boundRune{val: &blank}
|
||||
}
|
||||
|
||||
// BindRune returns a new bindable value that controls the contents of the provided rune variable.
|
||||
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindRune(v *rune) ExternalRune {
|
||||
if v == nil {
|
||||
var blank rune = rune(0)
|
||||
v = &blank // never allow a nil value pointer
|
||||
}
|
||||
b := &boundExternalRune{}
|
||||
b.val = v
|
||||
b.old = *v
|
||||
return b
|
||||
}
|
||||
|
||||
type boundRune struct {
|
||||
base
|
||||
|
||||
val *rune
|
||||
}
|
||||
|
||||
func (b *boundRune) Get() (rune, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.val == nil {
|
||||
return rune(0), nil
|
||||
}
|
||||
return *b.val, nil
|
||||
}
|
||||
|
||||
func (b *boundRune) Set(val rune) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if *b.val == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type boundExternalRune struct {
|
||||
boundRune
|
||||
|
||||
old rune
|
||||
}
|
||||
|
||||
func (b *boundExternalRune) Set(val rune) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.old == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
b.old = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boundExternalRune) Reload() error {
|
||||
return b.Set(*b.val)
|
||||
}
|
||||
|
||||
// String supports binding a string value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type String interface {
|
||||
DataItem
|
||||
Get() (string, error)
|
||||
Set(string) error
|
||||
}
|
||||
|
||||
// ExternalString supports binding a string value to an external value.
|
||||
//
|
||||
// Since: 2.0
|
||||
type ExternalString interface {
|
||||
String
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// NewString returns a bindable string value that is managed internally.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewString() String {
|
||||
var blank string = ""
|
||||
return &boundString{val: &blank}
|
||||
}
|
||||
|
||||
// BindString returns a new bindable value that controls the contents of the provided string variable.
|
||||
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindString(v *string) ExternalString {
|
||||
if v == nil {
|
||||
var blank string = ""
|
||||
v = &blank // never allow a nil value pointer
|
||||
}
|
||||
b := &boundExternalString{}
|
||||
b.val = v
|
||||
b.old = *v
|
||||
return b
|
||||
}
|
||||
|
||||
type boundString struct {
|
||||
base
|
||||
|
||||
val *string
|
||||
}
|
||||
|
||||
func (b *boundString) Get() (string, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.val == nil {
|
||||
return "", nil
|
||||
}
|
||||
return *b.val, nil
|
||||
}
|
||||
|
||||
func (b *boundString) Set(val string) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if *b.val == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type boundExternalString struct {
|
||||
boundString
|
||||
|
||||
old string
|
||||
}
|
||||
|
||||
func (b *boundExternalString) Set(val string) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if b.old == val {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
b.old = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boundExternalString) Reload() error {
|
||||
return b.Set(*b.val)
|
||||
}
|
||||
|
||||
// URI supports binding a fyne.URI value.
|
||||
//
|
||||
// Since: 2.1
|
||||
type URI interface {
|
||||
DataItem
|
||||
Get() (fyne.URI, error)
|
||||
Set(fyne.URI) error
|
||||
}
|
||||
|
||||
// ExternalURI supports binding a fyne.URI value to an external value.
|
||||
//
|
||||
// Since: 2.1
|
||||
type ExternalURI interface {
|
||||
URI
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// NewURI returns a bindable fyne.URI value that is managed internally.
|
||||
//
|
||||
// Since: 2.1
|
||||
func NewURI() URI {
|
||||
var blank fyne.URI = fyne.URI(nil)
|
||||
return &boundURI{val: &blank}
|
||||
}
|
||||
|
||||
// BindURI returns a new bindable value that controls the contents of the provided fyne.URI variable.
|
||||
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
|
||||
//
|
||||
// Since: 2.1
|
||||
func BindURI(v *fyne.URI) ExternalURI {
|
||||
if v == nil {
|
||||
var blank fyne.URI = fyne.URI(nil)
|
||||
v = &blank // never allow a nil value pointer
|
||||
}
|
||||
b := &boundExternalURI{}
|
||||
b.val = v
|
||||
b.old = *v
|
||||
return b
|
||||
}
|
||||
|
||||
type boundURI struct {
|
||||
base
|
||||
|
||||
val *fyne.URI
|
||||
}
|
||||
|
||||
func (b *boundURI) Get() (fyne.URI, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.val == nil {
|
||||
return fyne.URI(nil), nil
|
||||
}
|
||||
return *b.val, nil
|
||||
}
|
||||
|
||||
func (b *boundURI) Set(val fyne.URI) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if compareURI(*b.val, val) {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type boundExternalURI struct {
|
||||
boundURI
|
||||
|
||||
old fyne.URI
|
||||
}
|
||||
|
||||
func (b *boundExternalURI) Set(val fyne.URI) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
if compareURI(b.old, val) {
|
||||
return nil
|
||||
}
|
||||
*b.val = val
|
||||
b.old = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *boundExternalURI) Reload() error {
|
||||
return b.Set(*b.val)
|
||||
}
|
1786
vendor/fyne.io/fyne/v2/data/binding/bindlists.go
generated
vendored
1786
vendor/fyne.io/fyne/v2/data/binding/bindlists.go
generated
vendored
File diff suppressed because it is too large
Load Diff
13
vendor/fyne.io/fyne/v2/data/binding/comparator_helper.go
generated
vendored
13
vendor/fyne.io/fyne/v2/data/binding/comparator_helper.go
generated
vendored
@@ -1,13 +0,0 @@
|
||||
package binding
|
||||
|
||||
import "fyne.io/fyne/v2"
|
||||
|
||||
func compareURI(v1, v2 fyne.URI) bool {
|
||||
if v1 == nil && v1 == v2 {
|
||||
return true
|
||||
}
|
||||
if v1 == nil || v2 == nil {
|
||||
return false
|
||||
}
|
||||
return v1.String() == v2.String()
|
||||
}
|
638
vendor/fyne.io/fyne/v2/data/binding/convert.go
generated
vendored
638
vendor/fyne.io/fyne/v2/data/binding/convert.go
generated
vendored
@@ -1,638 +0,0 @@
|
||||
// auto-generated
|
||||
// **** THIS FILE IS AUTO-GENERATED, PLEASE DO NOT EDIT IT **** //
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
type stringFromBool struct {
|
||||
base
|
||||
|
||||
format string
|
||||
|
||||
from Bool
|
||||
}
|
||||
|
||||
// BoolToString creates a binding that connects a Bool data item to a String.
|
||||
// Changes to the Bool will be pushed to the String and setting the string will parse and set the
|
||||
// Bool if the parse was successful.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BoolToString(v Bool) String {
|
||||
str := &stringFromBool{from: v}
|
||||
v.AddListener(str)
|
||||
return str
|
||||
}
|
||||
|
||||
// BoolToStringWithFormat creates a binding that connects a Bool data item to a String and is
|
||||
// presented using the specified format. Changes to the Bool will be pushed to the String and setting
|
||||
// the string will parse and set the Bool if the string matches the format and its parse was successful.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BoolToStringWithFormat(v Bool, format string) String {
|
||||
if format == "%t" { // Same as not using custom formatting.
|
||||
return BoolToString(v)
|
||||
}
|
||||
|
||||
str := &stringFromBool{from: v, format: format}
|
||||
v.AddListener(str)
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *stringFromBool) Get() (string, error) {
|
||||
val, err := s.from.Get()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if s.format != "" {
|
||||
return fmt.Sprintf(s.format, val), nil
|
||||
}
|
||||
|
||||
return formatBool(val), nil
|
||||
}
|
||||
|
||||
func (s *stringFromBool) Set(str string) error {
|
||||
var val bool
|
||||
if s.format != "" {
|
||||
safe := stripFormatPrecision(s.format)
|
||||
n, err := fmt.Sscanf(str, safe+" ", &val) // " " denotes match to end of string
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return errParseFailed
|
||||
}
|
||||
} else {
|
||||
new, err := parseBool(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val = new
|
||||
}
|
||||
|
||||
old, err := s.from.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val == old {
|
||||
return nil
|
||||
}
|
||||
if err = s.from.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DataChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringFromBool) DataChanged() {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.trigger()
|
||||
}
|
||||
|
||||
type stringFromFloat struct {
|
||||
base
|
||||
|
||||
format string
|
||||
|
||||
from Float
|
||||
}
|
||||
|
||||
// FloatToString creates a binding that connects a Float data item to a String.
|
||||
// Changes to the Float will be pushed to the String and setting the string will parse and set the
|
||||
// Float if the parse was successful.
|
||||
//
|
||||
// Since: 2.0
|
||||
func FloatToString(v Float) String {
|
||||
str := &stringFromFloat{from: v}
|
||||
v.AddListener(str)
|
||||
return str
|
||||
}
|
||||
|
||||
// FloatToStringWithFormat creates a binding that connects a Float data item to a String and is
|
||||
// presented using the specified format. Changes to the Float will be pushed to the String and setting
|
||||
// the string will parse and set the Float if the string matches the format and its parse was successful.
|
||||
//
|
||||
// Since: 2.0
|
||||
func FloatToStringWithFormat(v Float, format string) String {
|
||||
if format == "%f" { // Same as not using custom formatting.
|
||||
return FloatToString(v)
|
||||
}
|
||||
|
||||
str := &stringFromFloat{from: v, format: format}
|
||||
v.AddListener(str)
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *stringFromFloat) Get() (string, error) {
|
||||
val, err := s.from.Get()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if s.format != "" {
|
||||
return fmt.Sprintf(s.format, val), nil
|
||||
}
|
||||
|
||||
return formatFloat(val), nil
|
||||
}
|
||||
|
||||
func (s *stringFromFloat) Set(str string) error {
|
||||
var val float64
|
||||
if s.format != "" {
|
||||
safe := stripFormatPrecision(s.format)
|
||||
n, err := fmt.Sscanf(str, safe+" ", &val) // " " denotes match to end of string
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return errParseFailed
|
||||
}
|
||||
} else {
|
||||
new, err := parseFloat(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val = new
|
||||
}
|
||||
|
||||
old, err := s.from.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val == old {
|
||||
return nil
|
||||
}
|
||||
if err = s.from.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DataChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringFromFloat) DataChanged() {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.trigger()
|
||||
}
|
||||
|
||||
type stringFromInt struct {
|
||||
base
|
||||
|
||||
format string
|
||||
|
||||
from Int
|
||||
}
|
||||
|
||||
// IntToString creates a binding that connects a Int data item to a String.
|
||||
// Changes to the Int will be pushed to the String and setting the string will parse and set the
|
||||
// Int if the parse was successful.
|
||||
//
|
||||
// Since: 2.0
|
||||
func IntToString(v Int) String {
|
||||
str := &stringFromInt{from: v}
|
||||
v.AddListener(str)
|
||||
return str
|
||||
}
|
||||
|
||||
// IntToStringWithFormat creates a binding that connects a Int data item to a String and is
|
||||
// presented using the specified format. Changes to the Int will be pushed to the String and setting
|
||||
// the string will parse and set the Int if the string matches the format and its parse was successful.
|
||||
//
|
||||
// Since: 2.0
|
||||
func IntToStringWithFormat(v Int, format string) String {
|
||||
if format == "%d" { // Same as not using custom formatting.
|
||||
return IntToString(v)
|
||||
}
|
||||
|
||||
str := &stringFromInt{from: v, format: format}
|
||||
v.AddListener(str)
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *stringFromInt) Get() (string, error) {
|
||||
val, err := s.from.Get()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if s.format != "" {
|
||||
return fmt.Sprintf(s.format, val), nil
|
||||
}
|
||||
|
||||
return formatInt(val), nil
|
||||
}
|
||||
|
||||
func (s *stringFromInt) Set(str string) error {
|
||||
var val int
|
||||
if s.format != "" {
|
||||
safe := stripFormatPrecision(s.format)
|
||||
n, err := fmt.Sscanf(str, safe+" ", &val) // " " denotes match to end of string
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n != 1 {
|
||||
return errParseFailed
|
||||
}
|
||||
} else {
|
||||
new, err := parseInt(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
val = new
|
||||
}
|
||||
|
||||
old, err := s.from.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val == old {
|
||||
return nil
|
||||
}
|
||||
if err = s.from.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DataChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringFromInt) DataChanged() {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.trigger()
|
||||
}
|
||||
|
||||
type stringFromURI struct {
|
||||
base
|
||||
|
||||
from URI
|
||||
}
|
||||
|
||||
// URIToString creates a binding that connects a URI data item to a String.
|
||||
// Changes to the URI will be pushed to the String and setting the string will parse and set the
|
||||
// URI if the parse was successful.
|
||||
//
|
||||
// Since: 2.1
|
||||
func URIToString(v URI) String {
|
||||
str := &stringFromURI{from: v}
|
||||
v.AddListener(str)
|
||||
return str
|
||||
}
|
||||
|
||||
func (s *stringFromURI) Get() (string, error) {
|
||||
val, err := s.from.Get()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return uriToString(val)
|
||||
}
|
||||
|
||||
func (s *stringFromURI) Set(str string) error {
|
||||
val, err := uriFromString(str)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
old, err := s.from.Get()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if val == old {
|
||||
return nil
|
||||
}
|
||||
if err = s.from.Set(val); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DataChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringFromURI) DataChanged() {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.trigger()
|
||||
}
|
||||
|
||||
type stringToBool struct {
|
||||
base
|
||||
|
||||
format string
|
||||
|
||||
from String
|
||||
}
|
||||
|
||||
// StringToBool creates a binding that connects a String data item to a Bool.
|
||||
// Changes to the String will be parsed and pushed to the Bool if the parse was successful, and setting
|
||||
// the Bool update the String binding.
|
||||
//
|
||||
// Since: 2.0
|
||||
func StringToBool(str String) Bool {
|
||||
v := &stringToBool{from: str}
|
||||
str.AddListener(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// StringToBoolWithFormat creates a binding that connects a String data item to a Bool and is
|
||||
// presented using the specified format. Changes to the Bool will be parsed and if the format matches and
|
||||
// the parse is successful it will be pushed to the String. Setting the Bool will push a formatted value
|
||||
// into the String.
|
||||
//
|
||||
// Since: 2.0
|
||||
func StringToBoolWithFormat(str String, format string) Bool {
|
||||
if format == "%t" { // Same as not using custom format.
|
||||
return StringToBool(str)
|
||||
}
|
||||
|
||||
v := &stringToBool{from: str, format: format}
|
||||
str.AddListener(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *stringToBool) Get() (bool, error) {
|
||||
str, err := s.from.Get()
|
||||
if str == "" || err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
var val bool
|
||||
if s.format != "" {
|
||||
n, err := fmt.Sscanf(str, s.format+" ", &val) // " " denotes match to end of string
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if n != 1 {
|
||||
return false, errParseFailed
|
||||
}
|
||||
} else {
|
||||
new, err := parseBool(str)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
val = new
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (s *stringToBool) Set(val bool) error {
|
||||
var str string
|
||||
if s.format != "" {
|
||||
str = fmt.Sprintf(s.format, val)
|
||||
} else {
|
||||
str = formatBool(val)
|
||||
}
|
||||
|
||||
old, err := s.from.Get()
|
||||
if str == old {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.from.Set(str); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DataChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringToBool) DataChanged() {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.trigger()
|
||||
}
|
||||
|
||||
type stringToFloat struct {
|
||||
base
|
||||
|
||||
format string
|
||||
|
||||
from String
|
||||
}
|
||||
|
||||
// StringToFloat creates a binding that connects a String data item to a Float.
|
||||
// Changes to the String will be parsed and pushed to the Float if the parse was successful, and setting
|
||||
// the Float update the String binding.
|
||||
//
|
||||
// Since: 2.0
|
||||
func StringToFloat(str String) Float {
|
||||
v := &stringToFloat{from: str}
|
||||
str.AddListener(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// StringToFloatWithFormat creates a binding that connects a String data item to a Float and is
|
||||
// presented using the specified format. Changes to the Float will be parsed and if the format matches and
|
||||
// the parse is successful it will be pushed to the String. Setting the Float will push a formatted value
|
||||
// into the String.
|
||||
//
|
||||
// Since: 2.0
|
||||
func StringToFloatWithFormat(str String, format string) Float {
|
||||
if format == "%f" { // Same as not using custom format.
|
||||
return StringToFloat(str)
|
||||
}
|
||||
|
||||
v := &stringToFloat{from: str, format: format}
|
||||
str.AddListener(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *stringToFloat) Get() (float64, error) {
|
||||
str, err := s.from.Get()
|
||||
if str == "" || err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
|
||||
var val float64
|
||||
if s.format != "" {
|
||||
n, err := fmt.Sscanf(str, s.format+" ", &val) // " " denotes match to end of string
|
||||
if err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
if n != 1 {
|
||||
return 0.0, errParseFailed
|
||||
}
|
||||
} else {
|
||||
new, err := parseFloat(str)
|
||||
if err != nil {
|
||||
return 0.0, err
|
||||
}
|
||||
val = new
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (s *stringToFloat) Set(val float64) error {
|
||||
var str string
|
||||
if s.format != "" {
|
||||
str = fmt.Sprintf(s.format, val)
|
||||
} else {
|
||||
str = formatFloat(val)
|
||||
}
|
||||
|
||||
old, err := s.from.Get()
|
||||
if str == old {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.from.Set(str); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DataChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringToFloat) DataChanged() {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.trigger()
|
||||
}
|
||||
|
||||
type stringToInt struct {
|
||||
base
|
||||
|
||||
format string
|
||||
|
||||
from String
|
||||
}
|
||||
|
||||
// StringToInt creates a binding that connects a String data item to a Int.
|
||||
// Changes to the String will be parsed and pushed to the Int if the parse was successful, and setting
|
||||
// the Int update the String binding.
|
||||
//
|
||||
// Since: 2.0
|
||||
func StringToInt(str String) Int {
|
||||
v := &stringToInt{from: str}
|
||||
str.AddListener(v)
|
||||
return v
|
||||
}
|
||||
|
||||
// StringToIntWithFormat creates a binding that connects a String data item to a Int and is
|
||||
// presented using the specified format. Changes to the Int will be parsed and if the format matches and
|
||||
// the parse is successful it will be pushed to the String. Setting the Int will push a formatted value
|
||||
// into the String.
|
||||
//
|
||||
// Since: 2.0
|
||||
func StringToIntWithFormat(str String, format string) Int {
|
||||
if format == "%d" { // Same as not using custom format.
|
||||
return StringToInt(str)
|
||||
}
|
||||
|
||||
v := &stringToInt{from: str, format: format}
|
||||
str.AddListener(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *stringToInt) Get() (int, error) {
|
||||
str, err := s.from.Get()
|
||||
if str == "" || err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var val int
|
||||
if s.format != "" {
|
||||
n, err := fmt.Sscanf(str, s.format+" ", &val) // " " denotes match to end of string
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n != 1 {
|
||||
return 0, errParseFailed
|
||||
}
|
||||
} else {
|
||||
new, err := parseInt(str)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
val = new
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (s *stringToInt) Set(val int) error {
|
||||
var str string
|
||||
if s.format != "" {
|
||||
str = fmt.Sprintf(s.format, val)
|
||||
} else {
|
||||
str = formatInt(val)
|
||||
}
|
||||
|
||||
old, err := s.from.Get()
|
||||
if str == old {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.from.Set(str); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DataChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringToInt) DataChanged() {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.trigger()
|
||||
}
|
||||
|
||||
type stringToURI struct {
|
||||
base
|
||||
|
||||
from String
|
||||
}
|
||||
|
||||
// StringToURI creates a binding that connects a String data item to a URI.
|
||||
// Changes to the String will be parsed and pushed to the URI if the parse was successful, and setting
|
||||
// the URI update the String binding.
|
||||
//
|
||||
// Since: 2.1
|
||||
func StringToURI(str String) URI {
|
||||
v := &stringToURI{from: str}
|
||||
str.AddListener(v)
|
||||
return v
|
||||
}
|
||||
|
||||
func (s *stringToURI) Get() (fyne.URI, error) {
|
||||
str, err := s.from.Get()
|
||||
if str == "" || err != nil {
|
||||
return fyne.URI(nil), err
|
||||
}
|
||||
|
||||
return uriFromString(str)
|
||||
}
|
||||
|
||||
func (s *stringToURI) Set(val fyne.URI) error {
|
||||
str, err := uriToString(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
old, err := s.from.Get()
|
||||
if str == old {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = s.from.Set(str); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.DataChanged()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *stringToURI) DataChanged() {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
s.trigger()
|
||||
}
|
103
vendor/fyne.io/fyne/v2/data/binding/convert_helper.go
generated
vendored
103
vendor/fyne.io/fyne/v2/data/binding/convert_helper.go
generated
vendored
@@ -1,103 +0,0 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
)
|
||||
|
||||
func stripFormatPrecision(in string) string {
|
||||
// quick exit if certainly not float
|
||||
if !strings.ContainsAny(in, "f") {
|
||||
return in
|
||||
}
|
||||
|
||||
start := -1
|
||||
end := -1
|
||||
runes := []rune(in)
|
||||
for i, r := range runes {
|
||||
switch r {
|
||||
case '%':
|
||||
if i > 0 && start == i-1 { // ignore %%
|
||||
start = -1
|
||||
} else {
|
||||
start = i
|
||||
}
|
||||
case 'f':
|
||||
if start == -1 { // not part of format
|
||||
continue
|
||||
}
|
||||
end = i
|
||||
}
|
||||
|
||||
if end > -1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if end == start+1 { // no width/precision
|
||||
return in
|
||||
}
|
||||
|
||||
sizeRunes := runes[start+1 : end]
|
||||
width, err := parseFloat(string(sizeRunes))
|
||||
if err != nil {
|
||||
return string(runes[:start+1]) + string(runes[:end])
|
||||
}
|
||||
|
||||
if sizeRunes[0] == '.' { // formats like %.2f
|
||||
return string(runes[:start+1]) + string(runes[end:])
|
||||
}
|
||||
return string(runes[:start+1]) + strconv.Itoa(int(width)) + string(runes[end:])
|
||||
}
|
||||
|
||||
func uriFromString(in string) (fyne.URI, error) {
|
||||
return storage.ParseURI(in)
|
||||
}
|
||||
|
||||
func uriToString(in fyne.URI) (string, error) {
|
||||
if in == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return in.String(), nil
|
||||
}
|
||||
|
||||
func parseBool(in string) (bool, error) {
|
||||
out, err := strconv.ParseBool(in)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseFloat(in string) (float64, error) {
|
||||
out, err := strconv.ParseFloat(in, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func parseInt(in string) (int, error) {
|
||||
out, err := strconv.ParseInt(in, 0, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(out), nil
|
||||
}
|
||||
|
||||
func formatBool(in bool) string {
|
||||
return strconv.FormatBool(in)
|
||||
}
|
||||
|
||||
func formatFloat(in float64) string {
|
||||
return strconv.FormatFloat(in, 'f', 6, 64)
|
||||
}
|
||||
|
||||
func formatInt(in int) string {
|
||||
return strconv.FormatInt(int64(in), 10)
|
||||
}
|
37
vendor/fyne.io/fyne/v2/data/binding/listbinding.go
generated
vendored
37
vendor/fyne.io/fyne/v2/data/binding/listbinding.go
generated
vendored
@@ -1,37 +0,0 @@
|
||||
package binding
|
||||
|
||||
// DataList is the base interface for all bindable data lists.
|
||||
//
|
||||
// Since: 2.0
|
||||
type DataList interface {
|
||||
DataItem
|
||||
GetItem(index int) (DataItem, error)
|
||||
Length() int
|
||||
}
|
||||
|
||||
type listBase struct {
|
||||
base
|
||||
items []DataItem
|
||||
}
|
||||
|
||||
// GetItem returns the DataItem at the specified index.
|
||||
func (b *listBase) GetItem(i int) (DataItem, error) {
|
||||
if i < 0 || i >= len(b.items) {
|
||||
return nil, errOutOfBounds
|
||||
}
|
||||
|
||||
return b.items[i], nil
|
||||
}
|
||||
|
||||
// Length returns the number of items in this data list.
|
||||
func (b *listBase) Length() int {
|
||||
return len(b.items)
|
||||
}
|
||||
|
||||
func (b *listBase) appendItem(i DataItem) {
|
||||
b.items = append(b.items, i)
|
||||
}
|
||||
|
||||
func (b *listBase) deleteItem(i int) {
|
||||
b.items = append(b.items[:i], b.items[i+1:]...)
|
||||
}
|
534
vendor/fyne.io/fyne/v2/data/binding/mapbinding.go
generated
vendored
534
vendor/fyne.io/fyne/v2/data/binding/mapbinding.go
generated
vendored
@@ -1,534 +0,0 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// DataMap is the base interface for all bindable data maps.
|
||||
//
|
||||
// Since: 2.0
|
||||
type DataMap interface {
|
||||
DataItem
|
||||
GetItem(string) (DataItem, error)
|
||||
Keys() []string
|
||||
}
|
||||
|
||||
// ExternalUntypedMap is a map data binding with all values untyped (interface{}), connected to an external data source.
|
||||
//
|
||||
// Since: 2.0
|
||||
type ExternalUntypedMap interface {
|
||||
UntypedMap
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// UntypedMap is a map data binding with all values Untyped (interface{}).
|
||||
//
|
||||
// Since: 2.0
|
||||
type UntypedMap interface {
|
||||
DataMap
|
||||
Delete(string)
|
||||
Get() (map[string]interface{}, error)
|
||||
GetValue(string) (interface{}, error)
|
||||
Set(map[string]interface{}) error
|
||||
SetValue(string, interface{}) error
|
||||
}
|
||||
|
||||
// NewUntypedMap creates a new, empty map binding of string to interface{}.
|
||||
//
|
||||
// Since: 2.0
|
||||
func NewUntypedMap() UntypedMap {
|
||||
return &mapBase{items: make(map[string]reflectUntyped), val: &map[string]interface{}{}}
|
||||
}
|
||||
|
||||
// BindUntypedMap creates a new map binding of string to interface{} based on the data passed.
|
||||
// If your code changes the content of the map this refers to you should call Reload() to inform the bindings.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindUntypedMap(d *map[string]interface{}) ExternalUntypedMap {
|
||||
if d == nil {
|
||||
return NewUntypedMap().(ExternalUntypedMap)
|
||||
}
|
||||
m := &mapBase{items: make(map[string]reflectUntyped), val: d, updateExternal: true}
|
||||
|
||||
for k := range *d {
|
||||
m.setItem(k, bindUntypedMapValue(d, k, m.updateExternal))
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Struct is the base interface for a bound struct type.
|
||||
//
|
||||
// Since: 2.0
|
||||
type Struct interface {
|
||||
DataMap
|
||||
GetValue(string) (interface{}, error)
|
||||
SetValue(string, interface{}) error
|
||||
Reload() error
|
||||
}
|
||||
|
||||
// BindStruct creates a new map binding of string to interface{} using the struct passed as data.
|
||||
// The key for each item is a string representation of each exported field with the value set as an interface{}.
|
||||
// Only exported fields are included.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindStruct(i interface{}) Struct {
|
||||
if i == nil {
|
||||
return NewUntypedMap().(Struct)
|
||||
}
|
||||
t := reflect.TypeOf(i)
|
||||
if t.Kind() != reflect.Ptr ||
|
||||
(reflect.TypeOf(reflect.ValueOf(i).Elem()).Kind() != reflect.Struct) {
|
||||
fyne.LogError("Invalid type passed to BindStruct, must be pointer to struct", nil)
|
||||
return NewUntypedMap().(Struct)
|
||||
}
|
||||
|
||||
s := &boundStruct{orig: i}
|
||||
s.items = make(map[string]reflectUntyped)
|
||||
s.val = &map[string]interface{}{}
|
||||
s.updateExternal = true
|
||||
|
||||
v := reflect.ValueOf(i).Elem()
|
||||
t = v.Type()
|
||||
for j := 0; j < v.NumField(); j++ {
|
||||
f := v.Field(j)
|
||||
if !f.CanSet() {
|
||||
continue
|
||||
}
|
||||
|
||||
key := t.Field(j).Name
|
||||
s.items[key] = bindReflect(f)
|
||||
(*s.val)[key] = f.Interface()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
type reflectUntyped interface {
|
||||
DataItem
|
||||
get() (interface{}, error)
|
||||
set(interface{}) error
|
||||
}
|
||||
|
||||
type mapBase struct {
|
||||
base
|
||||
|
||||
updateExternal bool
|
||||
items map[string]reflectUntyped
|
||||
val *map[string]interface{}
|
||||
}
|
||||
|
||||
func (b *mapBase) GetItem(key string) (DataItem, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if v, ok := b.items[key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, errKeyNotFound
|
||||
}
|
||||
|
||||
func (b *mapBase) Keys() []string {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
ret := make([]string, len(b.items))
|
||||
i := 0
|
||||
for k := range b.items {
|
||||
ret[i] = k
|
||||
i++
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (b *mapBase) Delete(key string) {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
delete(b.items, key)
|
||||
|
||||
b.trigger()
|
||||
}
|
||||
|
||||
func (b *mapBase) Get() (map[string]interface{}, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if b.val == nil {
|
||||
return map[string]interface{}{}, nil
|
||||
}
|
||||
|
||||
return *b.val, nil
|
||||
}
|
||||
|
||||
func (b *mapBase) GetValue(key string) (interface{}, error) {
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
|
||||
if i, ok := b.items[key]; ok {
|
||||
return i.get()
|
||||
}
|
||||
|
||||
return nil, errKeyNotFound
|
||||
}
|
||||
|
||||
func (b *mapBase) Reload() error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
return b.doReload()
|
||||
}
|
||||
|
||||
func (b *mapBase) Set(v map[string]interface{}) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if b.val == nil { // was not initialized with a blank value, recover
|
||||
b.val = &v
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
*b.val = v
|
||||
return b.doReload()
|
||||
}
|
||||
|
||||
func (b *mapBase) SetValue(key string, d interface{}) error {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
if i, ok := b.items[key]; ok {
|
||||
return i.set(d)
|
||||
}
|
||||
|
||||
(*b.val)[key] = d
|
||||
item := bindUntypedMapValue(b.val, key, b.updateExternal)
|
||||
b.setItem(key, item)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *mapBase) doReload() (retErr error) {
|
||||
changed := false
|
||||
// add new
|
||||
for key := range *b.val {
|
||||
found := false
|
||||
for newKey := range b.items {
|
||||
if newKey == key {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
b.setItem(key, bindUntypedMapValue(b.val, key, b.updateExternal))
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
|
||||
// remove old
|
||||
for key := range b.items {
|
||||
found := false
|
||||
for newKey := range *b.val {
|
||||
if newKey == key {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
delete(b.items, key)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
b.trigger()
|
||||
}
|
||||
|
||||
for k, item := range b.items {
|
||||
var err error
|
||||
|
||||
if b.updateExternal {
|
||||
err = item.(*boundExternalMapValue).setIfChanged((*b.val)[k])
|
||||
} else {
|
||||
err = item.(*boundMapValue).set((*b.val)[k])
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
retErr = err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (b *mapBase) setItem(key string, d reflectUntyped) {
|
||||
b.items[key] = d
|
||||
|
||||
b.trigger()
|
||||
}
|
||||
|
||||
type boundStruct struct {
|
||||
mapBase
|
||||
|
||||
orig interface{}
|
||||
}
|
||||
|
||||
func (b *boundStruct) Reload() (retErr error) {
|
||||
b.lock.Lock()
|
||||
defer b.lock.Unlock()
|
||||
|
||||
v := reflect.ValueOf(b.orig).Elem()
|
||||
t := v.Type()
|
||||
for j := 0; j < v.NumField(); j++ {
|
||||
f := v.Field(j)
|
||||
if !f.CanSet() {
|
||||
continue
|
||||
}
|
||||
kind := f.Kind()
|
||||
if kind == reflect.Slice || kind == reflect.Struct {
|
||||
fyne.LogError("Data binding does not yet support slice or struct elements in a struct", nil)
|
||||
continue
|
||||
}
|
||||
|
||||
key := t.Field(j).Name
|
||||
old := (*b.val)[key]
|
||||
if f.Interface() == old {
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
err = b.items[key].(*reflectBool).Set(f.Bool())
|
||||
case reflect.Float32, reflect.Float64:
|
||||
err = b.items[key].(*reflectFloat).Set(f.Float())
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
err = b.items[key].(*reflectInt).Set(int(f.Int()))
|
||||
case reflect.String:
|
||||
err = b.items[key].(*reflectString).Set(f.String())
|
||||
}
|
||||
if err != nil {
|
||||
retErr = err
|
||||
}
|
||||
(*b.val)[key] = f.Interface()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func bindUntypedMapValue(m *map[string]interface{}, k string, external bool) reflectUntyped {
|
||||
if external {
|
||||
ret := &boundExternalMapValue{old: (*m)[k]}
|
||||
ret.val = m
|
||||
ret.key = k
|
||||
return ret
|
||||
}
|
||||
|
||||
return &boundMapValue{val: m, key: k}
|
||||
}
|
||||
|
||||
type boundMapValue struct {
|
||||
base
|
||||
|
||||
val *map[string]interface{}
|
||||
key string
|
||||
}
|
||||
|
||||
func (b *boundMapValue) get() (interface{}, error) {
|
||||
if v, ok := (*b.val)[b.key]; ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, errKeyNotFound
|
||||
}
|
||||
|
||||
func (b *boundMapValue) set(val interface{}) error {
|
||||
(*b.val)[b.key] = val
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type boundExternalMapValue struct {
|
||||
boundMapValue
|
||||
|
||||
old interface{}
|
||||
}
|
||||
|
||||
func (b *boundExternalMapValue) setIfChanged(val interface{}) error {
|
||||
if val == b.old {
|
||||
return nil
|
||||
}
|
||||
b.old = val
|
||||
|
||||
return b.set(val)
|
||||
}
|
||||
|
||||
type boundReflect struct {
|
||||
base
|
||||
|
||||
val reflect.Value
|
||||
}
|
||||
|
||||
func (b *boundReflect) get() (interface{}, error) {
|
||||
return b.val.Interface(), nil
|
||||
}
|
||||
|
||||
func (b *boundReflect) set(val interface{}) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("unable to set bool in data binding")
|
||||
}
|
||||
}()
|
||||
b.val.Set(reflect.ValueOf(val))
|
||||
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
type reflectBool struct {
|
||||
boundReflect
|
||||
}
|
||||
|
||||
func (r *reflectBool) Get() (val bool, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("invalid bool value in data binding")
|
||||
}
|
||||
}()
|
||||
|
||||
val = r.val.Bool()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *reflectBool) Set(b bool) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("unable to set bool in data binding")
|
||||
}
|
||||
}()
|
||||
|
||||
r.val.SetBool(b)
|
||||
r.trigger()
|
||||
return
|
||||
}
|
||||
|
||||
func bindReflectBool(f reflect.Value) reflectUntyped {
|
||||
r := &reflectBool{}
|
||||
r.val = f
|
||||
return r
|
||||
}
|
||||
|
||||
type reflectFloat struct {
|
||||
boundReflect
|
||||
}
|
||||
|
||||
func (r *reflectFloat) Get() (val float64, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("invalid float64 value in data binding")
|
||||
}
|
||||
}()
|
||||
|
||||
val = r.val.Float()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *reflectFloat) Set(f float64) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("unable to set float64 in data binding")
|
||||
}
|
||||
}()
|
||||
|
||||
r.val.SetFloat(f)
|
||||
r.trigger()
|
||||
return
|
||||
}
|
||||
|
||||
func bindReflectFloat(f reflect.Value) reflectUntyped {
|
||||
r := &reflectFloat{}
|
||||
r.val = f
|
||||
return r
|
||||
}
|
||||
|
||||
type reflectInt struct {
|
||||
boundReflect
|
||||
}
|
||||
|
||||
func (r *reflectInt) Get() (val int, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("invalid int value in data binding")
|
||||
}
|
||||
}()
|
||||
|
||||
val = int(r.val.Int())
|
||||
return
|
||||
}
|
||||
|
||||
func (r *reflectInt) Set(i int) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("unable to set int in data binding")
|
||||
}
|
||||
}()
|
||||
|
||||
r.val.SetInt(int64(i))
|
||||
r.trigger()
|
||||
return
|
||||
}
|
||||
|
||||
func bindReflectInt(f reflect.Value) reflectUntyped {
|
||||
r := &reflectInt{}
|
||||
r.val = f
|
||||
return r
|
||||
}
|
||||
|
||||
type reflectString struct {
|
||||
boundReflect
|
||||
}
|
||||
|
||||
func (r *reflectString) Get() (val string, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("invalid string value in data binding")
|
||||
}
|
||||
}()
|
||||
|
||||
val = r.val.String()
|
||||
return
|
||||
}
|
||||
|
||||
func (r *reflectString) Set(s string) (err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = errors.New("unable to set string in data binding")
|
||||
}
|
||||
}()
|
||||
|
||||
r.val.SetString(s)
|
||||
r.trigger()
|
||||
return
|
||||
}
|
||||
|
||||
func bindReflectString(f reflect.Value) reflectUntyped {
|
||||
r := &reflectString{}
|
||||
r.val = f
|
||||
return r
|
||||
}
|
||||
|
||||
func bindReflect(field reflect.Value) reflectUntyped {
|
||||
switch field.Kind() {
|
||||
case reflect.Bool:
|
||||
return bindReflectBool(field)
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return bindReflectFloat(field)
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return bindReflectInt(field)
|
||||
case reflect.String:
|
||||
return bindReflectString(field)
|
||||
}
|
||||
return &boundReflect{val: field}
|
||||
}
|
72
vendor/fyne.io/fyne/v2/data/binding/pref_helper.go
generated
vendored
72
vendor/fyne.io/fyne/v2/data/binding/pref_helper.go
generated
vendored
@@ -1,72 +0,0 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
type preferenceItem interface {
|
||||
checkForChange()
|
||||
}
|
||||
|
||||
type preferenceBindings struct {
|
||||
items sync.Map // map[string]preferenceItem
|
||||
}
|
||||
|
||||
func (b *preferenceBindings) getItem(key string) preferenceItem {
|
||||
val, loaded := b.items.Load(key)
|
||||
if !loaded {
|
||||
return nil
|
||||
}
|
||||
return val.(preferenceItem)
|
||||
}
|
||||
|
||||
func (b *preferenceBindings) list() []preferenceItem {
|
||||
ret := []preferenceItem{}
|
||||
b.items.Range(func(_, val interface{}) bool {
|
||||
ret = append(ret, val.(preferenceItem))
|
||||
return true
|
||||
})
|
||||
return ret
|
||||
}
|
||||
|
||||
func (b *preferenceBindings) setItem(key string, item preferenceItem) {
|
||||
b.items.Store(key, item)
|
||||
}
|
||||
|
||||
type preferencesMap struct {
|
||||
prefs sync.Map // map[fyne.Preferences]*preferenceBindings
|
||||
}
|
||||
|
||||
func newPreferencesMap() *preferencesMap {
|
||||
return &preferencesMap{}
|
||||
}
|
||||
|
||||
func (m *preferencesMap) ensurePreferencesAttached(p fyne.Preferences) *preferenceBindings {
|
||||
binds, loaded := m.prefs.LoadOrStore(p, &preferenceBindings{})
|
||||
if loaded {
|
||||
return binds.(*preferenceBindings)
|
||||
}
|
||||
|
||||
p.AddChangeListener(func() { m.preferencesChanged(p) })
|
||||
return binds.(*preferenceBindings)
|
||||
}
|
||||
|
||||
func (m *preferencesMap) getBindings(p fyne.Preferences) *preferenceBindings {
|
||||
binds, loaded := m.prefs.Load(p)
|
||||
if !loaded {
|
||||
return nil
|
||||
}
|
||||
return binds.(*preferenceBindings)
|
||||
}
|
||||
|
||||
func (m *preferencesMap) preferencesChanged(p fyne.Preferences) {
|
||||
binds := m.getBindings(p)
|
||||
if binds == nil {
|
||||
return
|
||||
}
|
||||
for _, item := range binds.list() {
|
||||
item.checkForChange()
|
||||
}
|
||||
}
|
228
vendor/fyne.io/fyne/v2/data/binding/preference.go
generated
vendored
228
vendor/fyne.io/fyne/v2/data/binding/preference.go
generated
vendored
@@ -1,228 +0,0 @@
|
||||
// auto-generated
|
||||
// **** THIS FILE IS AUTO-GENERATED, PLEASE DO NOT EDIT IT **** //
|
||||
|
||||
package binding
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
const keyTypeMismatchError = "A previous preference binding exists with different type for key: "
|
||||
|
||||
type prefBoundBool struct {
|
||||
base
|
||||
key string
|
||||
p fyne.Preferences
|
||||
cache atomic.Value // bool
|
||||
}
|
||||
|
||||
// BindPreferenceBool returns a bindable bool value that is managed by the application preferences.
|
||||
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindPreferenceBool(key string, p fyne.Preferences) Bool {
|
||||
binds := prefBinds.getBindings(p)
|
||||
if binds != nil {
|
||||
if listen := binds.getItem(key); listen != nil {
|
||||
if l, ok := listen.(Bool); ok {
|
||||
return l
|
||||
}
|
||||
fyne.LogError(keyTypeMismatchError+key, nil)
|
||||
}
|
||||
}
|
||||
|
||||
listen := &prefBoundBool{key: key, p: p}
|
||||
binds = prefBinds.ensurePreferencesAttached(p)
|
||||
binds.setItem(key, listen)
|
||||
return listen
|
||||
}
|
||||
|
||||
func (b *prefBoundBool) Get() (bool, error) {
|
||||
cache := b.p.Bool(b.key)
|
||||
b.cache.Store(cache)
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func (b *prefBoundBool) Set(v bool) error {
|
||||
b.p.SetBool(b.key, v)
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *prefBoundBool) checkForChange() {
|
||||
val := b.cache.Load()
|
||||
if val != nil {
|
||||
cache := val.(bool)
|
||||
if b.p.Bool(b.key) == cache {
|
||||
return
|
||||
}
|
||||
}
|
||||
b.trigger()
|
||||
}
|
||||
|
||||
type prefBoundFloat struct {
|
||||
base
|
||||
key string
|
||||
p fyne.Preferences
|
||||
cache atomic.Value // float64
|
||||
}
|
||||
|
||||
// BindPreferenceFloat returns a bindable float64 value that is managed by the application preferences.
|
||||
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindPreferenceFloat(key string, p fyne.Preferences) Float {
|
||||
binds := prefBinds.getBindings(p)
|
||||
if binds != nil {
|
||||
if listen := binds.getItem(key); listen != nil {
|
||||
if l, ok := listen.(Float); ok {
|
||||
return l
|
||||
}
|
||||
fyne.LogError(keyTypeMismatchError+key, nil)
|
||||
}
|
||||
}
|
||||
|
||||
listen := &prefBoundFloat{key: key, p: p}
|
||||
binds = prefBinds.ensurePreferencesAttached(p)
|
||||
binds.setItem(key, listen)
|
||||
return listen
|
||||
}
|
||||
|
||||
func (b *prefBoundFloat) Get() (float64, error) {
|
||||
cache := b.p.Float(b.key)
|
||||
b.cache.Store(cache)
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func (b *prefBoundFloat) Set(v float64) error {
|
||||
b.p.SetFloat(b.key, v)
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *prefBoundFloat) checkForChange() {
|
||||
val := b.cache.Load()
|
||||
if val != nil {
|
||||
cache := val.(float64)
|
||||
if b.p.Float(b.key) == cache {
|
||||
return
|
||||
}
|
||||
}
|
||||
b.trigger()
|
||||
}
|
||||
|
||||
type prefBoundInt struct {
|
||||
base
|
||||
key string
|
||||
p fyne.Preferences
|
||||
cache atomic.Value // int
|
||||
}
|
||||
|
||||
// BindPreferenceInt returns a bindable int value that is managed by the application preferences.
|
||||
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindPreferenceInt(key string, p fyne.Preferences) Int {
|
||||
binds := prefBinds.getBindings(p)
|
||||
if binds != nil {
|
||||
if listen := binds.getItem(key); listen != nil {
|
||||
if l, ok := listen.(Int); ok {
|
||||
return l
|
||||
}
|
||||
fyne.LogError(keyTypeMismatchError+key, nil)
|
||||
}
|
||||
}
|
||||
|
||||
listen := &prefBoundInt{key: key, p: p}
|
||||
binds = prefBinds.ensurePreferencesAttached(p)
|
||||
binds.setItem(key, listen)
|
||||
return listen
|
||||
}
|
||||
|
||||
func (b *prefBoundInt) Get() (int, error) {
|
||||
cache := b.p.Int(b.key)
|
||||
b.cache.Store(cache)
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func (b *prefBoundInt) Set(v int) error {
|
||||
b.p.SetInt(b.key, v)
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *prefBoundInt) checkForChange() {
|
||||
val := b.cache.Load()
|
||||
if val != nil {
|
||||
cache := val.(int)
|
||||
if b.p.Int(b.key) == cache {
|
||||
return
|
||||
}
|
||||
}
|
||||
b.trigger()
|
||||
}
|
||||
|
||||
type prefBoundString struct {
|
||||
base
|
||||
key string
|
||||
p fyne.Preferences
|
||||
cache atomic.Value // string
|
||||
}
|
||||
|
||||
// BindPreferenceString returns a bindable string value that is managed by the application preferences.
|
||||
// Changes to this value will be saved to application storage and when the app starts the previous values will be read.
|
||||
//
|
||||
// Since: 2.0
|
||||
func BindPreferenceString(key string, p fyne.Preferences) String {
|
||||
binds := prefBinds.getBindings(p)
|
||||
if binds != nil {
|
||||
if listen := binds.getItem(key); listen != nil {
|
||||
if l, ok := listen.(String); ok {
|
||||
return l
|
||||
}
|
||||
fyne.LogError(keyTypeMismatchError+key, nil)
|
||||
}
|
||||
}
|
||||
|
||||
listen := &prefBoundString{key: key, p: p}
|
||||
binds = prefBinds.ensurePreferencesAttached(p)
|
||||
binds.setItem(key, listen)
|
||||
return listen
|
||||
}
|
||||
|
||||
func (b *prefBoundString) Get() (string, error) {
|
||||
cache := b.p.String(b.key)
|
||||
b.cache.Store(cache)
|
||||
return cache, nil
|
||||
}
|
||||
|
||||
func (b *prefBoundString) Set(v string) error {
|
||||
b.p.SetString(b.key, v)
|
||||
|
||||
b.lock.RLock()
|
||||
defer b.lock.RUnlock()
|
||||
b.trigger()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *prefBoundString) checkForChange() {
|
||||
val := b.cache.Load()
|
||||
if val != nil {
|
||||
cache := val.(string)
|
||||
if b.p.String(b.key) == cache {
|
||||
return
|
||||
}
|
||||
}
|
||||
b.trigger()
|
||||
}
|
30
vendor/fyne.io/fyne/v2/data/binding/queue.go
generated
vendored
30
vendor/fyne.io/fyne/v2/data/binding/queue.go
generated
vendored
@@ -1,30 +0,0 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"fyne.io/fyne/v2/internal/async"
|
||||
)
|
||||
|
||||
var (
|
||||
once sync.Once
|
||||
queue *async.UnboundedFuncChan
|
||||
)
|
||||
|
||||
func queueItem(f func()) {
|
||||
once.Do(func() {
|
||||
queue = async.NewUnboundedFuncChan()
|
||||
go func() {
|
||||
for f := range queue.Out() {
|
||||
f()
|
||||
}
|
||||
}()
|
||||
})
|
||||
queue.In() <- f
|
||||
}
|
||||
|
||||
func waitForItems() {
|
||||
done := make(chan struct{})
|
||||
queue.In() <- func() { close(done) }
|
||||
<-done
|
||||
}
|
218
vendor/fyne.io/fyne/v2/data/binding/sprintf.go
generated
vendored
218
vendor/fyne.io/fyne/v2/data/binding/sprintf.go
generated
vendored
@@ -1,218 +0,0 @@
|
||||
package binding
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fyne.io/fyne/v2/storage"
|
||||
)
|
||||
|
||||
type sprintfString struct {
|
||||
String
|
||||
|
||||
format string
|
||||
source []DataItem
|
||||
err error
|
||||
}
|
||||
|
||||
// NewSprintf returns a String binding that format its content using the
|
||||
// format string and the provide additional parameter that must be other
|
||||
// data bindings. This data binding use fmt.Sprintf and fmt.Scanf internally
|
||||
// and will have all the same limitation as those function.
|
||||
//
|
||||
// Since: 2.2
|
||||
func NewSprintf(format string, b ...DataItem) String {
|
||||
ret := &sprintfString{
|
||||
String: NewString(),
|
||||
format: format,
|
||||
source: append(make([]DataItem, 0, len(b)), b...),
|
||||
}
|
||||
|
||||
for _, value := range b {
|
||||
value.AddListener(ret)
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (s *sprintfString) DataChanged() {
|
||||
data := make([]interface{}, 0, len(s.source))
|
||||
|
||||
s.err = nil
|
||||
for _, value := range s.source {
|
||||
switch x := value.(type) {
|
||||
case Bool:
|
||||
b, err := x.Get()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
|
||||
data = append(data, b)
|
||||
case Bytes:
|
||||
b, err := x.Get()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
|
||||
data = append(data, b)
|
||||
case Float:
|
||||
f, err := x.Get()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
|
||||
data = append(data, f)
|
||||
case Int:
|
||||
i, err := x.Get()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
|
||||
data = append(data, i)
|
||||
case Rune:
|
||||
r, err := x.Get()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
|
||||
data = append(data, r)
|
||||
case String:
|
||||
str, err := x.Get()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
// Set error?
|
||||
return
|
||||
}
|
||||
|
||||
data = append(data, str)
|
||||
case URI:
|
||||
u, err := x.Get()
|
||||
if err != nil {
|
||||
s.err = err
|
||||
return
|
||||
}
|
||||
|
||||
data = append(data, u)
|
||||
}
|
||||
}
|
||||
|
||||
r := fmt.Sprintf(s.format, data...)
|
||||
s.String.Set(r)
|
||||
}
|
||||
|
||||
func (s *sprintfString) Get() (string, error) {
|
||||
if s.err != nil {
|
||||
return "", s.err
|
||||
}
|
||||
return s.String.Get()
|
||||
}
|
||||
|
||||
func (s *sprintfString) Set(str string) error {
|
||||
data := make([]interface{}, 0, len(s.source))
|
||||
|
||||
s.err = nil
|
||||
for _, value := range s.source {
|
||||
switch value.(type) {
|
||||
case Bool:
|
||||
data = append(data, new(bool))
|
||||
case Bytes:
|
||||
return fmt.Errorf("impossible to convert '%s' to []bytes type", str)
|
||||
case Float:
|
||||
data = append(data, new(float64))
|
||||
case Int:
|
||||
data = append(data, new(int))
|
||||
case Rune:
|
||||
data = append(data, new(rune))
|
||||
case String:
|
||||
data = append(data, new(string))
|
||||
case URI:
|
||||
data = append(data, new(string))
|
||||
}
|
||||
}
|
||||
|
||||
count, err := fmt.Sscanf(str, s.format, data...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if count != len(data) {
|
||||
return fmt.Errorf("impossible to decode more than %v parameters in '%s' with format '%s'", count, str, s.format)
|
||||
}
|
||||
|
||||
for i, value := range s.source {
|
||||
switch x := value.(type) {
|
||||
case Bool:
|
||||
v := data[i].(*bool)
|
||||
|
||||
err := x.Set(*v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case Bytes:
|
||||
return fmt.Errorf("impossible to convert '%s' to []bytes type", str)
|
||||
case Float:
|
||||
v := data[i].(*float64)
|
||||
|
||||
err := x.Set(*v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case Int:
|
||||
v := data[i].(*int)
|
||||
|
||||
err := x.Set(*v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case Rune:
|
||||
v := data[i].(*rune)
|
||||
|
||||
err := x.Set(*v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case String:
|
||||
v := data[i].(*string)
|
||||
|
||||
err := x.Set(*v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case URI:
|
||||
v := data[i].(*string)
|
||||
|
||||
if v == nil {
|
||||
return fmt.Errorf("URI can not be nil in '%s'", str)
|
||||
}
|
||||
|
||||
uri, err := storage.ParseURI(*v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = x.Set(uri)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StringToStringWithFormat creates a binding that converts a string to another string using the specified format.
|
||||
// Changes to the returned String will be pushed to the passed in String and setting a new string value will parse and
|
||||
// set the underlying String if it matches the format and the parse was successful.
|
||||
//
|
||||
// Since: 2.2
|
||||
func StringToStringWithFormat(str String, format string) String {
|
||||
if format == "%s" { // Same as not using custom formatting.
|
||||
return str
|
||||
}
|
||||
|
||||
return NewSprintf(format, str)
|
||||
}
|
39
vendor/fyne.io/fyne/v2/device.go
generated
vendored
39
vendor/fyne.io/fyne/v2/device.go
generated
vendored
@@ -1,39 +0,0 @@
|
||||
package fyne
|
||||
|
||||
// DeviceOrientation represents the different ways that a mobile device can be held
|
||||
type DeviceOrientation int
|
||||
|
||||
const (
|
||||
// OrientationVertical is the default vertical orientation
|
||||
OrientationVertical DeviceOrientation = iota
|
||||
// OrientationVerticalUpsideDown is the portrait orientation held upside down
|
||||
OrientationVerticalUpsideDown
|
||||
// OrientationHorizontalLeft is used to indicate a landscape orientation with the top to the left
|
||||
OrientationHorizontalLeft
|
||||
// OrientationHorizontalRight is used to indicate a landscape orientation with the top to the right
|
||||
OrientationHorizontalRight
|
||||
)
|
||||
|
||||
// IsVertical is a helper utility that determines if a passed orientation is vertical
|
||||
func IsVertical(orient DeviceOrientation) bool {
|
||||
return orient == OrientationVertical || orient == OrientationVerticalUpsideDown
|
||||
}
|
||||
|
||||
// IsHorizontal is a helper utility that determines if a passed orientation is horizontal
|
||||
func IsHorizontal(orient DeviceOrientation) bool {
|
||||
return !IsVertical(orient)
|
||||
}
|
||||
|
||||
// Device provides information about the devices the code is running on
|
||||
type Device interface {
|
||||
Orientation() DeviceOrientation
|
||||
IsMobile() bool
|
||||
IsBrowser() bool
|
||||
HasKeyboard() bool
|
||||
SystemScaleForWindow(Window) float32
|
||||
}
|
||||
|
||||
// CurrentDevice returns the device information for the current hardware (via the driver)
|
||||
func CurrentDevice() Device {
|
||||
return CurrentApp().Driver().Device()
|
||||
}
|
294
vendor/fyne.io/fyne/v2/dialog/base.go
generated
vendored
294
vendor/fyne.io/fyne/v2/dialog/base.go
generated
vendored
@@ -1,294 +0,0 @@
|
||||
// Package dialog defines standard dialog windows for application GUIs.
|
||||
package dialog // import "fyne.io/fyne/v2/dialog"
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
col "fyne.io/fyne/v2/internal/color"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
const (
|
||||
padWidth = 32
|
||||
padHeight = 16
|
||||
)
|
||||
|
||||
// Dialog is the common API for any dialog window with a single dismiss button
|
||||
type Dialog interface {
|
||||
Show()
|
||||
Hide()
|
||||
SetDismissText(label string)
|
||||
SetOnClosed(closed func())
|
||||
Refresh()
|
||||
Resize(size fyne.Size)
|
||||
|
||||
// Since: 2.1
|
||||
MinSize() fyne.Size
|
||||
}
|
||||
|
||||
// Declare conformity to Dialog interface
|
||||
var _ Dialog = (*dialog)(nil)
|
||||
|
||||
type dialog struct {
|
||||
callback func(bool)
|
||||
title string
|
||||
icon fyne.Resource
|
||||
desiredSize fyne.Size
|
||||
|
||||
win *widget.PopUp
|
||||
bg *themedBackground
|
||||
content, label fyne.CanvasObject
|
||||
dismiss *widget.Button
|
||||
parent fyne.Window
|
||||
layout *dialogLayout
|
||||
}
|
||||
|
||||
// NewCustom creates and returns a dialog over the specified application using custom
|
||||
// content. The button will have the dismiss text set.
|
||||
// The MinSize() of the CanvasObject passed will be used to set the size of the window.
|
||||
func NewCustom(title, dismiss string, content fyne.CanvasObject, parent fyne.Window) Dialog {
|
||||
d := &dialog{content: content, title: title, icon: nil, parent: parent}
|
||||
d.layout = &dialogLayout{d: d}
|
||||
|
||||
d.dismiss = &widget.Button{Text: dismiss,
|
||||
OnTapped: d.Hide,
|
||||
}
|
||||
d.setButtons(container.NewHBox(layout.NewSpacer(), d.dismiss, layout.NewSpacer()))
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// NewCustomConfirm creates and returns a dialog over the specified application using
|
||||
// custom content. The cancel button will have the dismiss text set and the "OK" will
|
||||
// use the confirm text. The response callback is called on user action.
|
||||
// The MinSize() of the CanvasObject passed will be used to set the size of the window.
|
||||
func NewCustomConfirm(title, confirm, dismiss string, content fyne.CanvasObject,
|
||||
callback func(bool), parent fyne.Window) Dialog {
|
||||
d := &dialog{content: content, title: title, icon: nil, parent: parent}
|
||||
d.layout = &dialogLayout{d: d}
|
||||
d.callback = callback
|
||||
|
||||
d.dismiss = &widget.Button{Text: dismiss, Icon: theme.CancelIcon(),
|
||||
OnTapped: d.Hide,
|
||||
}
|
||||
ok := &widget.Button{Text: confirm, Icon: theme.ConfirmIcon(), Importance: widget.HighImportance,
|
||||
OnTapped: func() {
|
||||
d.hideWithResponse(true)
|
||||
},
|
||||
}
|
||||
d.setButtons(container.NewHBox(layout.NewSpacer(), d.dismiss, ok, layout.NewSpacer()))
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
// ShowCustom shows a dialog over the specified application using custom
|
||||
// content. The button will have the dismiss text set.
|
||||
// The MinSize() of the CanvasObject passed will be used to set the size of the window.
|
||||
func ShowCustom(title, dismiss string, content fyne.CanvasObject, parent fyne.Window) {
|
||||
NewCustom(title, dismiss, content, parent).Show()
|
||||
}
|
||||
|
||||
// ShowCustomConfirm shows a dialog over the specified application using custom
|
||||
// content. The cancel button will have the dismiss text set and the "OK" will use
|
||||
// the confirm text. The response callback is called on user action.
|
||||
// The MinSize() of the CanvasObject passed will be used to set the size of the window.
|
||||
func ShowCustomConfirm(title, confirm, dismiss string, content fyne.CanvasObject,
|
||||
callback func(bool), parent fyne.Window) {
|
||||
NewCustomConfirm(title, confirm, dismiss, content, callback, parent).Show()
|
||||
}
|
||||
|
||||
func (d *dialog) Hide() {
|
||||
d.hideWithResponse(false)
|
||||
}
|
||||
|
||||
// MinSize returns the size that this dialog should not shrink below
|
||||
//
|
||||
// Since: 2.1
|
||||
func (d *dialog) MinSize() fyne.Size {
|
||||
return d.win.MinSize()
|
||||
}
|
||||
|
||||
func (d *dialog) Show() {
|
||||
if !d.desiredSize.IsZero() {
|
||||
d.win.Resize(d.desiredSize)
|
||||
}
|
||||
d.win.Show()
|
||||
}
|
||||
|
||||
func (d *dialog) Refresh() {
|
||||
d.win.Refresh()
|
||||
}
|
||||
|
||||
// Resize dialog, call this function after dialog show
|
||||
func (d *dialog) Resize(size fyne.Size) {
|
||||
d.desiredSize = size
|
||||
d.win.Resize(size)
|
||||
}
|
||||
|
||||
// SetDismissText allows custom text to be set in the dismiss button
|
||||
func (d *dialog) SetDismissText(label string) {
|
||||
d.dismiss.SetText(label)
|
||||
d.win.Refresh()
|
||||
}
|
||||
|
||||
// SetOnClosed allows to set a callback function that is called when
|
||||
// the dialog is closed
|
||||
func (d *dialog) SetOnClosed(closed func()) {
|
||||
// if there is already a callback set, remember it and call both
|
||||
originalCallback := d.callback
|
||||
|
||||
d.callback = func(response bool) {
|
||||
closed()
|
||||
if originalCallback != nil {
|
||||
originalCallback(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dialog) hideWithResponse(resp bool) {
|
||||
d.win.Hide()
|
||||
if d.callback != nil {
|
||||
d.callback(resp)
|
||||
}
|
||||
}
|
||||
|
||||
func (d *dialog) setButtons(buttons fyne.CanvasObject) {
|
||||
d.bg = newThemedBackground()
|
||||
d.label = widget.NewLabelWithStyle(d.title, fyne.TextAlignLeading, fyne.TextStyle{Bold: true})
|
||||
|
||||
var content fyne.CanvasObject
|
||||
if d.icon == nil {
|
||||
content = container.New(d.layout,
|
||||
&canvas.Image{},
|
||||
d.bg,
|
||||
d.content,
|
||||
buttons,
|
||||
d.label,
|
||||
)
|
||||
} else {
|
||||
bgIcon := canvas.NewImageFromResource(d.icon)
|
||||
content = container.New(d.layout,
|
||||
bgIcon,
|
||||
d.bg,
|
||||
d.content,
|
||||
buttons,
|
||||
d.label,
|
||||
)
|
||||
}
|
||||
|
||||
d.win = widget.NewModalPopUp(content, d.parent.Canvas())
|
||||
d.Refresh()
|
||||
}
|
||||
|
||||
func newDialog(title, message string, icon fyne.Resource, callback func(bool), parent fyne.Window) *dialog {
|
||||
d := &dialog{content: newLabel(message), title: title, icon: icon, parent: parent}
|
||||
d.layout = &dialogLayout{d: d}
|
||||
|
||||
d.callback = callback
|
||||
|
||||
return d
|
||||
}
|
||||
|
||||
func newLabel(message string) fyne.CanvasObject {
|
||||
return widget.NewLabelWithStyle(message, fyne.TextAlignCenter, fyne.TextStyle{})
|
||||
}
|
||||
|
||||
func newButtonList(buttons ...*widget.Button) fyne.CanvasObject {
|
||||
list := container.New(layout.NewGridLayout(len(buttons)))
|
||||
for _, button := range buttons {
|
||||
list.Add(button)
|
||||
}
|
||||
|
||||
return list
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// ThemedBackground
|
||||
// ===============================================================
|
||||
|
||||
type themedBackground struct {
|
||||
widget.BaseWidget
|
||||
}
|
||||
|
||||
func newThemedBackground() *themedBackground {
|
||||
t := &themedBackground{}
|
||||
t.ExtendBaseWidget(t)
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *themedBackground) CreateRenderer() fyne.WidgetRenderer {
|
||||
t.ExtendBaseWidget(t)
|
||||
rect := canvas.NewRectangle(theme.BackgroundColor())
|
||||
return &themedBackgroundRenderer{rect, []fyne.CanvasObject{rect}}
|
||||
}
|
||||
|
||||
type themedBackgroundRenderer struct {
|
||||
rect *canvas.Rectangle
|
||||
objects []fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (renderer *themedBackgroundRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (renderer *themedBackgroundRenderer) Layout(size fyne.Size) {
|
||||
renderer.rect.Resize(size)
|
||||
}
|
||||
|
||||
func (renderer *themedBackgroundRenderer) MinSize() fyne.Size {
|
||||
return renderer.rect.MinSize()
|
||||
}
|
||||
|
||||
func (renderer *themedBackgroundRenderer) Objects() []fyne.CanvasObject {
|
||||
return renderer.objects
|
||||
}
|
||||
|
||||
func (renderer *themedBackgroundRenderer) Refresh() {
|
||||
r, g, b, _ := col.ToNRGBA(theme.BackgroundColor())
|
||||
bg := &color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: 230}
|
||||
renderer.rect.FillColor = bg
|
||||
}
|
||||
|
||||
// ===============================================================
|
||||
// DialogLayout
|
||||
// ===============================================================
|
||||
|
||||
type dialogLayout struct {
|
||||
d *dialog
|
||||
}
|
||||
|
||||
func (l *dialogLayout) Layout(obj []fyne.CanvasObject, size fyne.Size) {
|
||||
l.d.bg.Move(fyne.NewPos(0, 0))
|
||||
l.d.bg.Resize(size)
|
||||
|
||||
btnMin := obj[3].MinSize()
|
||||
|
||||
// icon
|
||||
iconHeight := padHeight*2 + l.d.label.MinSize().Height*2 - theme.Padding()
|
||||
obj[0].Resize(fyne.NewSize(iconHeight, iconHeight))
|
||||
obj[0].Move(fyne.NewPos(size.Width-iconHeight+theme.Padding(), -theme.Padding()))
|
||||
|
||||
// buttons
|
||||
obj[3].Resize(btnMin)
|
||||
obj[3].Move(fyne.NewPos(size.Width/2-(btnMin.Width/2), size.Height-padHeight-btnMin.Height))
|
||||
|
||||
// content
|
||||
contentStart := l.d.label.Position().Y + l.d.label.MinSize().Height + padHeight
|
||||
contentEnd := obj[3].Position().Y - theme.Padding()
|
||||
obj[2].Move(fyne.NewPos(padWidth/2, l.d.label.MinSize().Height+padHeight))
|
||||
obj[2].Resize(fyne.NewSize(size.Width-padWidth, contentEnd-contentStart))
|
||||
}
|
||||
|
||||
func (l *dialogLayout) MinSize(obj []fyne.CanvasObject) fyne.Size {
|
||||
contentMin := obj[2].MinSize()
|
||||
btnMin := obj[3].MinSize()
|
||||
|
||||
width := fyne.Max(fyne.Max(contentMin.Width, btnMin.Width), obj[4].MinSize().Width) + padWidth
|
||||
height := contentMin.Height + btnMin.Height + l.d.label.MinSize().Height + theme.Padding() + padHeight*2
|
||||
|
||||
return fyne.NewSize(width, height)
|
||||
}
|
328
vendor/fyne.io/fyne/v2/dialog/color.go
generated
vendored
328
vendor/fyne.io/fyne/v2/dialog/color.go
generated
vendored
@@ -1,328 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
col "fyne.io/fyne/v2/internal/color"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// ColorPickerDialog is a simple dialog window that displays a color picker.
|
||||
//
|
||||
// Since: 1.4
|
||||
type ColorPickerDialog struct {
|
||||
*dialog
|
||||
Advanced bool
|
||||
color color.Color
|
||||
callback func(c color.Color)
|
||||
advanced *widget.Accordion
|
||||
picker *colorAdvancedPicker
|
||||
}
|
||||
|
||||
// NewColorPicker creates a color dialog and returns the handle.
|
||||
// Using the returned type you should call Show() and then set its color through SetColor().
|
||||
// The callback is triggered when the user selects a color.
|
||||
//
|
||||
// Since: 1.4
|
||||
func NewColorPicker(title, message string, callback func(c color.Color), parent fyne.Window) *ColorPickerDialog {
|
||||
p := &ColorPickerDialog{
|
||||
dialog: newDialog(title, message, theme.ColorPaletteIcon(), nil /*cancel?*/, parent),
|
||||
color: theme.PrimaryColor(),
|
||||
callback: callback,
|
||||
}
|
||||
p.dialog.layout = &dialogLayout{d: p.dialog}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
// ShowColorPicker creates and shows a color dialog.
|
||||
// The callback is triggered when the user selects a color.
|
||||
//
|
||||
// Since: 1.4
|
||||
func ShowColorPicker(title, message string, callback func(c color.Color), parent fyne.Window) {
|
||||
NewColorPicker(title, message, callback, parent).Show()
|
||||
}
|
||||
|
||||
// Refresh causes this dialog to be updated
|
||||
func (p *ColorPickerDialog) Refresh() {
|
||||
p.updateUI()
|
||||
}
|
||||
|
||||
// SetColor updates the color of the color picker.
|
||||
func (p *ColorPickerDialog) SetColor(c color.Color) {
|
||||
p.picker.SetColor(c)
|
||||
}
|
||||
|
||||
// Show causes this dialog to be displayed
|
||||
func (p *ColorPickerDialog) Show() {
|
||||
if p.win == nil || p.Advanced != (p.advanced != nil) {
|
||||
p.updateUI()
|
||||
}
|
||||
p.dialog.Show()
|
||||
}
|
||||
|
||||
func (p *ColorPickerDialog) createSimplePickers() (contents []fyne.CanvasObject) {
|
||||
contents = append(contents, newColorBasicPicker(p.selectColor), newColorGreyscalePicker(p.selectColor))
|
||||
if recent := newColorRecentPicker(p.selectColor); len(recent.(*fyne.Container).Objects) > 0 {
|
||||
// Add divider and recents if there are any
|
||||
contents = append(contents, canvas.NewLine(theme.ShadowColor()), recent)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p *ColorPickerDialog) selectColor(c color.Color) {
|
||||
p.dialog.Hide()
|
||||
writeRecentColor(colorToString(p.color))
|
||||
if f := p.callback; f != nil {
|
||||
f(c)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ColorPickerDialog) updateUI() {
|
||||
if w := p.win; w != nil {
|
||||
w.Hide()
|
||||
}
|
||||
p.dialog.dismiss = &widget.Button{Text: "Cancel", Icon: theme.CancelIcon(),
|
||||
OnTapped: p.dialog.Hide,
|
||||
}
|
||||
if p.Advanced {
|
||||
p.picker = newColorAdvancedPicker(p.color, func(c color.Color) {
|
||||
p.color = c
|
||||
})
|
||||
p.advanced = widget.NewAccordion(widget.NewAccordionItem("Advanced", p.picker))
|
||||
|
||||
p.dialog.content = container.NewVBox(
|
||||
container.NewCenter(
|
||||
container.NewVBox(
|
||||
p.createSimplePickers()...,
|
||||
),
|
||||
),
|
||||
widget.NewSeparator(),
|
||||
p.advanced,
|
||||
)
|
||||
|
||||
confirm := &widget.Button{Text: "Confirm", Icon: theme.ConfirmIcon(), Importance: widget.HighImportance,
|
||||
OnTapped: func() {
|
||||
p.selectColor(p.color)
|
||||
},
|
||||
}
|
||||
p.dialog.setButtons(newButtonList(p.dialog.dismiss, confirm))
|
||||
} else {
|
||||
p.dialog.content = container.NewVBox(p.createSimplePickers()...)
|
||||
p.dialog.setButtons(newButtonList(p.dialog.dismiss))
|
||||
}
|
||||
}
|
||||
|
||||
func clamp(value, min, max int) int {
|
||||
if value < min {
|
||||
value = min
|
||||
}
|
||||
if value > max {
|
||||
value = max
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func wrapHue(hue int) int {
|
||||
for hue < 0 {
|
||||
hue += 360
|
||||
}
|
||||
for hue > 360 {
|
||||
hue -= 360
|
||||
}
|
||||
return hue
|
||||
}
|
||||
|
||||
func newColorButtonBox(colors []color.Color, icon fyne.Resource, callback func(color.Color)) fyne.CanvasObject {
|
||||
var objects []fyne.CanvasObject
|
||||
if icon != nil && len(colors) > 0 {
|
||||
objects = append(objects, widget.NewIcon(icon))
|
||||
}
|
||||
for _, c := range colors {
|
||||
objects = append(objects, newColorButton(c, callback))
|
||||
}
|
||||
return container.NewGridWithColumns(8, objects...)
|
||||
}
|
||||
|
||||
func newCheckeredBackground() *canvas.Raster {
|
||||
return canvas.NewRasterWithPixels(func(x, y, _, _ int) color.Color {
|
||||
const boxSize = 10
|
||||
|
||||
if (x/boxSize)%2 == (y/boxSize)%2 {
|
||||
return color.Gray{Y: 58}
|
||||
}
|
||||
|
||||
return color.Gray{Y: 84}
|
||||
})
|
||||
}
|
||||
|
||||
const (
|
||||
preferenceRecents = "color_recents"
|
||||
preferenceMaxRecents = 8
|
||||
)
|
||||
|
||||
func readRecentColors() (recents []string) {
|
||||
for _, r := range strings.Split(fyne.CurrentApp().Preferences().String(preferenceRecents), ",") {
|
||||
if r != "" {
|
||||
recents = append(recents, r)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func writeRecentColor(color string) {
|
||||
recents := []string{color}
|
||||
for _, r := range readRecentColors() {
|
||||
if r == color {
|
||||
continue // Color already in recents
|
||||
}
|
||||
recents = append(recents, r)
|
||||
}
|
||||
if len(recents) > preferenceMaxRecents {
|
||||
recents = recents[:preferenceMaxRecents]
|
||||
}
|
||||
fyne.CurrentApp().Preferences().SetString(preferenceRecents, strings.Join(recents, ","))
|
||||
}
|
||||
|
||||
func colorToString(c color.Color) string {
|
||||
red, green, blue, alpha := col.ToNRGBA(c)
|
||||
if alpha == 0xff {
|
||||
return fmt.Sprintf("#%02x%02x%02x", red, green, blue)
|
||||
}
|
||||
return fmt.Sprintf("#%02x%02x%02x%02x", red, green, blue, alpha)
|
||||
}
|
||||
|
||||
func stringToColor(s string) (color.Color, error) {
|
||||
var c color.NRGBA
|
||||
var err error
|
||||
if len(s) == 7 {
|
||||
c.A = 0xFF
|
||||
_, err = fmt.Sscanf(s, "#%02x%02x%02x", &c.R, &c.G, &c.B)
|
||||
} else {
|
||||
_, err = fmt.Sscanf(s, "#%02x%02x%02x%02x", &c.R, &c.G, &c.B, &c.A)
|
||||
}
|
||||
return c, err
|
||||
}
|
||||
|
||||
func stringsToColors(ss ...string) (colors []color.Color) {
|
||||
for _, s := range ss {
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
c, err := stringToColor(s)
|
||||
if err != nil {
|
||||
fyne.LogError("Couldn't parse color:", err)
|
||||
} else {
|
||||
colors = append(colors, c)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func colorToHSLA(c color.Color) (int, int, int, int) {
|
||||
r, g, b, a := col.ToNRGBA(c)
|
||||
h, s, l := rgbToHsl(r, g, b)
|
||||
return h, s, l, a
|
||||
}
|
||||
|
||||
// https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||
|
||||
func rgbToHsl(r, g, b int) (int, int, int) {
|
||||
red := float64(r) / 255.0
|
||||
green := float64(g) / 255.0
|
||||
blue := float64(b) / 255.0
|
||||
|
||||
min := math.Min(red, math.Min(green, blue))
|
||||
max := math.Max(red, math.Max(green, blue))
|
||||
|
||||
lightness := (max + min) / 2.0
|
||||
|
||||
delta := max - min
|
||||
|
||||
if delta == 0.0 {
|
||||
// Achromatic
|
||||
return 0, 0, int(lightness * 100.0)
|
||||
}
|
||||
|
||||
// Chromatic
|
||||
|
||||
var saturation float64
|
||||
|
||||
if lightness < 0.5 {
|
||||
saturation = (max - min) / (max + min)
|
||||
} else {
|
||||
saturation = (max - min) / (2.0 - max - min)
|
||||
}
|
||||
|
||||
var hue float64
|
||||
|
||||
if red == max {
|
||||
hue = (green - blue) / delta
|
||||
} else if green == max {
|
||||
hue = 2.0 + (blue-red)/delta
|
||||
} else if blue == max {
|
||||
hue = 4.0 + (red-green)/delta
|
||||
}
|
||||
|
||||
h := wrapHue(int(hue * 60.0))
|
||||
s := int(saturation * 100.0)
|
||||
l := int(lightness * 100.0)
|
||||
return h, s, l
|
||||
}
|
||||
|
||||
func hslToRgb(h, s, l int) (int, int, int) {
|
||||
hue := float64(h) / 360.0
|
||||
saturation := float64(s) / 100.0
|
||||
lightness := float64(l) / 100.0
|
||||
|
||||
if saturation == 0.0 {
|
||||
// Greyscale
|
||||
g := int(lightness * 255.0)
|
||||
return g, g, g
|
||||
}
|
||||
|
||||
var v1 float64
|
||||
if lightness < 0.5 {
|
||||
v1 = lightness * (1.0 + saturation)
|
||||
} else {
|
||||
v1 = (lightness + saturation) - (lightness * saturation)
|
||||
}
|
||||
|
||||
v2 := 2.0*lightness - v1
|
||||
|
||||
red := hueToChannel(hue+(1.0/3.0), v1, v2)
|
||||
green := hueToChannel(hue, v1, v2)
|
||||
blue := hueToChannel(hue-(1.0/3.0), v1, v2)
|
||||
|
||||
r := int(math.Round(255.0 * red))
|
||||
g := int(math.Round(255.0 * green))
|
||||
b := int(math.Round(255.0 * blue))
|
||||
|
||||
return r, g, b
|
||||
}
|
||||
|
||||
func hueToChannel(h, v1, v2 float64) float64 {
|
||||
for h < 0.0 {
|
||||
h += 1.0
|
||||
}
|
||||
for h > 1.0 {
|
||||
h -= 1.0
|
||||
}
|
||||
if 6.0*h < 1.0 {
|
||||
return v2 + (v1-v2)*6*h
|
||||
}
|
||||
if 2.0*h < 1.0 {
|
||||
return v1
|
||||
}
|
||||
if 3.0*h < 2.0 {
|
||||
return v2 + (v1-v2)*6*((2.0/3.0)-h)
|
||||
}
|
||||
return v2
|
||||
}
|
113
vendor/fyne.io/fyne/v2/dialog/color_button.go
generated
vendored
113
vendor/fyne.io/fyne/v2/dialog/color_button.go
generated
vendored
@@ -1,113 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
internalwidget "fyne.io/fyne/v2/internal/widget"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
var _ fyne.Widget = (*colorButton)(nil)
|
||||
var _ desktop.Hoverable = (*colorButton)(nil)
|
||||
|
||||
// colorButton displays a color and triggers the callback when tapped.
|
||||
type colorButton struct {
|
||||
widget.BaseWidget
|
||||
color color.Color
|
||||
onTap func(color.Color)
|
||||
hovered bool
|
||||
}
|
||||
|
||||
// newColorButton creates a colorButton with the given color and callback.
|
||||
func newColorButton(color color.Color, onTap func(color.Color)) *colorButton {
|
||||
b := &colorButton{
|
||||
color: color,
|
||||
onTap: onTap,
|
||||
}
|
||||
b.ExtendBaseWidget(b)
|
||||
return b
|
||||
}
|
||||
|
||||
// CreateRenderer is a private method to Fyne which links this widget to its renderer
|
||||
func (b *colorButton) CreateRenderer() fyne.WidgetRenderer {
|
||||
b.ExtendBaseWidget(b)
|
||||
background := newCheckeredBackground()
|
||||
rectangle := &canvas.Rectangle{
|
||||
FillColor: b.color,
|
||||
}
|
||||
return &colorButtonRenderer{
|
||||
BaseRenderer: internalwidget.NewBaseRenderer([]fyne.CanvasObject{background, rectangle}),
|
||||
button: b,
|
||||
background: background,
|
||||
rectangle: rectangle,
|
||||
}
|
||||
}
|
||||
|
||||
// MouseIn is called when a desktop pointer enters the widget
|
||||
func (b *colorButton) MouseIn(*desktop.MouseEvent) {
|
||||
b.hovered = true
|
||||
b.Refresh()
|
||||
}
|
||||
|
||||
// MouseOut is called when a desktop pointer exits the widget
|
||||
func (b *colorButton) MouseOut() {
|
||||
b.hovered = false
|
||||
b.Refresh()
|
||||
}
|
||||
|
||||
// MouseMoved is called when a desktop pointer hovers over the widget
|
||||
func (b *colorButton) MouseMoved(*desktop.MouseEvent) {
|
||||
}
|
||||
|
||||
// MinSize returns the size that this widget should not shrink below
|
||||
func (b *colorButton) MinSize() fyne.Size {
|
||||
return b.BaseWidget.MinSize()
|
||||
}
|
||||
|
||||
// SetColor updates the color selected in this color widget
|
||||
func (b *colorButton) SetColor(color color.Color) {
|
||||
if b.color == color {
|
||||
return
|
||||
}
|
||||
b.color = color
|
||||
b.Refresh()
|
||||
}
|
||||
|
||||
// Tapped is called when a pointer tapped event is captured and triggers any change handler
|
||||
func (b *colorButton) Tapped(*fyne.PointEvent) {
|
||||
writeRecentColor(colorToString(b.color))
|
||||
if f := b.onTap; f != nil {
|
||||
f(b.color)
|
||||
}
|
||||
}
|
||||
|
||||
type colorButtonRenderer struct {
|
||||
internalwidget.BaseRenderer
|
||||
button *colorButton
|
||||
background *canvas.Raster
|
||||
rectangle *canvas.Rectangle
|
||||
}
|
||||
|
||||
func (r *colorButtonRenderer) Layout(size fyne.Size) {
|
||||
r.rectangle.Move(fyne.NewPos(0, 0))
|
||||
r.rectangle.Resize(size)
|
||||
}
|
||||
|
||||
func (r *colorButtonRenderer) MinSize() fyne.Size {
|
||||
return r.rectangle.MinSize().Max(fyne.NewSize(32, 32))
|
||||
}
|
||||
|
||||
func (r *colorButtonRenderer) Refresh() {
|
||||
if r.button.hovered {
|
||||
r.rectangle.StrokeColor = theme.HoverColor()
|
||||
r.rectangle.StrokeWidth = float32(theme.Padding())
|
||||
} else {
|
||||
r.rectangle.StrokeWidth = 0
|
||||
}
|
||||
r.rectangle.FillColor = r.button.color
|
||||
canvas.Refresh(r.button)
|
||||
}
|
185
vendor/fyne.io/fyne/v2/dialog/color_channel.go
generated
vendored
185
vendor/fyne.io/fyne/v2/dialog/color_channel.go
generated
vendored
@@ -1,185 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
internalwidget "fyne.io/fyne/v2/internal/widget"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
var _ fyne.Widget = (*colorChannel)(nil)
|
||||
|
||||
// colorChannel controls a channel of a color and triggers the callback when changed.
|
||||
type colorChannel struct {
|
||||
widget.BaseWidget
|
||||
name string
|
||||
min, max int
|
||||
value int
|
||||
onChanged func(int)
|
||||
}
|
||||
|
||||
// newColorChannel returns a new color channel control for the channel with the given name.
|
||||
func newColorChannel(name string, min, max, value int, onChanged func(int)) *colorChannel {
|
||||
c := &colorChannel{
|
||||
name: name,
|
||||
min: min,
|
||||
max: max,
|
||||
value: clamp(value, min, max),
|
||||
onChanged: onChanged,
|
||||
}
|
||||
c.ExtendBaseWidget(c)
|
||||
return c
|
||||
}
|
||||
|
||||
// CreateRenderer is a private method to Fyne which links this widget to its renderer
|
||||
func (c *colorChannel) CreateRenderer() fyne.WidgetRenderer {
|
||||
label := widget.NewLabelWithStyle(c.name, fyne.TextAlignTrailing, fyne.TextStyle{Bold: true})
|
||||
entry := newColorChannelEntry(c)
|
||||
slider := &widget.Slider{
|
||||
Value: 0.0,
|
||||
Min: float64(c.min),
|
||||
Max: float64(c.max),
|
||||
Step: 1.0,
|
||||
Orientation: widget.Horizontal,
|
||||
OnChanged: func(value float64) {
|
||||
c.SetValue(int(value))
|
||||
},
|
||||
}
|
||||
r := &colorChannelRenderer{
|
||||
BaseRenderer: internalwidget.NewBaseRenderer([]fyne.CanvasObject{
|
||||
label,
|
||||
slider,
|
||||
entry,
|
||||
}),
|
||||
control: c,
|
||||
label: label,
|
||||
entry: entry,
|
||||
slider: slider,
|
||||
}
|
||||
r.updateObjects()
|
||||
return r
|
||||
}
|
||||
|
||||
// MinSize returns the size that this widget should not shrink below
|
||||
func (c *colorChannel) MinSize() fyne.Size {
|
||||
c.ExtendBaseWidget(c)
|
||||
return c.BaseWidget.MinSize()
|
||||
}
|
||||
|
||||
// SetValue updates the value in this color widget
|
||||
func (c *colorChannel) SetValue(value int) {
|
||||
value = clamp(value, c.min, c.max)
|
||||
if c.value == value {
|
||||
return
|
||||
}
|
||||
c.value = value
|
||||
c.Refresh()
|
||||
if f := c.onChanged; f != nil {
|
||||
f(value)
|
||||
}
|
||||
}
|
||||
|
||||
type colorChannelRenderer struct {
|
||||
internalwidget.BaseRenderer
|
||||
control *colorChannel
|
||||
label *widget.Label
|
||||
entry *colorChannelEntry
|
||||
slider *widget.Slider
|
||||
}
|
||||
|
||||
func (r *colorChannelRenderer) Layout(size fyne.Size) {
|
||||
lMin := r.label.MinSize()
|
||||
eMin := r.entry.MinSize()
|
||||
r.label.Move(fyne.NewPos(0, (size.Height-lMin.Height)/2))
|
||||
r.label.Resize(fyne.NewSize(lMin.Width, lMin.Height))
|
||||
r.slider.Move(fyne.NewPos(lMin.Width, 0))
|
||||
r.slider.Resize(fyne.NewSize(size.Width-lMin.Width-eMin.Width, size.Height))
|
||||
r.entry.Move(fyne.NewPos(size.Width-eMin.Width, 0))
|
||||
r.entry.Resize(fyne.NewSize(eMin.Width, size.Height))
|
||||
}
|
||||
|
||||
func (r *colorChannelRenderer) MinSize() fyne.Size {
|
||||
lMin := r.label.MinSize()
|
||||
sMin := r.slider.MinSize()
|
||||
eMin := r.entry.MinSize()
|
||||
return fyne.NewSize(
|
||||
lMin.Width+sMin.Width+eMin.Width,
|
||||
fyne.Max(lMin.Height, fyne.Max(sMin.Height, eMin.Height)),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *colorChannelRenderer) Refresh() {
|
||||
r.updateObjects()
|
||||
r.Layout(r.control.Size())
|
||||
canvas.Refresh(r.control)
|
||||
}
|
||||
|
||||
func (r *colorChannelRenderer) updateObjects() {
|
||||
r.entry.SetText(strconv.Itoa(r.control.value))
|
||||
r.slider.Value = float64(r.control.value)
|
||||
r.slider.Refresh()
|
||||
}
|
||||
|
||||
type colorChannelEntry struct {
|
||||
userChangeEntry
|
||||
}
|
||||
|
||||
func newColorChannelEntry(c *colorChannel) *colorChannelEntry {
|
||||
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
|
||||
}
|
||||
|
||||
func (e *colorChannelEntry) MinSize() fyne.Size {
|
||||
// Ensure space for 3 digits
|
||||
min := fyne.MeasureText("000", theme.TextSize(), fyne.TextStyle{})
|
||||
min = min.Add(fyne.NewSize(theme.Padding()*6, theme.Padding()*4))
|
||||
return min.Max(e.Entry.MinSize())
|
||||
}
|
||||
|
||||
type userChangeEntry struct {
|
||||
widget.Entry
|
||||
userTyped uint32 // atomic, 0 == false, 1 == true
|
||||
}
|
||||
|
||||
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) {
|
||||
if !atomic.CompareAndSwapUint32(&e.userTyped, 1, 0) {
|
||||
return
|
||||
}
|
||||
if onChanged != nil {
|
||||
onChanged(text)
|
||||
}
|
||||
}
|
||||
e.ExtendBaseWidget(e)
|
||||
}
|
||||
|
||||
func (e *userChangeEntry) TypedRune(r rune) {
|
||||
atomic.StoreUint32(&e.userTyped, 1)
|
||||
e.Entry.TypedRune(r)
|
||||
}
|
||||
|
||||
func (e *userChangeEntry) TypedKey(ev *fyne.KeyEvent) {
|
||||
atomic.StoreUint32(&e.userTyped, 1)
|
||||
e.Entry.TypedKey(ev)
|
||||
}
|
299
vendor/fyne.io/fyne/v2/dialog/color_picker.go
generated
vendored
299
vendor/fyne.io/fyne/v2/dialog/color_picker.go
generated
vendored
@@ -1,299 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
col "fyne.io/fyne/v2/internal/color"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// newColorBasicPicker returns a component for selecting basic colors.
|
||||
func newColorBasicPicker(callback func(color.Color)) fyne.CanvasObject {
|
||||
return newColorButtonBox([]color.Color{
|
||||
theme.PrimaryColorNamed(theme.ColorRed),
|
||||
theme.PrimaryColorNamed(theme.ColorOrange),
|
||||
theme.PrimaryColorNamed(theme.ColorYellow),
|
||||
theme.PrimaryColorNamed(theme.ColorGreen),
|
||||
theme.PrimaryColorNamed(theme.ColorBlue),
|
||||
theme.PrimaryColorNamed(theme.ColorPurple),
|
||||
theme.PrimaryColorNamed(theme.ColorBrown),
|
||||
}, theme.ColorChromaticIcon(), callback)
|
||||
}
|
||||
|
||||
// newColorGreyscalePicker returns a component for selecting greyscale colors.
|
||||
func newColorGreyscalePicker(callback func(color.Color)) fyne.CanvasObject {
|
||||
return newColorButtonBox(stringsToColors([]string{
|
||||
"#ffffff",
|
||||
"#cccccc",
|
||||
"#aaaaaa",
|
||||
"#888888",
|
||||
"#555555",
|
||||
"#333333",
|
||||
"#000000",
|
||||
}...), theme.ColorAchromaticIcon(), callback)
|
||||
}
|
||||
|
||||
// newColorRecentPicker returns a component for selecting recent colors.
|
||||
func newColorRecentPicker(callback func(color.Color)) fyne.CanvasObject {
|
||||
return newColorButtonBox(stringsToColors(readRecentColors()...), theme.HistoryIcon(), callback)
|
||||
}
|
||||
|
||||
var _ fyne.Widget = (*colorAdvancedPicker)(nil)
|
||||
|
||||
// colorAdvancedPicker widget is a component for selecting a color.
|
||||
type colorAdvancedPicker struct {
|
||||
widget.BaseWidget
|
||||
Red, Green, Blue, Alpha int // Range 0-255
|
||||
Hue int // Range 0-360 (degrees)
|
||||
Saturation, Lightness int // Range 0-100 (percent)
|
||||
ColorModel string
|
||||
|
||||
onChange func(color.Color)
|
||||
}
|
||||
|
||||
// newColorAdvancedPicker returns a new color widget set to the given color.
|
||||
func newColorAdvancedPicker(color color.Color, onChange func(color.Color)) *colorAdvancedPicker {
|
||||
c := &colorAdvancedPicker{
|
||||
onChange: onChange,
|
||||
}
|
||||
c.ExtendBaseWidget(c)
|
||||
c.updateColor(color)
|
||||
return c
|
||||
}
|
||||
|
||||
// Color returns the currently selected color.
|
||||
func (p *colorAdvancedPicker) Color() color.Color {
|
||||
return &color.NRGBA{
|
||||
uint8(p.Red),
|
||||
uint8(p.Green),
|
||||
uint8(p.Blue),
|
||||
uint8(p.Alpha),
|
||||
}
|
||||
}
|
||||
|
||||
// SetColor updates the color selected in this color widget.
|
||||
func (p *colorAdvancedPicker) SetColor(color color.Color) {
|
||||
if p.updateColor(color) {
|
||||
p.Refresh()
|
||||
if f := p.onChange; f != nil {
|
||||
f(color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetHSLA updated the Hue, Saturation, Lightness, and Alpha components of the currently selected color.
|
||||
func (p *colorAdvancedPicker) SetHSLA(h, s, l, a int) {
|
||||
if p.updateHSLA(h, s, l, a) {
|
||||
p.Refresh()
|
||||
if f := p.onChange; f != nil {
|
||||
f(p.Color())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetRGBA updated the Red, Green, Blue, and Alpha components of the currently selected color.
|
||||
func (p *colorAdvancedPicker) SetRGBA(r, g, b, a int) {
|
||||
if p.updateRGBA(r, g, b, a) {
|
||||
p.Refresh()
|
||||
if f := p.onChange; f != nil {
|
||||
f(p.Color())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MinSize returns the size that this widget should not shrink below.
|
||||
func (p *colorAdvancedPicker) MinSize() fyne.Size {
|
||||
p.ExtendBaseWidget(p)
|
||||
return p.BaseWidget.MinSize()
|
||||
}
|
||||
|
||||
// CreateRenderer is a private method to Fyne which links this widget to its renderer.
|
||||
func (p *colorAdvancedPicker) CreateRenderer() fyne.WidgetRenderer {
|
||||
p.ExtendBaseWidget(p)
|
||||
|
||||
// Preview
|
||||
preview := &canvas.Rectangle{}
|
||||
|
||||
// HSL
|
||||
hueChannel := newColorChannel("H", 0, 360, p.Hue, func(h int) {
|
||||
p.SetHSLA(h, p.Saturation, p.Lightness, p.Alpha)
|
||||
})
|
||||
saturationChannel := newColorChannel("S", 0, 100, p.Saturation, func(s int) {
|
||||
p.SetHSLA(p.Hue, s, p.Lightness, p.Alpha)
|
||||
})
|
||||
lightnessChannel := newColorChannel("L", 0, 100, p.Lightness, func(l int) {
|
||||
p.SetHSLA(p.Hue, p.Saturation, l, p.Alpha)
|
||||
})
|
||||
hslBox := container.NewVBox(
|
||||
hueChannel,
|
||||
saturationChannel,
|
||||
lightnessChannel,
|
||||
)
|
||||
|
||||
// RGB
|
||||
redChannel := newColorChannel("R", 0, 255, p.Red, func(r int) {
|
||||
p.SetRGBA(r, p.Green, p.Blue, p.Alpha)
|
||||
})
|
||||
greenChannel := newColorChannel("G", 0, 255, p.Green, func(g int) {
|
||||
p.SetRGBA(p.Red, g, p.Blue, p.Alpha)
|
||||
})
|
||||
blueChannel := newColorChannel("B", 0, 255, p.Blue, func(b int) {
|
||||
p.SetRGBA(p.Red, p.Green, b, p.Alpha)
|
||||
})
|
||||
rgbBox := container.NewVBox(
|
||||
redChannel,
|
||||
greenChannel,
|
||||
blueChannel,
|
||||
)
|
||||
|
||||
// Wheel
|
||||
wheel := newColorWheel(func(hue, saturation, lightness, alpha int) {
|
||||
p.SetHSLA(hue, saturation, lightness, alpha)
|
||||
})
|
||||
|
||||
// Alpha
|
||||
alphaChannel := newColorChannel("A", 0, 255, p.Alpha, func(a int) {
|
||||
p.SetRGBA(p.Red, p.Green, p.Blue, a)
|
||||
})
|
||||
|
||||
// Hex
|
||||
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 := container.NewPadded(container.NewVBox(
|
||||
container.NewGridWithColumns(3,
|
||||
container.NewPadded(wheel),
|
||||
hslBox,
|
||||
rgbBox),
|
||||
container.NewGridWithColumns(3,
|
||||
container.NewPadded(
|
||||
container.NewMax(
|
||||
newCheckeredBackground(),
|
||||
preview,
|
||||
),
|
||||
),
|
||||
hex,
|
||||
alphaChannel,
|
||||
),
|
||||
))
|
||||
|
||||
r := &colorPickerRenderer{
|
||||
WidgetRenderer: widget.NewSimpleRenderer(contents),
|
||||
picker: p,
|
||||
redChannel: redChannel,
|
||||
greenChannel: greenChannel,
|
||||
blueChannel: blueChannel,
|
||||
hueChannel: hueChannel,
|
||||
saturationChannel: saturationChannel,
|
||||
lightnessChannel: lightnessChannel,
|
||||
wheel: wheel,
|
||||
preview: preview,
|
||||
alphaChannel: alphaChannel,
|
||||
hex: hex,
|
||||
contents: contents,
|
||||
}
|
||||
r.updateObjects()
|
||||
return r
|
||||
}
|
||||
|
||||
func (p *colorAdvancedPicker) updateColor(color color.Color) bool {
|
||||
r, g, b, a := col.ToNRGBA(color)
|
||||
if p.Red == r && p.Green == g && p.Blue == b && p.Alpha == a {
|
||||
return false
|
||||
}
|
||||
return p.updateRGBA(r, g, b, a)
|
||||
}
|
||||
|
||||
func (p *colorAdvancedPicker) updateHSLA(h, s, l, a int) bool {
|
||||
h = wrapHue(h)
|
||||
s = clamp(s, 0, 100)
|
||||
l = clamp(l, 0, 100)
|
||||
a = clamp(a, 0, 255)
|
||||
if p.Hue == h && p.Saturation == s && p.Lightness == l && p.Alpha == a {
|
||||
return false
|
||||
}
|
||||
p.Hue = h
|
||||
p.Saturation = s
|
||||
p.Lightness = l
|
||||
p.Alpha = a
|
||||
p.Red, p.Green, p.Blue = hslToRgb(p.Hue, p.Saturation, p.Lightness)
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *colorAdvancedPicker) updateRGBA(r, g, b, a int) bool {
|
||||
r = clamp(r, 0, 255)
|
||||
g = clamp(g, 0, 255)
|
||||
b = clamp(b, 0, 255)
|
||||
a = clamp(a, 0, 255)
|
||||
if p.Red == r && p.Green == g && p.Blue == b && p.Alpha == a {
|
||||
return false
|
||||
}
|
||||
p.Red = r
|
||||
p.Green = g
|
||||
p.Blue = b
|
||||
p.Alpha = a
|
||||
p.Hue, p.Saturation, p.Lightness = rgbToHsl(p.Red, p.Green, p.Blue)
|
||||
return true
|
||||
}
|
||||
|
||||
var _ fyne.WidgetRenderer = (*colorPickerRenderer)(nil)
|
||||
|
||||
type colorPickerRenderer struct {
|
||||
fyne.WidgetRenderer
|
||||
picker *colorAdvancedPicker
|
||||
redChannel *colorChannel
|
||||
greenChannel *colorChannel
|
||||
blueChannel *colorChannel
|
||||
hueChannel *colorChannel
|
||||
saturationChannel *colorChannel
|
||||
lightnessChannel *colorChannel
|
||||
wheel *colorWheel
|
||||
preview *canvas.Rectangle
|
||||
alphaChannel *colorChannel
|
||||
hex *userChangeEntry
|
||||
contents fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (r *colorPickerRenderer) Refresh() {
|
||||
r.updateObjects()
|
||||
r.WidgetRenderer.Refresh()
|
||||
}
|
||||
|
||||
func (r *colorPickerRenderer) updateObjects() {
|
||||
// HSL
|
||||
r.hueChannel.SetValue(r.picker.Hue)
|
||||
r.saturationChannel.SetValue(r.picker.Saturation)
|
||||
r.lightnessChannel.SetValue(r.picker.Lightness)
|
||||
|
||||
// RGB
|
||||
r.redChannel.SetValue(r.picker.Red)
|
||||
r.greenChannel.SetValue(r.picker.Green)
|
||||
r.blueChannel.SetValue(r.picker.Blue)
|
||||
|
||||
// Wheel
|
||||
r.wheel.SetHSLA(r.picker.Hue, r.picker.Saturation, r.picker.Lightness, r.picker.Alpha)
|
||||
|
||||
color := r.picker.Color()
|
||||
|
||||
// Preview
|
||||
r.preview.FillColor = color
|
||||
r.preview.Refresh()
|
||||
|
||||
// Alpha
|
||||
r.alphaChannel.SetValue(r.picker.Alpha)
|
||||
|
||||
// Hex
|
||||
r.hex.SetText(colorToString(color))
|
||||
}
|
205
vendor/fyne.io/fyne/v2/dialog/color_wheel.go
generated
vendored
205
vendor/fyne.io/fyne/v2/dialog/color_wheel.go
generated
vendored
@@ -1,205 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
"image/draw"
|
||||
"math"
|
||||
"math/cmplx"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
internalwidget "fyne.io/fyne/v2/internal/widget"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
var _ fyne.Widget = (*colorWheel)(nil)
|
||||
var _ fyne.Tappable = (*colorWheel)(nil)
|
||||
var _ fyne.Draggable = (*colorWheel)(nil)
|
||||
|
||||
// colorWheel displays a circular color gradient and triggers the callback when tapped.
|
||||
type colorWheel struct {
|
||||
widget.BaseWidget
|
||||
generator func(w, h int) image.Image
|
||||
cache draw.Image
|
||||
onChange func(int, int, int, int)
|
||||
|
||||
Hue int // Range 0-360 (degrees)
|
||||
Saturation, Lightness int // Range 0-100 (percent)
|
||||
Alpha int // Range 0-255
|
||||
}
|
||||
|
||||
// newColorWheel returns a new color area that triggers the given onChange callback when tapped.
|
||||
func newColorWheel(onChange func(int, int, int, int)) *colorWheel {
|
||||
a := &colorWheel{
|
||||
onChange: onChange,
|
||||
}
|
||||
a.generator = func(w, h int) image.Image {
|
||||
if a.cache == nil || a.cache.Bounds().Dx() != w || a.cache.Bounds().Dy() != h {
|
||||
rect := image.Rect(0, 0, w, h)
|
||||
a.cache = image.NewRGBA(rect)
|
||||
}
|
||||
for x := 0; x < w; x++ {
|
||||
for y := 0; y < h; y++ {
|
||||
if c := a.colorAt(x, y, w, h); c != nil {
|
||||
a.cache.Set(x, y, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
return a.cache
|
||||
}
|
||||
a.ExtendBaseWidget(a)
|
||||
return a
|
||||
}
|
||||
|
||||
// Cursor returns the cursor type of this widget.
|
||||
func (a *colorWheel) Cursor() desktop.Cursor {
|
||||
return desktop.CrosshairCursor
|
||||
}
|
||||
|
||||
// CreateRenderer is a private method to Fyne which links this widget to its renderer.
|
||||
func (a *colorWheel) CreateRenderer() fyne.WidgetRenderer {
|
||||
raster := &canvas.Raster{
|
||||
Generator: a.generator,
|
||||
}
|
||||
x := canvas.NewLine(color.Black)
|
||||
y := canvas.NewLine(color.Black)
|
||||
return &colorWheelRenderer{
|
||||
BaseRenderer: internalwidget.NewBaseRenderer([]fyne.CanvasObject{raster, x, y}),
|
||||
area: a,
|
||||
raster: raster,
|
||||
x: x,
|
||||
y: y,
|
||||
}
|
||||
}
|
||||
|
||||
// MinSize returns the size that this widget should not shrink below.
|
||||
func (a *colorWheel) MinSize() fyne.Size {
|
||||
a.ExtendBaseWidget(a)
|
||||
return a.BaseWidget.MinSize()
|
||||
}
|
||||
|
||||
// SetHSLA updates the selected color in the wheel.
|
||||
func (a *colorWheel) SetHSLA(hue, saturation, lightness, alpha int) {
|
||||
if a.Hue == hue && a.Saturation == saturation && a.Lightness == lightness && a.Alpha == alpha {
|
||||
return
|
||||
}
|
||||
a.Hue = hue
|
||||
a.Saturation = saturation
|
||||
a.Lightness = lightness
|
||||
a.Alpha = alpha
|
||||
a.Refresh()
|
||||
}
|
||||
|
||||
// Tapped is called when a pointer tapped event is captured and triggers any change handler.
|
||||
func (a *colorWheel) Tapped(event *fyne.PointEvent) {
|
||||
a.trigger(event.Position)
|
||||
}
|
||||
|
||||
// Dragged is called when a pointer drag event is captured and triggers any change handler
|
||||
func (a *colorWheel) Dragged(event *fyne.DragEvent) {
|
||||
a.trigger(event.Position)
|
||||
}
|
||||
|
||||
// DragEnd is called when a pointer drag ends
|
||||
func (a *colorWheel) DragEnd() {
|
||||
}
|
||||
|
||||
func (a *colorWheel) colorAt(x, y, w, h int) color.Color {
|
||||
width, height := float64(w), float64(h)
|
||||
dx := float64(x) - (width / 2.0)
|
||||
dy := float64(y) - (height / 2.0)
|
||||
radius, radians := cmplx.Polar(complex(dx, dy))
|
||||
limit := math.Min(width, height) / 2.0
|
||||
if radius > limit {
|
||||
// Out of bounds
|
||||
return theme.BackgroundColor()
|
||||
}
|
||||
degrees := radians * (180.0 / math.Pi)
|
||||
hue := wrapHue(int(degrees))
|
||||
saturation := int(radius / limit * 100.0)
|
||||
red, green, blue := hslToRgb(hue, saturation, a.Lightness)
|
||||
return &color.NRGBA{
|
||||
R: uint8(red),
|
||||
G: uint8(green),
|
||||
B: uint8(blue),
|
||||
A: uint8(a.Alpha),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *colorWheel) locationForPosition(pos fyne.Position) (x, y int) {
|
||||
can := fyne.CurrentApp().Driver().CanvasForObject(a)
|
||||
x, y = int(pos.X), int(pos.Y)
|
||||
if can != nil {
|
||||
x, y = can.PixelCoordinateForPosition(pos)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (a *colorWheel) selection(width, height float32) (float32, float32) {
|
||||
w, h := float64(width), float64(height)
|
||||
radius := float64(a.Saturation) / 100.0 * math.Min(w, h) / 2.0
|
||||
degrees := float64(a.Hue)
|
||||
radians := degrees * math.Pi / 180.0
|
||||
c := cmplx.Rect(radius, radians)
|
||||
return float32(real(c) + w/2.0), float32(imag(c) + h/2.0)
|
||||
}
|
||||
|
||||
func (a *colorWheel) trigger(pos fyne.Position) {
|
||||
x, y := a.locationForPosition(pos)
|
||||
if c, f := a.cache, a.onChange; c != nil && f != nil {
|
||||
b := c.Bounds()
|
||||
width, height := float64(b.Dx()), float64(b.Dy())
|
||||
dx := float64(x) - (width / 2)
|
||||
dy := float64(y) - (height / 2)
|
||||
radius, radians := cmplx.Polar(complex(dx, dy))
|
||||
limit := math.Min(width, height) / 2.0
|
||||
if radius > limit {
|
||||
// Out of bounds
|
||||
return
|
||||
}
|
||||
degrees := radians * (180.0 / math.Pi)
|
||||
a.Hue = wrapHue(int(degrees))
|
||||
a.Saturation = int(radius / limit * 100.0)
|
||||
f(a.Hue, a.Saturation, a.Lightness, a.Alpha)
|
||||
}
|
||||
a.Refresh()
|
||||
}
|
||||
|
||||
type colorWheelRenderer struct {
|
||||
internalwidget.BaseRenderer
|
||||
area *colorWheel
|
||||
raster *canvas.Raster
|
||||
x, y *canvas.Line
|
||||
}
|
||||
|
||||
func (r *colorWheelRenderer) Layout(size fyne.Size) {
|
||||
x, y := r.area.selection(size.Width, size.Height)
|
||||
r.x.Position1 = fyne.NewPos(0, y)
|
||||
r.x.Position2 = fyne.NewPos(size.Width, y)
|
||||
r.y.Position1 = fyne.NewPos(x, 0)
|
||||
r.y.Position2 = fyne.NewPos(x, size.Height)
|
||||
r.raster.Move(fyne.NewPos(0, 0))
|
||||
r.raster.Resize(size)
|
||||
}
|
||||
|
||||
func (r *colorWheelRenderer) MinSize() fyne.Size {
|
||||
return r.raster.MinSize().Max(fyne.NewSize(128, 128))
|
||||
}
|
||||
|
||||
func (r *colorWheelRenderer) Refresh() {
|
||||
s := r.area.Size()
|
||||
if s.IsZero() {
|
||||
r.area.Resize(r.area.MinSize())
|
||||
} else {
|
||||
r.Layout(s)
|
||||
}
|
||||
r.x.StrokeColor = theme.ForegroundColor()
|
||||
r.x.Refresh()
|
||||
r.y.StrokeColor = theme.ForegroundColor()
|
||||
r.y.Refresh()
|
||||
r.raster.Refresh()
|
||||
canvas.Refresh(r.area)
|
||||
}
|
46
vendor/fyne.io/fyne/v2/dialog/confirm.go
generated
vendored
46
vendor/fyne.io/fyne/v2/dialog/confirm.go
generated
vendored
@@ -1,46 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// ConfirmDialog is like the standard Dialog but with an additional confirmation button
|
||||
type ConfirmDialog struct {
|
||||
*dialog
|
||||
|
||||
confirm *widget.Button
|
||||
}
|
||||
|
||||
// SetConfirmText allows custom text to be set in the confirmation button
|
||||
func (d *ConfirmDialog) SetConfirmText(label string) {
|
||||
d.confirm.SetText(label)
|
||||
d.win.Refresh()
|
||||
}
|
||||
|
||||
// NewConfirm creates a dialog over the specified window for user confirmation.
|
||||
// The title is used for the dialog window and message is the content.
|
||||
// The callback is executed when the user decides. After creation you should call Show().
|
||||
func NewConfirm(title, message string, callback func(bool), parent fyne.Window) *ConfirmDialog {
|
||||
d := newDialog(title, message, theme.QuestionIcon(), callback, parent)
|
||||
|
||||
d.dismiss = &widget.Button{Text: "No", Icon: theme.CancelIcon(),
|
||||
OnTapped: d.Hide,
|
||||
}
|
||||
confirm := &widget.Button{Text: "Yes", Icon: theme.ConfirmIcon(), Importance: widget.HighImportance,
|
||||
OnTapped: func() {
|
||||
d.hideWithResponse(true)
|
||||
},
|
||||
}
|
||||
d.setButtons(newButtonList(d.dismiss, confirm))
|
||||
|
||||
return &ConfirmDialog{d, confirm}
|
||||
}
|
||||
|
||||
// ShowConfirm shows a dialog over the specified window for a user
|
||||
// confirmation. The title is used for the dialog window and message is the content.
|
||||
// The callback is executed when the user decides.
|
||||
func ShowConfirm(title, message string, callback func(bool), parent fyne.Window) {
|
||||
NewConfirm(title, message, callback, parent).Show()
|
||||
}
|
74
vendor/fyne.io/fyne/v2/dialog/entry.go
generated
vendored
74
vendor/fyne.io/fyne/v2/dialog/entry.go
generated
vendored
@@ -1,74 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// EntryDialog is a variation of a dialog which prompts the user to enter some text.
|
||||
//
|
||||
// Deprecated: Use dialog.NewFormDialog() or dialog.ShowFormDialog() with a widget.Entry inside instead.
|
||||
type EntryDialog struct {
|
||||
*formDialog
|
||||
|
||||
entry *widget.Entry
|
||||
|
||||
onClosed func()
|
||||
}
|
||||
|
||||
// SetText changes the current text value of the entry dialog, this can
|
||||
// be useful for setting a default value.
|
||||
func (i *EntryDialog) SetText(s string) {
|
||||
i.entry.SetText(s)
|
||||
}
|
||||
|
||||
// SetPlaceholder defines the placeholder text for the entry
|
||||
func (i *EntryDialog) SetPlaceholder(s string) {
|
||||
i.entry.SetPlaceHolder(s)
|
||||
}
|
||||
|
||||
// SetOnClosed changes the callback which is run when the dialog is closed,
|
||||
// which is nil by default.
|
||||
//
|
||||
// The callback is called unconditionally whether the user confirms or cancels.
|
||||
//
|
||||
// Note that the callback will be called after onConfirm, if both are non-nil.
|
||||
// This way onConfirm can potential modify state that this callback needs to
|
||||
// get the user input when the user confirms, while also being able to handle
|
||||
// the case where the user cancelled.
|
||||
func (i *EntryDialog) SetOnClosed(callback func()) {
|
||||
i.onClosed = callback
|
||||
}
|
||||
|
||||
// NewEntryDialog creates a dialog over the specified window for the user to enter a value.
|
||||
//
|
||||
// onConfirm is a callback that runs when the user enters a string of
|
||||
// text and clicks the "confirm" button. May be nil.
|
||||
//
|
||||
// Deprecated: Use dialog.NewFormDialog() with a widget.Entry inside instead.
|
||||
func NewEntryDialog(title, message string, onConfirm func(string), parent fyne.Window) *EntryDialog {
|
||||
i := &EntryDialog{entry: widget.NewEntry()}
|
||||
items := []*widget.FormItem{widget.NewFormItem(message, i.entry)}
|
||||
i.formDialog = NewForm(title, "Ok", "Cancel", items, func(ok bool) {
|
||||
// User has confirmed and entered an input
|
||||
if ok && onConfirm != nil {
|
||||
onConfirm(i.entry.Text)
|
||||
}
|
||||
|
||||
if i.onClosed != nil {
|
||||
i.onClosed()
|
||||
}
|
||||
|
||||
i.entry.Text = ""
|
||||
i.win.Hide() // Close directly without executing the callback. This is the callback.
|
||||
}, parent).(*formDialog)
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
// ShowEntryDialog creates a new entry dialog and shows it immediately.
|
||||
//
|
||||
// Deprecated: Use dialog.ShowFormDialog() with a widget.Entry inside instead.
|
||||
func ShowEntryDialog(title, message string, onConfirm func(string), parent fyne.Window) {
|
||||
NewEntryDialog(title, message, onConfirm, parent).Show()
|
||||
}
|
766
vendor/fyne.io/fyne/v2/dialog/file.go
generated
vendored
766
vendor/fyne.io/fyne/v2/dialog/file.go
generated
vendored
@@ -1,766 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/storage/repository"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type viewLayout int
|
||||
|
||||
const (
|
||||
gridView viewLayout = iota
|
||||
listView
|
||||
)
|
||||
|
||||
type textWidget interface {
|
||||
fyne.Widget
|
||||
SetText(string)
|
||||
}
|
||||
|
||||
type favoriteItem struct {
|
||||
locName string
|
||||
locIcon fyne.Resource
|
||||
loc fyne.URI
|
||||
}
|
||||
|
||||
type fileDialog struct {
|
||||
file *FileDialog
|
||||
fileName textWidget
|
||||
dismiss *widget.Button
|
||||
open *widget.Button
|
||||
breadcrumb *fyne.Container
|
||||
breadcrumbScroll *container.Scroll
|
||||
files *fyne.Container
|
||||
filesScroll *container.Scroll
|
||||
favorites []favoriteItem
|
||||
favoritesList *widget.List
|
||||
showHidden bool
|
||||
|
||||
view viewLayout
|
||||
|
||||
win *widget.PopUp
|
||||
selected *fileDialogItem
|
||||
dir fyne.ListableURI
|
||||
// this will be the initial filename in a FileDialog in save mode
|
||||
initialFileName string
|
||||
}
|
||||
|
||||
// FileDialog is a dialog containing a file picker for use in opening or saving files.
|
||||
type FileDialog struct {
|
||||
callback interface{}
|
||||
onClosedCallback func(bool)
|
||||
parent fyne.Window
|
||||
dialog *fileDialog
|
||||
|
||||
confirmText, dismissText string
|
||||
desiredSize fyne.Size
|
||||
filter storage.FileFilter
|
||||
save bool
|
||||
// this will be applied to dialog.dir when it's loaded
|
||||
startingLocation fyne.ListableURI
|
||||
// this will be the initial filename in a FileDialog in save mode
|
||||
initialFileName string
|
||||
}
|
||||
|
||||
// Declare conformity to Dialog interface
|
||||
var _ Dialog = (*FileDialog)(nil)
|
||||
|
||||
func (f *fileDialog) makeUI() fyne.CanvasObject {
|
||||
if f.file.save {
|
||||
saveName := widget.NewEntry()
|
||||
saveName.OnChanged = func(s string) {
|
||||
if s == "" {
|
||||
f.open.Disable()
|
||||
} else {
|
||||
f.open.Enable()
|
||||
}
|
||||
}
|
||||
saveName.SetPlaceHolder("Enter filename")
|
||||
f.fileName = saveName
|
||||
} else {
|
||||
f.fileName = widget.NewLabel("")
|
||||
}
|
||||
|
||||
label := "Open"
|
||||
if f.file.save {
|
||||
label = "Save"
|
||||
}
|
||||
if f.file.confirmText != "" {
|
||||
label = f.file.confirmText
|
||||
}
|
||||
f.open = widget.NewButton(label, func() {
|
||||
if f.file.callback == nil {
|
||||
f.win.Hide()
|
||||
if f.file.onClosedCallback != nil {
|
||||
f.file.onClosedCallback(false)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if f.file.save {
|
||||
callback := f.file.callback.(func(fyne.URIWriteCloser, error))
|
||||
name := f.fileName.(*widget.Entry).Text
|
||||
location, _ := storage.Child(f.dir, name)
|
||||
|
||||
exists, _ := storage.Exists(location)
|
||||
|
||||
// check if a directory is selected
|
||||
listable, err := storage.CanList(location)
|
||||
|
||||
if !exists {
|
||||
f.win.Hide()
|
||||
if f.file.onClosedCallback != nil {
|
||||
f.file.onClosedCallback(true)
|
||||
}
|
||||
callback(storage.Writer(location))
|
||||
return
|
||||
} else if err == nil && listable {
|
||||
// a directory has been selected
|
||||
ShowInformation("Cannot overwrite",
|
||||
"Files cannot replace a directory,\ncheck the file name and try again", f.file.parent)
|
||||
return
|
||||
}
|
||||
|
||||
ShowConfirm("Overwrite?", "Are you sure you want to overwrite the file\n"+name+"?",
|
||||
func(ok bool) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
f.win.Hide()
|
||||
|
||||
callback(storage.Writer(location))
|
||||
if f.file.onClosedCallback != nil {
|
||||
f.file.onClosedCallback(true)
|
||||
}
|
||||
}, f.file.parent)
|
||||
} else if f.selected != nil {
|
||||
callback := f.file.callback.(func(fyne.URIReadCloser, error))
|
||||
f.win.Hide()
|
||||
if f.file.onClosedCallback != nil {
|
||||
f.file.onClosedCallback(true)
|
||||
}
|
||||
callback(storage.Reader(f.selected.location))
|
||||
} else if f.file.isDirectory() {
|
||||
callback := f.file.callback.(func(fyne.ListableURI, error))
|
||||
f.win.Hide()
|
||||
if f.file.onClosedCallback != nil {
|
||||
f.file.onClosedCallback(true)
|
||||
}
|
||||
callback(f.dir, nil)
|
||||
}
|
||||
})
|
||||
f.open.Importance = widget.HighImportance
|
||||
f.open.Disable()
|
||||
if f.file.save {
|
||||
f.fileName.SetText(f.initialFileName)
|
||||
}
|
||||
dismissLabel := "Cancel"
|
||||
if f.file.dismissText != "" {
|
||||
dismissLabel = f.file.dismissText
|
||||
}
|
||||
f.dismiss = widget.NewButton(dismissLabel, func() {
|
||||
f.win.Hide()
|
||||
if f.file.onClosedCallback != nil {
|
||||
f.file.onClosedCallback(false)
|
||||
}
|
||||
if f.file.callback != nil {
|
||||
if f.file.save {
|
||||
f.file.callback.(func(fyne.URIWriteCloser, error))(nil, nil)
|
||||
} else if f.file.isDirectory() {
|
||||
f.file.callback.(func(fyne.ListableURI, error))(nil, nil)
|
||||
} else {
|
||||
f.file.callback.(func(fyne.URIReadCloser, error))(nil, nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
buttons := container.NewGridWithRows(1, f.dismiss, f.open)
|
||||
|
||||
f.filesScroll = container.NewScroll(nil) // filesScroll's content will be set by setView function.
|
||||
verticalExtra := float32(float64(fileIconSize) * 0.25)
|
||||
f.filesScroll.SetMinSize(fyne.NewSize(fileIconCellWidth*2+theme.Padding(),
|
||||
(fileIconSize+fileTextSize)+theme.Padding()*2+verticalExtra))
|
||||
|
||||
f.breadcrumb = container.NewHBox()
|
||||
f.breadcrumbScroll = container.NewHScroll(container.NewPadded(f.breadcrumb))
|
||||
title := label + " File"
|
||||
if f.file.isDirectory() {
|
||||
title = label + " Folder"
|
||||
}
|
||||
|
||||
f.setView(gridView)
|
||||
f.loadFavorites()
|
||||
|
||||
f.favoritesList = widget.NewList(
|
||||
func() int {
|
||||
return len(f.favorites)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewHBox(widget.NewIcon(theme.DocumentIcon()), widget.NewLabel("Template Object"))
|
||||
},
|
||||
func(id widget.ListItemID, item fyne.CanvasObject) {
|
||||
item.(*fyne.Container).Objects[0].(*widget.Icon).SetResource(f.favorites[id].locIcon)
|
||||
item.(*fyne.Container).Objects[1].(*widget.Label).SetText(f.favorites[id].locName)
|
||||
},
|
||||
)
|
||||
f.favoritesList.OnSelected = func(id widget.ListItemID) {
|
||||
f.setLocation(f.favorites[id].loc)
|
||||
}
|
||||
|
||||
var optionsButton *widget.Button
|
||||
optionsButton = widget.NewButtonWithIcon("", theme.SettingsIcon(), func() {
|
||||
f.optionsMenu(fyne.CurrentApp().Driver().AbsolutePositionForObject(optionsButton), optionsButton.Size())
|
||||
})
|
||||
|
||||
var toggleViewButton *widget.Button
|
||||
toggleViewButton = widget.NewButtonWithIcon("", theme.ListIcon(), func() {
|
||||
if f.view == gridView {
|
||||
f.setView(listView)
|
||||
toggleViewButton.SetIcon(theme.GridIcon())
|
||||
} else {
|
||||
f.setView(gridView)
|
||||
toggleViewButton.SetIcon(theme.ListIcon())
|
||||
}
|
||||
})
|
||||
|
||||
optionsbuttons := container.NewHBox(
|
||||
toggleViewButton,
|
||||
optionsButton,
|
||||
)
|
||||
|
||||
header := container.NewBorder(nil, nil, nil, optionsbuttons,
|
||||
optionsbuttons, widget.NewLabelWithStyle(title, fyne.TextAlignLeading, fyne.TextStyle{Bold: true}),
|
||||
)
|
||||
|
||||
footer := container.NewBorder(nil, nil, nil, buttons,
|
||||
buttons, container.NewHScroll(f.fileName),
|
||||
)
|
||||
|
||||
body := container.NewHSplit(
|
||||
f.favoritesList,
|
||||
container.NewBorder(f.breadcrumbScroll, nil, nil, nil,
|
||||
f.breadcrumbScroll, f.filesScroll,
|
||||
),
|
||||
)
|
||||
body.SetOffset(0) // Set the minimum offset so that the favoritesList takes only it's minimal width
|
||||
|
||||
return container.NewBorder(header, footer, nil, nil, body)
|
||||
}
|
||||
|
||||
func (f *fileDialog) optionsMenu(position fyne.Position, buttonSize fyne.Size) {
|
||||
hiddenFiles := widget.NewCheck("Show Hidden Files", func(changed bool) {
|
||||
f.showHidden = changed
|
||||
f.refreshDir(f.dir)
|
||||
})
|
||||
hiddenFiles.Checked = f.showHidden
|
||||
hiddenFiles.Refresh()
|
||||
content := container.NewVBox(hiddenFiles)
|
||||
|
||||
p := position.Add(buttonSize)
|
||||
pos := fyne.NewPos(p.X-content.MinSize().Width-theme.Padding()*2, p.Y+theme.Padding()*2)
|
||||
widget.ShowPopUpAtPosition(content, f.win.Canvas, pos)
|
||||
}
|
||||
|
||||
func (f *fileDialog) loadFavorites() {
|
||||
favoriteLocations, err := getFavoriteLocations()
|
||||
if err != nil {
|
||||
fyne.LogError("Getting favorite locations", err)
|
||||
}
|
||||
favoriteIcons := getFavoriteIcons()
|
||||
favoriteOrder := getFavoriteOrder()
|
||||
|
||||
f.favorites = []favoriteItem{
|
||||
{locName: "Home", locIcon: theme.HomeIcon(), loc: favoriteLocations["Home"]}}
|
||||
app := fyne.CurrentApp()
|
||||
if hasAppFiles(app) {
|
||||
f.favorites = append(f.favorites,
|
||||
favoriteItem{locName: "App Files", locIcon: theme.FileIcon(), loc: storageURI(app)})
|
||||
}
|
||||
f.favorites = append(f.favorites, f.getPlaces()...)
|
||||
|
||||
for _, locName := range favoriteOrder {
|
||||
loc, ok := favoriteLocations[locName]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
locIcon := favoriteIcons[locName]
|
||||
f.favorites = append(f.favorites,
|
||||
favoriteItem{locName: locName, locIcon: locIcon, loc: loc})
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fileDialog) refreshDir(dir fyne.ListableURI) {
|
||||
f.files.Objects = nil
|
||||
|
||||
files, err := dir.List()
|
||||
if err != nil {
|
||||
fyne.LogError("Unable to read ListableURI "+dir.String(), err)
|
||||
return
|
||||
}
|
||||
|
||||
var icons []fyne.CanvasObject
|
||||
parent, err := storage.Parent(dir)
|
||||
if err != nil && err != repository.ErrURIRoot {
|
||||
fyne.LogError("Unable to get parent of "+dir.String(), err)
|
||||
return
|
||||
}
|
||||
if parent != nil && parent.String() != dir.String() {
|
||||
fi := &fileDialogItem{picker: f, name: "(Parent)", location: parent, dir: true}
|
||||
fi.ExtendBaseWidget(fi)
|
||||
icons = append(icons, fi)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if !f.showHidden && isHidden(file) {
|
||||
continue
|
||||
}
|
||||
|
||||
listable, err := storage.CanList(file)
|
||||
if f.file.isDirectory() && err != nil {
|
||||
continue
|
||||
} else if err == nil && listable { // URI points to a directory
|
||||
icons = append(icons, f.newFileItem(file, true)) // Pass the listable URI to avoid doing the same check in FileIcon
|
||||
} else if f.file.filter == nil || f.file.filter.Matches(file) {
|
||||
icons = append(icons, f.newFileItem(file, false))
|
||||
}
|
||||
}
|
||||
|
||||
f.files.Objects = icons
|
||||
f.files.Refresh()
|
||||
f.filesScroll.Offset = fyne.NewPos(0, 0)
|
||||
f.filesScroll.Refresh()
|
||||
}
|
||||
|
||||
func (f *fileDialog) setLocation(dir fyne.URI) error {
|
||||
if dir == nil {
|
||||
return fmt.Errorf("failed to open nil directory")
|
||||
}
|
||||
list, err := storage.ListerForURI(dir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
isFav := false
|
||||
for i, fav := range f.favorites {
|
||||
if fav.loc == nil {
|
||||
continue
|
||||
}
|
||||
if fav.loc.Path() == dir.Path() {
|
||||
f.favoritesList.Select(i)
|
||||
isFav = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isFav {
|
||||
f.favoritesList.UnselectAll()
|
||||
}
|
||||
|
||||
f.setSelected(nil)
|
||||
f.dir = list
|
||||
|
||||
f.breadcrumb.Objects = nil
|
||||
|
||||
localdir := dir.String()[len(dir.Scheme())+3:]
|
||||
|
||||
buildDir := filepath.VolumeName(localdir)
|
||||
for i, d := range strings.Split(localdir, "/") {
|
||||
if d == "" {
|
||||
if i > 0 { // what we get if we split "/"
|
||||
break
|
||||
}
|
||||
buildDir = "/"
|
||||
d = "/"
|
||||
} else if i > 0 {
|
||||
buildDir = filepath.Join(buildDir, d)
|
||||
} else {
|
||||
d = buildDir
|
||||
buildDir = d + string(os.PathSeparator)
|
||||
}
|
||||
|
||||
newDir := storage.NewFileURI(buildDir)
|
||||
isDir, err := storage.CanList(newDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isDir {
|
||||
return errors.New("location was not a listable URI")
|
||||
}
|
||||
f.breadcrumb.Add(
|
||||
widget.NewButton(d, func() {
|
||||
err := f.setLocation(newDir)
|
||||
if err != nil {
|
||||
fyne.LogError("Failed to set directory", err)
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
f.breadcrumbScroll.Refresh()
|
||||
f.breadcrumbScroll.Offset.X = f.breadcrumbScroll.Content.Size().Width - f.breadcrumbScroll.Size().Width
|
||||
f.breadcrumbScroll.Refresh()
|
||||
|
||||
if f.file.isDirectory() {
|
||||
f.fileName.SetText(dir.Name())
|
||||
f.open.Enable()
|
||||
}
|
||||
f.refreshDir(list)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fileDialog) setSelected(file *fileDialogItem) {
|
||||
if f.selected != nil {
|
||||
f.selected.isCurrent = false
|
||||
f.selected.Refresh()
|
||||
}
|
||||
if file != nil && file.isDirectory() {
|
||||
listable, err := storage.CanList(file.location)
|
||||
if err != nil || !listable {
|
||||
fyne.LogError("Failed to create lister for URI"+file.location.String(), err)
|
||||
}
|
||||
f.setLocation(file.location)
|
||||
return
|
||||
}
|
||||
f.selected = file
|
||||
|
||||
if file == nil || file.location.String()[len(file.location.Scheme())+3:] == "" {
|
||||
// keep user input while navigating
|
||||
// in a FileSave dialog
|
||||
if !f.file.save {
|
||||
f.fileName.SetText("")
|
||||
f.open.Disable()
|
||||
}
|
||||
} else {
|
||||
file.isCurrent = true
|
||||
f.fileName.SetText(file.location.Name())
|
||||
f.open.Enable()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *fileDialog) setView(view viewLayout) {
|
||||
f.view = view
|
||||
if f.view == gridView {
|
||||
padding := fyne.NewSize(fileIconCellWidth-fileIconSize, theme.Padding())
|
||||
f.files = container.NewGridWrap(
|
||||
fyne.NewSize(fileIconSize, fileIconSize+fileTextSize).Add(padding),
|
||||
)
|
||||
} else {
|
||||
f.files = container.NewVBox()
|
||||
}
|
||||
if f.dir != nil {
|
||||
f.refreshDir(f.dir)
|
||||
}
|
||||
f.filesScroll.Content = container.NewPadded(f.files)
|
||||
f.filesScroll.Refresh()
|
||||
}
|
||||
|
||||
// effectiveStartingDir calculates the directory at which the file dialog should
|
||||
// open, based on the values of startingDirectory, CWD, home, and any error
|
||||
// conditions which occur.
|
||||
//
|
||||
// Order of precedence is:
|
||||
//
|
||||
// * file.startingDirectory if non-empty, os.Stat()-able, and uses the file://
|
||||
// URI scheme
|
||||
// * os.UserHomeDir()
|
||||
// * os.Getwd()
|
||||
// * "/" (should be filesystem root on all supported platforms)
|
||||
//
|
||||
func (f *FileDialog) effectiveStartingDir() fyne.ListableURI {
|
||||
if f.startingLocation != nil {
|
||||
if f.startingLocation.Scheme() == "file" {
|
||||
path := f.startingLocation.Path()
|
||||
|
||||
// the starting directory is set explicitly
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
fyne.LogError("Error with StartingLocation", err)
|
||||
} else {
|
||||
return f.startingLocation
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Try app storage
|
||||
app := fyne.CurrentApp()
|
||||
if hasAppFiles(app) {
|
||||
list, _ := storage.ListerForURI(storageURI(app))
|
||||
return list
|
||||
}
|
||||
|
||||
// Try home dir
|
||||
dir, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
lister, err := storage.ListerForURI(storage.NewFileURI(dir))
|
||||
if err == nil {
|
||||
return lister
|
||||
}
|
||||
fyne.LogError("Could not create lister for user home dir", err)
|
||||
}
|
||||
fyne.LogError("Could not load user home dir", err)
|
||||
|
||||
// Try to get ./
|
||||
wd, err := os.Getwd()
|
||||
if err == nil {
|
||||
lister, err := storage.ListerForURI(storage.NewFileURI(wd))
|
||||
if err == nil {
|
||||
return lister
|
||||
}
|
||||
fyne.LogError("Could not create lister for working dir", err)
|
||||
}
|
||||
|
||||
lister, err := storage.ListerForURI(storage.NewFileURI("/"))
|
||||
if err != nil {
|
||||
fyne.LogError("could not create lister for /", err)
|
||||
return nil
|
||||
}
|
||||
return lister
|
||||
}
|
||||
|
||||
func showFile(file *FileDialog) *fileDialog {
|
||||
d := &fileDialog{file: file, initialFileName: file.initialFileName}
|
||||
ui := d.makeUI()
|
||||
size := ui.MinSize().Add(fyne.NewSize(fileIconCellWidth*2+theme.Padding()*6+theme.Padding(),
|
||||
(fileIconSize+fileTextSize)+theme.Padding()*6))
|
||||
|
||||
d.win = widget.NewModalPopUp(ui, file.parent.Canvas())
|
||||
d.win.Resize(size)
|
||||
|
||||
d.setLocation(file.effectiveStartingDir())
|
||||
d.win.Show()
|
||||
return d
|
||||
}
|
||||
|
||||
// MinSize returns the size that this dialog should not shrink below
|
||||
//
|
||||
// Since: 2.1
|
||||
func (f *FileDialog) MinSize() fyne.Size {
|
||||
return f.dialog.win.MinSize()
|
||||
}
|
||||
|
||||
// Show shows the file dialog.
|
||||
func (f *FileDialog) Show() {
|
||||
if f.save {
|
||||
if fileSaveOSOverride(f) {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if fileOpenOSOverride(f) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if f.dialog != nil {
|
||||
f.dialog.win.Show()
|
||||
return
|
||||
}
|
||||
f.dialog = showFile(f)
|
||||
if !f.desiredSize.IsZero() {
|
||||
f.Resize(f.desiredSize)
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh causes this dialog to be updated
|
||||
func (f *FileDialog) Refresh() {
|
||||
f.dialog.win.Refresh()
|
||||
}
|
||||
|
||||
// Resize dialog to the requested size, if there is sufficient space.
|
||||
// If the parent window is not large enough then the size will be reduced to fit.
|
||||
func (f *FileDialog) Resize(size fyne.Size) {
|
||||
f.desiredSize = size
|
||||
if f.dialog == nil {
|
||||
return
|
||||
}
|
||||
f.dialog.win.Resize(size)
|
||||
}
|
||||
|
||||
// Hide hides the file dialog.
|
||||
func (f *FileDialog) Hide() {
|
||||
if f.dialog == nil {
|
||||
return
|
||||
}
|
||||
f.dialog.win.Hide()
|
||||
if f.onClosedCallback != nil {
|
||||
f.onClosedCallback(false)
|
||||
}
|
||||
}
|
||||
|
||||
// SetConfirmText allows custom text to be set in the confirmation button
|
||||
//
|
||||
// Since: 2.2
|
||||
func (f *FileDialog) SetConfirmText(label string) {
|
||||
f.confirmText = label
|
||||
if f.dialog == nil {
|
||||
return
|
||||
}
|
||||
f.dialog.open.SetText(label)
|
||||
f.dialog.win.Refresh()
|
||||
}
|
||||
|
||||
// SetDismissText allows custom text to be set in the dismiss button
|
||||
func (f *FileDialog) SetDismissText(label string) {
|
||||
f.dismissText = label
|
||||
if f.dialog == nil {
|
||||
return
|
||||
}
|
||||
f.dialog.dismiss.SetText(label)
|
||||
f.dialog.win.Refresh()
|
||||
}
|
||||
|
||||
// SetLocation tells this FileDirectory which location to display.
|
||||
// This is normally called before the dialog is shown.
|
||||
//
|
||||
// Since: 1.4
|
||||
func (f *FileDialog) SetLocation(u fyne.ListableURI) {
|
||||
f.startingLocation = u
|
||||
if f.dialog != nil {
|
||||
f.dialog.setLocation(u)
|
||||
}
|
||||
}
|
||||
|
||||
// SetOnClosed sets a callback function that is called when
|
||||
// the dialog is closed.
|
||||
func (f *FileDialog) SetOnClosed(closed func()) {
|
||||
if f.dialog == nil {
|
||||
return
|
||||
}
|
||||
// If there is already a callback set, remember it and call both.
|
||||
originalCallback := f.onClosedCallback
|
||||
|
||||
f.onClosedCallback = func(response bool) {
|
||||
closed()
|
||||
if originalCallback != nil {
|
||||
originalCallback(response)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetFilter sets a filter for limiting files that can be chosen in the file dialog.
|
||||
func (f *FileDialog) SetFilter(filter storage.FileFilter) {
|
||||
if f.isDirectory() {
|
||||
fyne.LogError("Cannot set a filter for a folder dialog", nil)
|
||||
return
|
||||
}
|
||||
f.filter = filter
|
||||
if f.dialog != nil {
|
||||
f.dialog.refreshDir(f.dialog.dir)
|
||||
}
|
||||
}
|
||||
|
||||
// SetFileName sets the filename in a FileDialog in save mode.
|
||||
// This is normally called before the dialog is shown.
|
||||
func (f *FileDialog) SetFileName(fileName string) {
|
||||
if f.save {
|
||||
f.initialFileName = fileName
|
||||
//Update entry if fileDialog has already been created
|
||||
if f.dialog != nil {
|
||||
f.dialog.fileName.SetText(fileName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewFileOpen creates a file dialog allowing the user to choose a file to open.
|
||||
// The callback function will run when the dialog closes. The URI will be nil
|
||||
// when the user cancels or when nothing is selected.
|
||||
//
|
||||
// The dialog will appear over the window specified when Show() is called.
|
||||
func NewFileOpen(callback func(fyne.URIReadCloser, error), parent fyne.Window) *FileDialog {
|
||||
dialog := &FileDialog{callback: callback, parent: parent}
|
||||
return dialog
|
||||
}
|
||||
|
||||
// NewFileSave creates a file dialog allowing the user to choose a file to save
|
||||
// to (new or overwrite). If the user chooses an existing file they will be
|
||||
// asked if they are sure. The callback function will run when the dialog
|
||||
// closes. The URI will be nil when the user cancels or when nothing is
|
||||
// selected.
|
||||
//
|
||||
// The dialog will appear over the window specified when Show() is called.
|
||||
func NewFileSave(callback func(fyne.URIWriteCloser, error), parent fyne.Window) *FileDialog {
|
||||
dialog := &FileDialog{callback: callback, parent: parent, save: true}
|
||||
return dialog
|
||||
}
|
||||
|
||||
// ShowFileOpen creates and shows a file dialog allowing the user to choose a
|
||||
// file to open. The callback function will run when the dialog closes. The URI
|
||||
// will be nil when the user cancels or when nothing is selected.
|
||||
//
|
||||
// The dialog will appear over the window specified.
|
||||
func ShowFileOpen(callback func(fyne.URIReadCloser, error), parent fyne.Window) {
|
||||
dialog := NewFileOpen(callback, parent)
|
||||
if fileOpenOSOverride(dialog) {
|
||||
return
|
||||
}
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
// ShowFileSave creates and shows a file dialog allowing the user to choose a
|
||||
// file to save to (new or overwrite). If the user chooses an existing file they
|
||||
// will be asked if they are sure. The callback function will run when the
|
||||
// dialog closes. The URI will be nil when the user cancels or when nothing is
|
||||
// selected.
|
||||
//
|
||||
// The dialog will appear over the window specified.
|
||||
func ShowFileSave(callback func(fyne.URIWriteCloser, error), parent fyne.Window) {
|
||||
dialog := NewFileSave(callback, parent)
|
||||
if fileSaveOSOverride(dialog) {
|
||||
return
|
||||
}
|
||||
dialog.Show()
|
||||
}
|
||||
|
||||
func getFavoriteIcons() map[string]fyne.Resource {
|
||||
if runtime.GOOS == "darwin" {
|
||||
return map[string]fyne.Resource{
|
||||
"Documents": theme.DocumentIcon(),
|
||||
"Downloads": theme.DownloadIcon(),
|
||||
"Music": theme.MediaMusicIcon(),
|
||||
"Pictures": theme.MediaPhotoIcon(),
|
||||
"Movies": theme.MediaVideoIcon(),
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]fyne.Resource{
|
||||
"Documents": theme.DocumentIcon(),
|
||||
"Downloads": theme.DownloadIcon(),
|
||||
"Music": theme.MediaMusicIcon(),
|
||||
"Pictures": theme.MediaPhotoIcon(),
|
||||
"Videos": theme.MediaVideoIcon(),
|
||||
}
|
||||
}
|
||||
|
||||
func getFavoriteOrder() []string {
|
||||
order := []string{
|
||||
"Documents",
|
||||
"Downloads",
|
||||
"Music",
|
||||
"Pictures",
|
||||
"Videos",
|
||||
}
|
||||
|
||||
if runtime.GOOS == "darwin" {
|
||||
order[4] = "Movies"
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
func hasAppFiles(a fyne.App) bool {
|
||||
return len(a.Storage().List()) > 0
|
||||
}
|
||||
|
||||
func storageURI(a fyne.App) fyne.URI {
|
||||
dir, _ := storage.Child(a.Storage().RootURI(), "Documents")
|
||||
return dir
|
||||
}
|
44
vendor/fyne.io/fyne/v2/dialog/file_darwin.go
generated
vendored
44
vendor/fyne.io/fyne/v2/dialog/file_darwin.go
generated
vendored
@@ -1,44 +0,0 @@
|
||||
//go:build !ios && !android && !wasm && !js
|
||||
// +build !ios,!android,!wasm,!js
|
||||
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
)
|
||||
|
||||
func getFavoriteLocations() (map[string]fyne.ListableURI, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
homeURI := storage.NewFileURI(homeDir)
|
||||
|
||||
favoriteNames := append(getFavoriteOrder(), "Home")
|
||||
favoriteLocations := make(map[string]fyne.ListableURI)
|
||||
for _, favName := range favoriteNames {
|
||||
var uri fyne.URI
|
||||
var err1 error
|
||||
if favName == "Home" {
|
||||
uri = homeURI
|
||||
} else {
|
||||
uri, err1 = storage.Child(homeURI, favName)
|
||||
}
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
continue
|
||||
}
|
||||
|
||||
listURI, err1 := storage.ListerForURI(uri)
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
continue
|
||||
}
|
||||
favoriteLocations[favName] = listURI
|
||||
}
|
||||
|
||||
return favoriteLocations, err
|
||||
}
|
36
vendor/fyne.io/fyne/v2/dialog/file_goxjs.go
generated
vendored
36
vendor/fyne.io/fyne/v2/dialog/file_goxjs.go
generated
vendored
@@ -1,36 +0,0 @@
|
||||
//go:build wasm || js
|
||||
// +build wasm js
|
||||
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
func (f *fileDialog) loadPlaces() []fyne.CanvasObject {
|
||||
return nil
|
||||
}
|
||||
|
||||
func isHidden(file fyne.URI) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func fileOpenOSOverride(f *FileDialog) bool {
|
||||
// TODO #2737
|
||||
return true
|
||||
}
|
||||
|
||||
func fileSaveOSOverride(f *FileDialog) bool {
|
||||
// TODO #2738
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *fileDialog) getPlaces() []favoriteItem {
|
||||
return []favoriteItem{}
|
||||
}
|
||||
|
||||
func getFavoriteLocations() (map[string]fyne.ListableURI, error) {
|
||||
favoriteLocations := make(map[string]fyne.ListableURI)
|
||||
|
||||
return favoriteLocations, nil
|
||||
}
|
76
vendor/fyne.io/fyne/v2/dialog/file_mobile.go
generated
vendored
76
vendor/fyne.io/fyne/v2/dialog/file_mobile.go
generated
vendored
@@ -1,76 +0,0 @@
|
||||
//go:build ios || android
|
||||
// +build ios android
|
||||
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/internal/driver/mobile"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
)
|
||||
|
||||
func (f *fileDialog) getPlaces() []favoriteItem {
|
||||
return []favoriteItem{}
|
||||
}
|
||||
|
||||
func isHidden(file fyne.URI) bool {
|
||||
if file.Scheme() != "file" {
|
||||
fyne.LogError("Cannot check if non file is hidden", nil)
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hideFile(filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileOpenOSOverride(f *FileDialog) bool {
|
||||
if f.isDirectory() {
|
||||
mobile.ShowFolderOpenPicker(f.callback.(func(fyne.ListableURI, error)))
|
||||
} else {
|
||||
mobile.ShowFileOpenPicker(f.callback.(func(fyne.URIReadCloser, error)), f.filter)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func fileSaveOSOverride(f *FileDialog) bool {
|
||||
mobile.ShowFileSavePicker(f.callback.(func(fyne.URIWriteCloser, error)), f.filter, f.initialFileName)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func getFavoriteLocations() (map[string]fyne.ListableURI, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
homeURI := storage.NewFileURI(homeDir)
|
||||
|
||||
favoriteNames := getFavoriteOrder()
|
||||
favoriteLocations := make(map[string]fyne.ListableURI)
|
||||
for _, favName := range favoriteNames {
|
||||
var uri fyne.URI
|
||||
var err1 error
|
||||
if favName == "Home" {
|
||||
uri = homeURI
|
||||
} else {
|
||||
uri, err1 = storage.Child(homeURI, favName)
|
||||
}
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
continue
|
||||
}
|
||||
|
||||
listURI, err1 := storage.ListerForURI(uri)
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
continue
|
||||
}
|
||||
favoriteLocations[favName] = listURI
|
||||
}
|
||||
|
||||
return favoriteLocations, err
|
||||
}
|
47
vendor/fyne.io/fyne/v2/dialog/file_unix.go
generated
vendored
47
vendor/fyne.io/fyne/v2/dialog/file_unix.go
generated
vendored
@@ -1,47 +0,0 @@
|
||||
//go:build !windows && !android && !ios && !wasm && !js
|
||||
// +build !windows,!android,!ios,!wasm,!js
|
||||
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
func (f *fileDialog) getPlaces() []favoriteItem {
|
||||
lister, err := storage.ListerForURI(storage.NewFileURI("/"))
|
||||
if err != nil {
|
||||
fyne.LogError("could not create lister for /", err)
|
||||
return []favoriteItem{}
|
||||
}
|
||||
return []favoriteItem{{
|
||||
"Computer",
|
||||
theme.ComputerIcon(),
|
||||
lister,
|
||||
}}
|
||||
}
|
||||
|
||||
func isHidden(file fyne.URI) bool {
|
||||
if file.Scheme() != "file" {
|
||||
fyne.LogError("Cannot check if non file is hidden", nil)
|
||||
return false
|
||||
}
|
||||
path := file.String()[len(file.Scheme())+3:]
|
||||
name := filepath.Base(path)
|
||||
return name == "" || name[0] == '.'
|
||||
}
|
||||
|
||||
func hideFile(filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func fileOpenOSOverride(*FileDialog) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func fileSaveOSOverride(*FileDialog) bool {
|
||||
return false
|
||||
}
|
130
vendor/fyne.io/fyne/v2/dialog/file_windows.go
generated
vendored
130
vendor/fyne.io/fyne/v2/dialog/file_windows.go
generated
vendored
@@ -1,130 +0,0 @@
|
||||
package dialog
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
func driveMask() uint32 {
|
||||
dll, err := syscall.LoadLibrary("kernel32.dll")
|
||||
if err != nil {
|
||||
fyne.LogError("Error loading kernel32.dll", err)
|
||||
return 0
|
||||
}
|
||||
handle, err := syscall.GetProcAddress(dll, "GetLogicalDrives")
|
||||
if err != nil {
|
||||
fyne.LogError("Could not find GetLogicalDrives call", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
ret, _, err := syscall.Syscall(uintptr(handle), 0, 0, 0, 0)
|
||||
if err != syscall.Errno(0) { // for some reason Syscall returns something not nil on success
|
||||
fyne.LogError("Error calling GetLogicalDrives", err)
|
||||
return 0
|
||||
}
|
||||
|
||||
return uint32(ret)
|
||||
}
|
||||
|
||||
func listDrives() []string {
|
||||
var drives []string
|
||||
mask := driveMask()
|
||||
|
||||
for i := 0; i < 26; i++ {
|
||||
if mask&1 == 1 {
|
||||
letter := string('A' + rune(i))
|
||||
drives = append(drives, letter+":")
|
||||
}
|
||||
mask >>= 1
|
||||
}
|
||||
|
||||
return drives
|
||||
}
|
||||
|
||||
func (f *fileDialog) getPlaces() []favoriteItem {
|
||||
var places []favoriteItem
|
||||
|
||||
for _, drive := range listDrives() {
|
||||
driveRoot := drive + string(os.PathSeparator) // capture loop var
|
||||
driveRootURI, _ := storage.ListerForURI(storage.NewURI("file://" + driveRoot))
|
||||
places = append(places, favoriteItem{
|
||||
drive,
|
||||
theme.StorageIcon(),
|
||||
driveRootURI,
|
||||
})
|
||||
}
|
||||
return places
|
||||
}
|
||||
|
||||
func isHidden(file fyne.URI) bool {
|
||||
if file.Scheme() != "file" {
|
||||
fyne.LogError("Cannot check if non file is hidden", nil)
|
||||
return false
|
||||
}
|
||||
|
||||
path := file.String()[len(file.Scheme())+3:]
|
||||
|
||||
point, err := syscall.UTF16PtrFromString(path)
|
||||
if err != nil {
|
||||
fyne.LogError("Error making string pointer", err)
|
||||
return false
|
||||
}
|
||||
attr, err := syscall.GetFileAttributes(point)
|
||||
if err != nil {
|
||||
fyne.LogError("Error getting file attributes", err)
|
||||
return false
|
||||
}
|
||||
|
||||
return attr&syscall.FILE_ATTRIBUTE_HIDDEN != 0
|
||||
}
|
||||
|
||||
func hideFile(filename string) (err error) {
|
||||
// git does not preserve windows hidden flag so we have to set it.
|
||||
filenameW, err := syscall.UTF16PtrFromString(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.SetFileAttributes(filenameW, syscall.FILE_ATTRIBUTE_HIDDEN)
|
||||
}
|
||||
|
||||
func fileOpenOSOverride(*FileDialog) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func fileSaveOSOverride(*FileDialog) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func getFavoriteLocations() (map[string]fyne.ListableURI, error) {
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
homeURI := storage.NewFileURI(homeDir)
|
||||
|
||||
favoriteNames := getFavoriteOrder()
|
||||
home, _ := storage.ListerForURI(homeURI)
|
||||
favoriteLocations := map[string]fyne.ListableURI{
|
||||
"Home": home,
|
||||
}
|
||||
for _, favName := range favoriteNames {
|
||||
uri, err1 := storage.Child(homeURI, favName)
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
continue
|
||||
}
|
||||
|
||||
listURI, err1 := storage.ListerForURI(uri)
|
||||
if err1 != nil {
|
||||
err = err1
|
||||
continue
|
||||
}
|
||||
favoriteLocations[favName] = listURI
|
||||
}
|
||||
|
||||
return favoriteLocations, err
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user