Update go2tv-lite

This commit is contained in:
Alex Ballas
2022-07-19 21:51:52 +03:00
parent 948964bdba
commit c31ca8703e
1678 changed files with 42 additions and 701459 deletions

View File

@@ -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)
}

View File

@@ -1 +1 @@
1.12.0
devel

View File

@@ -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 (

View File

@@ -1 +1 @@
1.12.0
devel

54
vendor/fyne.io/fyne/v2/.gitignore generated vendored
View File

@@ -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~

View File

@@ -1 +0,0 @@
fyne.io/fyne/v2

14
vendor/fyne.io/fyne/v2/AUTHORS generated vendored
View File

@@ -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
View File

@@ -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 widgets 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

View File

@@ -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

View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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
View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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

View File

@@ -1,8 +0,0 @@
//go:build debug
// +build debug
package app
import "fyne.io/fyne/v2"
const buildMode = fyne.BuildDebug

View File

@@ -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()
}

View File

@@ -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
View File

@@ -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)
}

View File

@@ -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/"
}

View File

@@ -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
}

View File

@@ -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++;
}

View File

@@ -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")
}

View File

@@ -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
}

View File

@@ -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];
}

View File

@@ -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"

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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.")
}

View File

@@ -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
}

View File

@@ -1,8 +0,0 @@
//go:build release
// +build release
package app
import "fyne.io/fyne/v2"
const buildMode = fyne.BuildRelease

View File

@@ -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)
}

View File

@@ -1,8 +0,0 @@
//go:build !debug && !release
// +build !debug,!release
package app
import "fyne.io/fyne/v2"
const buildMode = fyne.BuildStandard

View File

@@ -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()
})
}
}

View File

@@ -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
}))
}
}

View File

@@ -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
}

View File

@@ -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
View File

@@ -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
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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()
})
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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() {
}

View File

@@ -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
}

View File

@@ -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
View File

@@ -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)
}

View File

@@ -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
View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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}
}

View File

@@ -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
View File

@@ -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,
}
}

View File

@@ -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
},
}
}

View File

@@ -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,
}
}

View File

@@ -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,
}
}

View File

@@ -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)
}

View File

@@ -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
View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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...)
}

View File

@@ -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()
}

View File

@@ -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...)
}

View File

@@ -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)
}

View File

@@ -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 := &divider{
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 &dividerRenderer{
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
}

View File

@@ -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()
}

View File

@@ -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())
}

View File

@@ -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)
}

File diff suppressed because it is too large Load Diff

View File

@@ -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()
}

View File

@@ -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()
}

View File

@@ -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)
}

View File

@@ -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:]...)
}

View File

@@ -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}
}

View File

@@ -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()
}
}

View File

@@ -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()
}

View File

@@ -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
}

View File

@@ -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
View File

@@ -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
View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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))
}

View File

@@ -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)
}

View File

@@ -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()
}

View File

@@ -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
View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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