feat: add .desktop entry

This commit is contained in:
Louis Dalibard 2024-04-29 19:13:50 +02:00
parent 75b0ea38ae
commit a64eabcfd4
1447 changed files with 755891 additions and 0 deletions

54
vendor/fyne.io/fyne/v2/.gitignore generated vendored Normal file

@ -0,0 +1,54 @@
### Project Specific
cmd/fyne/fyne
cmd/fyne/fyne.exe
cmd/fyne_demo/fyne_demo
cmd/fyne_demo/fyne_demo.apk
cmd/fyne_demo/fyne-demo.app
cmd/fyne_demo/fyne_demo.exe
cmd/fyne_settings/fyne_settings
cmd/fyne_settings/fyne_settings.apk
cmd/fyne_settings/fyne_settings.app
cmd/fyne_settings/fyne_settings.exe
cmd/hello/hello
cmd/hello/hello.apk
cmd/hello/hello.app
cmd/hello/hello.exe
fyne-cross
### Tests
**/testdata/failed
### Go
# Output of the coverage tool
*.out
### macOS
# General
.DS_Store
# Thumbnails
._*
### JetBrains
.idea
### VSCode
.vscode
### Vim
# Swap
[._]*.s[a-v][a-z]
[._]*.sw[a-p]
[._]s[a-v][a-z]
[._]sw[a-p]
# Session
Session.vim
# Temporary
.netrwhist
*~
# Auto-generated tag files
tags
# Persistent undo
[._]*.un~

1
vendor/fyne.io/fyne/v2/.godocdown.import generated vendored Normal file

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

14
vendor/fyne.io/fyne/v2/AUTHORS generated vendored Normal file

@ -0,0 +1,14 @@
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 <jacalz@tutanota.com>
Charles A. Daniels <charles@cdaniels.net>
Pablo Fuentes <f.pablo1@hotmail.com>
Changkun Ou <hi@changkun.de>

1316
vendor/fyne.io/fyne/v2/CHANGELOG.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

76
vendor/fyne.io/fyne/v2/CODE_OF_CONDUCT.md generated vendored Normal file

@ -0,0 +1,76 @@
# 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

63
vendor/fyne.io/fyne/v2/CONTRIBUTING.md generated vendored Normal file

@ -0,0 +1,63 @@
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)
For detailed Code style, check [Contributing](https://github.com/fyne-io/fyne/wiki/Contributing#code-style) in our wiki please.
# 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 Normal file

@ -0,0 +1,28 @@
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.

188
vendor/fyne.io/fyne/v2/README.md generated vendored Normal file

@ -0,0 +1,188 @@
<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='https://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.4 is the current release of the Fyne API, it added rounded corners, emoji,
layout debug support and table headers, along with a large number of
smaller feature additions.
We are now working towards the next big release, codenamed
[Elgin](https://github.com/fyne-io/fyne/milestone/21)
and more news will follow in our news feeds and GitHub project.
# Prerequisites
To develop apps using Fyne you will need Go version 1.17 or later, a C compiler and your system's development tools.
If you're not sure if that's all installed or you don't know how then check out our
[Getting Started](https://fyne.io/develop/) document.
Using the standard go tools you can install Fyne's core library using:
go get fyne.io/fyne/v2@latest
After importing a new module, run the following command before compiling the code for the first time. Avoid running it before writing code that uses the module to prevent accidental removal of dependencies:
go mod tidy
# Widget demo
To run a showcase of the features of Fyne execute the following:
go install fyne.io/fyne/v2/cmd/fyne_demo@latest
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" 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" 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" 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" alt="Fyne Hello Dark Theme" />
</td><td style="border: none;">
<img src="img/hello-dark.png" width="207" alt="Fyne Hello Dark Theme" />
</td></tr>
</table>
</div>
## 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 install fyne.io/fyne/v2/cmd/fyne@latest
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 install | 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/fyshos/fynedesk) as well :)
![FyneDesk screenshopt in dark mode](https://fyshos.com/img/desktop.png)

15
vendor/fyne.io/fyne/v2/SECURITY.md generated vendored Normal file

@ -0,0 +1,15 @@
# Security Policy
## Supported Versions
Minor releases will receive security updates and fixes until the next minor or major release.
| Version | Supported |
| ------- | ------------------ |
| 2.4.x | :white_check_mark: |
| < 2.4.0 | :x: |
## Reporting a Vulnerability
Report security vulnerabilities using the [advisories](https://github.com/fyne-io/fyne/security/advisories) page on GitHub.
The team of core developers will evaluate and address the issue as appropriate.

84
vendor/fyne.io/fyne/v2/animation.go generated vendored Normal file

@ -0,0 +1,84 @@
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
}

144
vendor/fyne.io/fyne/v2/app.go generated vendored Normal file

@ -0,0 +1,144 @@
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
// CloudProvider returns the current app cloud provider,
// if one has been registered by the developer or chosen by the user.
//
// Since: 2.3
CloudProvider() CloudProvider // get the (if any) configured provider
// SetCloudProvider allows developers to specify how this application should integrate with cloud services.
// See `fyne.io/cloud` package for implementation details.
//
// Since: 2.3
SetCloudProvider(CloudProvider) // configure cloud for this app
}
// 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
// Release if true this binary was build in release mode
// Since 2.3
Release bool
// Custom contain the custom metadata defined either in FyneApp.toml or on the compile command line
// Since 2.3
Custom map[string]string
}
// 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())
}

169
vendor/fyne.io/fyne/v2/app/app.go generated vendored Normal file

@ -0,0 +1,169 @@
// 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 (
"os"
"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"
)
// Declare conformity with App interface
var _ fyne.App = (*fyneApp)(nil)
type fyneApp struct {
driver fyne.Driver
icon fyne.Resource
uniqueID string
cloud fyne.CloudProvider
lifecycle fyne.Lifecycle
settings *settings
storage fyne.Storage
prefs fyne.Preferences
running uint32 // atomic, 1 == running, 0 == stopped
}
func (a *fyneApp) CloudProvider() fyne.CloudProvider {
return a.cloud
}
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()
}
}
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
}
func (a *fyneApp) newDefaultPreferences() *preferences {
p := newPreferences(a)
if a.uniqueID != "" {
p.load()
}
return p
}
// 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 makeStoreDocs(id string, s *store) *internal.Docs {
if id != "" {
err := os.MkdirAll(s.a.storageRoot(), 0755) // make the space before anyone can use it
if err != nil {
fyne.LogError("Failed to create app storage space", err)
}
root, _ := s.docRootURI()
return &internal.Docs{RootDocURI: root}
} else {
return &internal.Docs{} // an empty impl to avoid crashes
}
}
func newAppWithDriver(d fyne.Driver, id string) fyne.App {
newApp := &fyneApp{uniqueID: id, driver: d, lifecycle: &app.Lifecycle{}}
fyne.SetCurrentApp(newApp)
newApp.prefs = newApp.newDefaultPreferences()
newApp.lifecycle.(*app.Lifecycle).SetOnStoppedHookExecuted(func() {
if prefs, ok := newApp.prefs.(*preferences); ok {
prefs.forceImmediateSave()
}
})
newApp.settings = loadSettings()
store := &store{a: newApp}
store.Docs = makeStoreDocs(id, store)
newApp.storage = store
if !d.Device().IsMobile() {
newApp.settings.watchSettings()
}
httpHandler := intRepo.NewHTTPRepository()
repository.Register("http", httpHandler)
repository.Register("https", httpHandler)
return newApp
}
// marker interface to pass system tray to supporting drivers
type systrayDriver interface {
SetSystemTrayMenu(*fyne.Menu)
SetSystemTrayIcon(resource fyne.Resource)
}

60
vendor/fyne.io/fyne/v2/app/app_darwin.go generated vendored Normal file

@ -0,0 +1,60 @@
//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)
}
func escapeNotificationString(in string) string {
noSlash := strings.ReplaceAll(in, "\\", "\\\\")
return strings.ReplaceAll(noSlash, "\"", "\\\"")
}
//export fallbackSend
func fallbackSend(cTitle, cContent *C.char) {
title := C.GoString(cTitle)
content := C.GoString(cContent)
fallbackNotification(title, content)
}
func fallbackNotification(title, content string) {
template := `display notification "%s" with title "%s"`
script := fmt.Sprintf(template, escapeNotificationString(content), escapeNotificationString(title))
err := execabs.Command("osascript", "-e", script).Start()
if err != nil {
fyne.LogError("Failed to launch darwin notify script", err)
}
}

61
vendor/fyne.io/fyne/v2/app/app_darwin.m generated vendored Normal file

@ -0,0 +1,61 @@
//go:build !ci
// +build !ci
#import <Foundation/Foundation.h>
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
#import <UserNotifications/UserNotifications.h>
#endif
static int notifyNum = 0;
extern void fallbackSend(char *cTitle, char *cBody);
bool isBundled() {
return [[NSBundle mainBundle] bundleIdentifier] != nil;
}
#if __MAC_OS_X_VERSION_MAX_ALLOWED >= 101400
void doSendNotification(UNUserNotificationCenter *center, NSString *title, NSString *body) {
UNMutableNotificationContent *content = [UNMutableNotificationContent new];
[content autorelease];
content.title = title;
content.body = body;
notifyNum++;
NSString *identifier = [NSString stringWithFormat:@"fyne-notify-%d", notifyNum];
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier
content:content trigger:nil];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(@"Could not send notification: %@", error);
}
}];
}
void sendNotification(char *cTitle, char *cBody) {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
NSString *title = [NSString stringWithUTF8String:cTitle];
NSString *body = [NSString stringWithUTF8String:cBody];
UNAuthorizationOptions options = UNAuthorizationOptionAlert;
[center requestAuthorizationWithOptions:options
completionHandler:^(BOOL granted, NSError *_Nullable error) {
if (!granted) {
if (error != NULL) {
NSLog(@"Error asking for permission to send notifications %@", error);
// this happens if our app was not signed, so do it the old way
fallbackSend((char *)[title UTF8String], (char *)[body UTF8String]);
} else {
NSLog(@"Unable to get permission to send notifications");
}
} else {
doSendNotification(center, title, body);
}
}];
}
#else
void sendNotification(char *cTitle, char *cBody) {
fallbackSend(cTitle, cBody);
}
#endif

8
vendor/fyne.io/fyne/v2/app/app_debug.go generated vendored Normal file

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

69
vendor/fyne.io/fyne/v2/app/app_desktop_darwin.go generated vendored Normal file

@ -0,0 +1,69 @@
//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"
"golang.org/x/sys/execabs"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)
// 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 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 := execabs.Command("open", url.String())
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
return cmd.Run()
}
//export themeChanged
func themeChanged() {
fyne.CurrentApp().Settings().(*settings).setupTheme()
}
func watchTheme() {
C.watchTheme()
}

18
vendor/fyne.io/fyne/v2/app/app_desktop_darwin.m generated vendored Normal file

@ -0,0 +1,18 @@
//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 Normal file

@ -0,0 +1,15 @@
//go:build !ci && !android && !ios && !mobile
// +build !ci,!android,!ios,!mobile
package app
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/driver/glfw"
)
// NewWithID returns a new app instance using the appropriate runtime driver.
// The ID string should be globally unique to this app.
func NewWithID(id string) fyne.App {
return newAppWithDriver(glfw.NewGLDriver(), id)
}

19
vendor/fyne.io/fyne/v2/app/app_goxjs.go generated vendored Normal file

@ -0,0 +1,19 @@
//go:build !ci && (!android || !ios || !mobile) && (js || wasm || test_web_driver)
// +build !ci
// +build !android !ios !mobile
// +build js wasm test_web_driver
package app
import (
"fyne.io/fyne/v2"
)
func (app *fyneApp) SendNotification(_ *fyne.Notification) {
// TODO #2735
fyne.LogError("Sending notification is not supported yet.", nil)
}
func rootConfigDir() string {
return "/data/"
}

25
vendor/fyne.io/fyne/v2/app/app_mobile.go generated vendored Normal file

@ -0,0 +1,25 @@
//go:build !ci && (android || ios || mobile)
// +build !ci
// +build android ios mobile
package app
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/driver/mobile"
)
var systemTheme fyne.ThemeVariant
// NewWithID returns a new app instance using the appropriate runtime driver.
// The ID string should be globally unique to this app.
func NewWithID(id string) fyne.App {
d := mobile.NewGoMobileDriver()
a := newAppWithDriver(d, id)
d.(mobile.ConfiguredDriver).SetOnConfigurationChanged(func(c *mobile.Configuration) {
systemTheme = c.SystemTheme
a.Settings().(*settings).setupTheme()
})
return a
}

131
vendor/fyne.io/fyne/v2/app/app_mobile_and.c generated vendored Normal file

@ -0,0 +1,131 @@
//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, (jobject)ctx);
jmethodID getSystemService = find_method(env, ctxClass, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;");
return (jobject)(*env)->CallObjectMethod(env, (jobject)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, (jobject)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((uintptr_t)env, ctx, "notification");
if (isOreoOrLater(env)) {
jstring channelId = (*env)->NewStringUTF(env, "fyne-notif");
jstring name = (*env)->NewStringUTF(env, "Fyne Notification");
int importance = 4; // IMPORTANCE_HIGH
jclass chanCls = find_class(env, "android/app/NotificationChannel");
jmethodID constructor = find_method(env, chanCls, "<init>", "(Ljava/lang/String;Ljava/lang/CharSequence;I)V");
jobject channel = (*env)->NewObject(env, chanCls, constructor, channelId, name, importance);
jmethodID createChannel = find_method(env, mgrCls, "createNotificationChannel", "(Landroid/app/NotificationChannel;)V");
(*env)->CallVoidMethod(env, mgr, createChannel, channel);
jmethodID setChannelId = find_method(env, cls, "setChannelId", "(Ljava/lang/String;)Landroid/app/Notification$Builder;");
(*env)->CallObjectMethod(env, builder, setChannelId, channelId);
}
jmethodID setContentTitle = find_method(env, cls, "setContentTitle", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;");
(*env)->CallObjectMethod(env, builder, setContentTitle, titleStr);
jmethodID setContentText = find_method(env, cls, "setContentText", "(Ljava/lang/CharSequence;)Landroid/app/Notification$Builder;");
(*env)->CallObjectMethod(env, builder, setContentText, bodyStr);
int iconID = 17629184; // constant of "unknown app icon"
jmethodID setSmallIcon = find_method(env, cls, "setSmallIcon", "(I)Landroid/app/Notification$Builder;");
(*env)->CallObjectMethod(env, builder, setSmallIcon, iconID);
jmethodID build = find_method(env, cls, "build", "()Landroid/app/Notification;");
jobject notif = (*env)->CallObjectMethod(env, builder, build);
jmethodID notify = find_method(env, mgrCls, "notify", "(ILandroid/app/Notification;)V");
(*env)->CallVoidMethod(env, mgr, notify, nextId, notif);
nextId++;
}

61
vendor/fyne.io/fyne/v2/app/app_mobile_and.go generated vendored Normal file

@ -0,0 +1,61 @@
//go:build !ci && android
// +build !ci,android
package app
/*
#cgo LDFLAGS: -landroid -llog
#include <stdlib.h>
void openURL(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *url);
void sendNotification(uintptr_t java_vm, uintptr_t jni_env, uintptr_t ctx, char *title, char *content);
*/
import "C"
import (
"log"
"net/url"
"os"
"path/filepath"
"unsafe"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/driver/mobile/app"
)
func (a *fyneApp) OpenURL(url *url.URL) error {
urlStr := C.CString(url.String())
defer C.free(unsafe.Pointer(urlStr))
app.RunOnJVM(func(vm, env, ctx uintptr) error {
C.openURL(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), urlStr)
return nil
})
return nil
}
func (a *fyneApp) SendNotification(n *fyne.Notification) {
titleStr := C.CString(n.Title)
defer C.free(unsafe.Pointer(titleStr))
contentStr := C.CString(n.Content)
defer C.free(unsafe.Pointer(contentStr))
app.RunOnJVM(func(vm, env, ctx uintptr) error {
C.sendNotification(C.uintptr_t(vm), C.uintptr_t(env), C.uintptr_t(ctx), titleStr, contentStr)
return nil
})
}
func defaultVariant() fyne.ThemeVariant {
return systemTheme
}
func rootConfigDir() string {
filesDir := os.Getenv("FILESDIR")
if filesDir == "" {
log.Println("FILESDIR env was not set by android native code")
return "/data/data" // probably won't work, but we can't make a better guess
}
return filepath.Join(filesDir, "fyne")
}

40
vendor/fyne.io/fyne/v2/app/app_mobile_ios.go generated vendored Normal file

@ -0,0 +1,40 @@
//go:build !ci && ios
// +build !ci,ios
package app
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework Foundation -framework UIKit -framework UserNotifications
#include <stdlib.h>
char *documentsPath(void);
void openURL(char *urlStr);
void sendNotification(char *title, char *content);
*/
import "C"
import (
"net/url"
"path/filepath"
"unsafe"
"fyne.io/fyne/v2"
)
func rootConfigDir() string {
root := C.documentsPath()
return filepath.Join(C.GoString(root), "fyne")
}
func (a *fyneApp) OpenURL(url *url.URL) error {
urlStr := C.CString(url.String())
C.openURL(urlStr)
C.free(unsafe.Pointer(urlStr))
return nil
}
func defaultVariant() fyne.ThemeVariant {
return systemTheme
}

16
vendor/fyne.io/fyne/v2/app/app_mobile_ios.m generated vendored Normal file

@ -0,0 +1,16 @@
//go:build !ci && ios
// +build !ci,ios
#import <UIKit/UIKit.h>
void openURL(char *urlStr) {
UIApplication *app = [UIApplication sharedApplication];
NSURL *url = [NSURL URLWithString:[NSString stringWithUTF8String:urlStr]];
[app openURL:url options:@{} completionHandler:nil];
}
char *documentsPath() {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *path = paths.firstObject;
return [path UTF8String];
}

9
vendor/fyne.io/fyne/v2/app/app_notlegacy_darwin.go generated vendored Normal file

@ -0,0 +1,9 @@
//go:build !ci && !legacy && !js && !wasm && !test_web_driver
// +build !ci,!legacy,!js,!wasm,!test_web_driver
package app
/*
#cgo LDFLAGS: -framework Foundation -framework UserNotifications
*/
import "C"

20
vendor/fyne.io/fyne/v2/app/app_openurl_js.go generated vendored Normal file

@ -0,0 +1,20 @@
//go:build !ci && js && !wasm
// +build !ci,js,!wasm
package app
import (
"fmt"
"net/url"
"honnef.co/go/js/dom"
)
func (app *fyneApp) OpenURL(url *url.URL) error {
window := dom.GetWindow().Open(url.String(), "_blank", "")
if window == nil {
return fmt.Errorf("Unable to open a new window/tab for URL: %v.", url)
}
window.Focus()
return nil
}

19
vendor/fyne.io/fyne/v2/app/app_openurl_wasm.go generated vendored Normal file

@ -0,0 +1,19 @@
//go:build !ci && wasm
// +build !ci,wasm
package app
import (
"fmt"
"net/url"
"syscall/js"
)
func (app *fyneApp) OpenURL(url *url.URL) error {
window := js.Global().Call("open", url.String(), "_blank", "")
if window.Equal(js.Null()) {
return fmt.Errorf("Unable to open a new window/tab for URL: %v.", url)
}
window.Call("focus")
return nil
}

13
vendor/fyne.io/fyne/v2/app/app_openurl_web.go generated vendored Normal file

@ -0,0 +1,13 @@
//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.")
}

34
vendor/fyne.io/fyne/v2/app/app_other.go generated vendored Normal file

@ -0,0 +1,34 @@
//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"
"os"
"path/filepath"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)
func defaultVariant() fyne.ThemeVariant {
return theme.VariantDark
}
func rootConfigDir() string {
return filepath.Join(os.TempDir(), "fyne-test")
}
func (a *fyneApp) OpenURL(_ *url.URL) error {
return errors.New("Unable to open url for unknown operating system")
}
func (a *fyneApp) SendNotification(_ *fyne.Notification) {
fyne.LogError("Refusing to show notification for unknown operating system", nil)
}
func watchTheme() {
// no-op
}

8
vendor/fyne.io/fyne/v2/app/app_release.go generated vendored Normal file

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

16
vendor/fyne.io/fyne/v2/app/app_software.go generated vendored Normal file

@ -0,0 +1,16 @@
//go:build ci
// +build ci
package app
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/painter/software"
"fyne.io/fyne/v2/test"
)
// NewWithID returns a new app instance using the test (headless) driver.
// The ID string should be globally unique to this app.
func NewWithID(id string) fyne.App {
return newAppWithDriver(test.NewDriverWithPainter(software.NewPainter()), id)
}

8
vendor/fyne.io/fyne/v2/app/app_standard.go generated vendored Normal file

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

29
vendor/fyne.io/fyne/v2/app/app_theme_js.go generated vendored Normal file

@ -0,0 +1,29 @@
//go:build !ci && js && !wasm
// +build !ci,js,!wasm
package app
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
"github.com/gopherjs/gopherjs/js"
)
func defaultVariant() fyne.ThemeVariant {
if matchMedia := js.Global.Call("matchMedia", "(prefers-color-scheme: dark)"); matchMedia != js.Undefined {
if matches := matchMedia.Get("matches"); matches != js.Undefined && matches.Bool() {
return theme.VariantDark
}
return theme.VariantLight
}
return theme.VariantDark
}
func init() {
if matchMedia := js.Global.Call("matchMedia", "(prefers-color-scheme: dark)"); matchMedia != js.Undefined {
matchMedia.Call("addEventListener", "change", func(o *js.Object) {
fyne.CurrentApp().Settings().(*settings).setupTheme()
})
}
}

31
vendor/fyne.io/fyne/v2/app/app_theme_wasm.go generated vendored Normal file

@ -0,0 +1,31 @@
//go:build !ci && wasm
// +build !ci,wasm
package app
import (
"syscall/js"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)
func defaultVariant() fyne.ThemeVariant {
matches := js.Global().Call("matchMedia", "(prefers-color-scheme: dark)")
if matches.Truthy() {
if matches.Get("matches").Bool() {
return theme.VariantDark
}
return theme.VariantLight
}
return theme.VariantDark
}
func init() {
if matchMedia := js.Global().Call("matchMedia", "(prefers-color-scheme: dark)"); matchMedia.Truthy() {
matchMedia.Call("addEventListener", "change", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
fyne.CurrentApp().Settings().(*settings).setupTheme()
return nil
}))
}
}

13
vendor/fyne.io/fyne/v2/app/app_theme_web.go generated vendored Normal file

@ -0,0 +1,13 @@
//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
}

124
vendor/fyne.io/fyne/v2/app/app_windows.go generated vendored Normal file

@ -0,0 +1,124 @@
//go:build !ci && !js && !android && !ios && !wasm && !test_web_driver
// +build !ci,!js,!android,!ios,!wasm,!test_web_driver
package app
import (
"fmt"
"net/url"
"os"
"path/filepath"
"strings"
"syscall"
"golang.org/x/sys/execabs"
"golang.org/x/sys/windows/registry"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)
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 := execabs.Command("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 := os.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
}

202
vendor/fyne.io/fyne/v2/app/app_xdg.go generated vendored Normal file

@ -0,0 +1,202 @@
//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"
"golang.org/x/sys/execabs"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)
var once sync.Once
func defaultVariant() fyne.ThemeVariant {
return findFreedestktopColorScheme()
}
func (a *fyneApp) OpenURL(url *url.URL) error {
cmd := execabs.Command("xdg-open", url.String())
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
return cmd.Start()
}
// fetch color variant from dbus portal desktop settings.
func findFreedestktopColorScheme() fyne.ThemeVariant {
dbusConn, err := dbus.SessionBus()
if err != nil {
fyne.LogError("Unable to connect to session D-Bus", err)
return theme.VariantDark
}
dbusObj := dbusConn.Object("org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop")
call := dbusObj.Call(
"org.freedesktop.portal.Settings.Read",
dbus.FlagNoAutoStart,
"org.freedesktop.appearance",
"color-scheme",
)
if call.Err != nil {
// many desktops don't have this exported yet
return theme.VariantDark
}
var value uint8
if err = call.Store(&value); err != nil {
fyne.LogError("failed to read theme variant from D-Bus", err)
return theme.VariantDark
}
// See: https://github.com/flatpak/xdg-desktop-portal/blob/1.16.0/data/org.freedesktop.impl.portal.Settings.xml#L32-L46
// 0: No preference
// 1: Prefer dark appearance
// 2: Prefer light appearance
switch value {
case 2:
return theme.VariantLight
case 1:
return theme.VariantDark
default:
// Default to light theme to support Gnome's default see https://github.com/fyne-io/fyne/pull/3561
return theme.VariantLight
}
}
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) {
if desk, ok := a.Driver().(systrayDriver); ok { // don't use this on mobile tag
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) {
if desk, ok := a.Driver().(systrayDriver); ok { // don't use this on mobile tag
desk.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() {
go watchFreedekstopThemeChange()
}
func themeChanged() {
fyne.CurrentApp().Settings().(*settings).setupTheme()
}
// connect to dbus to detect color-schem theme changes in portal settings.
func watchFreedekstopThemeChange() {
conn, err := dbus.SessionBus()
if err != nil {
fyne.LogError("Unable to connect to session D-Bus", err)
return
}
if err := conn.AddMatchSignal(
dbus.WithMatchObjectPath("/org/freedesktop/portal/desktop"),
dbus.WithMatchInterface("org.freedesktop.portal.Settings"),
dbus.WithMatchMember("SettingChanged"),
); err != nil {
fyne.LogError("D-Bus signal match failed", err)
return
}
defer conn.Close()
dbusChan := make(chan *dbus.Signal)
conn.Signal(dbusChan)
for sig := range dbusChan {
for _, v := range sig.Body {
if v == "color-scheme" {
themeChanged()
break
}
}
}
}

47
vendor/fyne.io/fyne/v2/app/cloud.go generated vendored Normal file

@ -0,0 +1,47 @@
package app
import "fyne.io/fyne/v2"
func (a *fyneApp) SetCloudProvider(p fyne.CloudProvider) {
if p == nil {
a.cloud = nil
return
}
a.transitionCloud(p)
}
func (a *fyneApp) transitionCloud(p fyne.CloudProvider) {
if a.cloud != nil {
a.cloud.Cleanup(a)
}
err := p.Setup(a)
if err != nil {
fyne.LogError("Failed to set up cloud provider "+p.ProviderName(), err)
return
}
a.cloud = p
listeners := a.prefs.ChangeListeners()
if pp, ok := p.(fyne.CloudProviderPreferences); ok {
a.prefs = pp.CloudPreferences(a)
} else {
a.prefs = a.newDefaultPreferences()
}
if cloud, ok := p.(fyne.CloudProviderStorage); ok {
a.storage = cloud.CloudStorage(a)
} else {
store := &store{a: a}
store.Docs = makeStoreDocs(a.uniqueID, store)
a.storage = store
}
for _, l := range listeners {
a.prefs.AddChangeListener(l)
l() // assume that preferences have changed because we replaced the provider
}
// after transition ensure settings listener is fired
a.settings.apply()
}

28
vendor/fyne.io/fyne/v2/app/meta.go generated vendored Normal file

@ -0,0 +1,28 @@
package app
import (
"fyne.io/fyne/v2"
)
var meta = fyne.AppMetadata{
ID: "",
Name: "",
Version: "0.0.1",
Build: 1,
Release: false,
Custom: map[string]string{},
}
// 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
if meta.Custom == nil {
meta.Custom = map[string]string{}
}
}
func (a *fyneApp) Metadata() fyne.AppMetadata {
return meta
}

208
vendor/fyne.io/fyne/v2/app/preferences.go generated vendored Normal file

@ -0,0 +1,208 @@
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
needsSaveBeforeExit bool
}
// Declare conformity with Preferences interface
var _ fyne.Preferences = (*preferences)(nil)
// forceImmediateSave writes preferences to file immediately, ignoring the debouncing
// logic in the change listener. Does nothing if preferences are not backed with a file.
func (p *preferences) forceImmediateSave() {
if !p.needsSaveBeforeExit {
return
}
err := p.save()
if err != nil {
fyne.LogError("Failed on force saving preferences", err)
}
}
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)
if err != nil {
return
}
convertLists(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.needsSaveBeforeExit = true
p.AddChangeListener(func() {
if p != app.prefs {
return
}
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
}
func convertLists(values map[string]interface{}) {
for k, v := range values {
if items, ok := v.([]interface{}); ok {
if len(items) == 0 {
continue
}
switch items[0].(type) {
case bool:
bools := make([]bool, len(items))
for i, item := range items {
bools[i] = item.(bool)
}
values[k] = bools
case float64:
floats := make([]float64, len(items))
for i, item := range items {
floats[i] = item.(float64)
}
values[k] = floats
case int:
ints := make([]int, len(items))
for i, item := range items {
ints[i] = item.(int)
}
values[k] = ints
case string:
strings := make([]string, len(items))
for i, item := range items {
strings[i] = item.(string)
}
values[k] = strings
}
}
}
}

21
vendor/fyne.io/fyne/v2/app/preferences_android.go generated vendored Normal file

@ -0,0 +1,21 @@
//go:build android
// +build android
package app
import "path/filepath"
// storagePath returns the location of the settings storage
func (p *preferences) storagePath() string {
// we have no global storage, use app global instead - rootConfigDir looks up in app_mobile_and.go
return filepath.Join(p.app.storageRoot(), "preferences.json")
}
// storageRoot returns the location of the app storage
func (a *fyneApp) storageRoot() string {
return rootConfigDir() // we are in a sandbox, so no app ID added to this path
}
func (p *preferences) watch() {
// no-op on mobile
}

24
vendor/fyne.io/fyne/v2/app/preferences_ios.go generated vendored Normal file

@ -0,0 +1,24 @@
//go:build ios
// +build ios
package app
import (
"path/filepath"
)
import "C"
// storagePath returns the location of the settings storage
func (p *preferences) storagePath() string {
ret := filepath.Join(p.app.storageRoot(), "preferences.json")
return ret
}
// storageRoot returns the location of the app storage
func (a *fyneApp) storageRoot() string {
return rootConfigDir() // we are in a sandbox, so no app ID added to this path
}
func (p *preferences) watch() {
// no-op on mobile
}

20
vendor/fyne.io/fyne/v2/app/preferences_mobile.go generated vendored Normal file

@ -0,0 +1,20 @@
//go:build mobile
// +build mobile
package app
import "path/filepath"
// storagePath returns the location of the settings storage
func (p *preferences) storagePath() string {
return filepath.Join(p.app.storageRoot(), "preferences.json")
}
// storageRoot returns the location of the app storage
func (a *fyneApp) storageRoot() string {
return filepath.Join(rootConfigDir(), a.UniqueID())
}
func (p *preferences) watch() {
// no-op as we are in mobile simulation mode
}

29
vendor/fyne.io/fyne/v2/app/preferences_other.go generated vendored Normal file

@ -0,0 +1,29 @@
//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()
})
}

168
vendor/fyne.io/fyne/v2/app/settings.go generated vendored Normal file

@ -0,0 +1,168 @@
package app
import (
"bytes"
"os"
"path/filepath"
"sync"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/theme"
)
var noAnimations bool // set to true at compile time if no_animations tag is passed
// 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"`
CloudName string `json:"cloud_name"`
CloudConfig string `json:"cloud_config"`
DisableAnimations bool `json:"no_animations"`
}
// 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) ShowAnimations() bool {
return !s.schema.DisableAnimations && !noAnimations
}
func (s *settings) ThemeVariant() fyne.ThemeVariant {
return s.variant
}
func (s *settings) applyTheme(theme fyne.Theme, variant fyne.ThemeVariant) {
s.propertyLock.Lock()
defer s.propertyLock.Unlock()
s.variant = variant
s.theme = theme
s.apply()
}
func (s *settings) Scale() float32 {
s.propertyLock.RLock()
defer s.propertyLock.RUnlock()
if s.schema.Scale < 0.0 {
return 1.0 // catching any really old data still using the `-1` value for "auto" scale
}
return s.schema.Scale
}
func (s *settings) AddChangeListener(listener chan fyne.Settings) {
s.changeListeners.Store(listener, true) // the boolean is just a dummy value here.
}
func (s *settings) apply() {
s.changeListeners.Range(func(key, _ interface{}) bool {
listener := key.(chan fyne.Settings)
select {
case listener <- s:
default:
l := listener
go func() { l <- s }()
}
return true
})
}
func (s *settings) fileChanged() {
s.load()
s.apply()
}
func (s *settings) loadSystemTheme() fyne.Theme {
path := filepath.Join(rootConfigDir(), "theme.json")
data, err := fyne.LoadResourceFromPath(path)
if err != nil {
if !os.IsNotExist(err) {
fyne.LogError("Failed to load user theme file: "+path, err)
}
return theme.DefaultTheme()
}
if data != nil && data.Content() != nil {
th, err := theme.FromJSONReader(bytes.NewReader(data.Content()))
if err == nil {
return th
}
fyne.LogError("Failed to parse user theme file: "+path, err)
}
return theme.DefaultTheme()
}
func (s *settings) setupTheme() {
name := s.schema.ThemeName
if env := os.Getenv("FYNE_THEME"); env != "" {
name = env
}
variant := defaultVariant()
effectiveTheme := s.theme
if !s.themeSpecified {
effectiveTheme = s.loadSystemTheme()
}
switch name {
case "light":
variant = theme.VariantLight
case "dark":
variant = theme.VariantDark
}
s.applyTheme(effectiveTheme, variant)
}
func loadSettings() *settings {
s := &settings{}
s.load()
return s
}

75
vendor/fyne.io/fyne/v2/app/settings_desktop.go generated vendored Normal file

@ -0,0 +1,75 @@
//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.Has(fsnotify.Remove) { // 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
}

35
vendor/fyne.io/fyne/v2/app/settings_file.go generated vendored Normal file

@ -0,0 +1,35 @@
//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
}
defer file.Close()
decode := json.NewDecoder(file)
return decode.Decode(&s.schema)
}

24
vendor/fyne.io/fyne/v2/app/settings_goxjs.go generated vendored Normal file

@ -0,0 +1,24 @@
//go:build js || wasm || test_web_driver
// +build js wasm test_web_driver
package app
// TODO: #2734
func (s *settings) load() {
s.setupTheme()
s.schema.Scale = 1
}
func (s *settings) loadFromFile(path string) error {
return nil
}
func watchFile(path string, callback func()) {
}
func (s *settings) watchSettings() {
}
func (s *settings) stopWatching() {
}

12
vendor/fyne.io/fyne/v2/app/settings_mobile.go generated vendored Normal file

@ -0,0 +1,12 @@
//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
}

8
vendor/fyne.io/fyne/v2/app/settings_noanimation.go generated vendored Normal file

@ -0,0 +1,8 @@
//go:build no_animations
// +build no_animations
package app
func init() {
noAnimations = true
}

27
vendor/fyne.io/fyne/v2/app/storage.go generated vendored Normal file

@ -0,0 +1,27 @@
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 Normal file

@ -0,0 +1,58 @@
package fyne
import "image"
// Canvas defines a graphical canvas to which a CanvasObject or Container can be added.
// Each canvas has a scale which is automatically applied during the render process.
type Canvas interface {
Content() CanvasObject
SetContent(CanvasObject)
Refresh(CanvasObject)
// Focus makes the provided item focused.
// The item has to be added to the contents of the canvas before calling this.
Focus(Focusable)
// FocusNext focuses the next focusable item.
// If no item is currently focused, the first focusable item is focused.
// If the last focusable item is currently focused, the first focusable item is focused.
//
// Since: 2.0
FocusNext()
// FocusPrevious focuses the previous focusable item.
// If no item is currently focused, the last focusable item is focused.
// If the first focusable item is currently focused, the last focusable item is focused.
//
// Since: 2.0
FocusPrevious()
Unfocus()
Focused() Focusable
// Size returns the current size of this canvas
Size() Size
// Scale returns the current scale (multiplication factor) this canvas uses to render
// The pixel size of a CanvasObject can be found by multiplying by this value.
Scale() float32
// Overlays returns the overlay stack.
Overlays() OverlayStack
OnTypedRune() func(rune)
SetOnTypedRune(func(rune))
OnTypedKey() func(*KeyEvent)
SetOnTypedKey(func(*KeyEvent))
AddShortcut(shortcut Shortcut, handler func(shortcut Shortcut))
RemoveShortcut(shortcut Shortcut)
Capture() image.Image
// PixelCoordinateForPosition returns the x and y pixel coordinate for a given position on this canvas.
// This can be used to find absolute pixel positions or pixel offsets relative to an object top left.
PixelCoordinateForPosition(Position) (int, int)
// InteractiveArea returns the position and size of the central interactive area.
// Operating system elements may overlap the portions outside this area and widgets should avoid being outside.
//
// Since: 1.4
InteractiveArea() (Position, Size)
}

86
vendor/fyne.io/fyne/v2/canvas/animation.go generated vendored Normal file

@ -0,0 +1,86 @@
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 Normal file

@ -0,0 +1,100 @@
// 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
}

29
vendor/fyne.io/fyne/v2/canvas/canvas.go generated vendored Normal file

@ -0,0 +1,29 @@
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)
}
}
// repaint instructs the containing canvas to redraw, even if nothing changed.
func repaint(obj fyne.CanvasObject) {
if fyne.CurrentApp() == nil || fyne.CurrentApp().Driver() == nil {
return
}
c := fyne.CurrentApp().Driver().CanvasForObject(obj)
if c != nil {
if paint, ok := c.(interface{ SetDirty() }); ok {
paint.SetDirty()
}
}
}

90
vendor/fyne.io/fyne/v2/canvas/circle.go generated vendored Normal file

@ -0,0 +1,90 @@
package canvas
import (
"image/color"
"math"
"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
repaint(c)
}
// 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)
repaint(c)
}
// 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 with its configured 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(float32(math.Abs(float64(c.Position2.X)-float64(c.Position1.X))),
float32(math.Abs(float64(c.Position2.Y)-float64(c.Position1.Y))))
}
// Visible returns true if this circle is visible, false otherwise
func (c *Circle) Visible() bool {
return !c.Hidden
}

212
vendor/fyne.io/fyne/v2/canvas/gradient.go generated vendored Normal file

@ -0,0 +1,212 @@
package canvas
import (
"image"
"image/color"
"math"
"fyne.io/fyne/v2"
)
// 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)
}
// Hide will set this gradient to not be visible
func (g *LinearGradient) Hide() {
g.baseObject.Hide()
repaint(g)
}
// Move the gradient to a new position, relative to its parent / canvas
func (g *LinearGradient) Move(pos fyne.Position) {
g.baseObject.Move(pos)
repaint(g)
}
// Refresh causes this gradient to be redrawn with its configured 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)
}
// Hide will set this gradient to not be visible
func (g *RadialGradient) Hide() {
g.baseObject.Hide()
repaint(g)
}
// Move the gradient to a new position, relative to its parent / canvas
func (g *RadialGradient) Move(pos fyne.Position) {
g.baseObject.Move(pos)
repaint(g)
}
// Refresh causes this gradient to be redrawn with its configured 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 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}
}

362
vendor/fyne.io/fyne/v2/canvas/image.go generated vendored Normal file

@ -0,0 +1,362 @@
package canvas
import (
"bytes"
"errors"
"image"
_ "image/jpeg" // avoid users having to import when using image widget
_ "image/png" // avoid the same for PNG images
"io"
"os"
"path/filepath"
"sync"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal/cache"
"fyne.io/fyne/v2/internal/scale"
"fyne.io/fyne/v2/internal/svg"
"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, it will fill the area.
// The fill mode can be changed by setting FillMode to a different ImageFill.
type Image struct {
baseObject
aspect float32
icon *svg.Decoder
isSVG bool
lock sync.Mutex
// 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
}
// Aspect will return the original content aspect after it was last refreshed.
//
// Since: 2.4
func (i *Image) Aspect() float32 {
if i.aspect == 0 {
i.Refresh()
}
return i.aspect
}
// Hide will set this image to not be visible
func (i *Image) Hide() {
i.baseObject.Hide()
repaint(i)
}
// MinSize returns the specified minimum size, if set, or {1, 1} otherwise.
func (i *Image) MinSize() fyne.Size {
if i.Image == nil || i.aspect == 0 {
i.Refresh()
}
return i.baseObject.MinSize()
}
// Move the image object to a new position, relative to its parent top, left corner.
func (i *Image) Move(pos fyne.Position) {
i.baseObject.Move(pos)
repaint(i)
}
// Refresh causes this image to be redrawn with its configured state.
func (i *Image) Refresh() {
i.lock.Lock()
defer i.lock.Unlock()
rc, err := i.updateReader()
if err != nil {
fyne.LogError("Failed to load image", err)
return
}
if rc != nil {
rcMem := rc
defer rcMem.Close()
}
if i.File != "" || i.Resource != nil || i.Image != nil {
r, err := i.updateAspectAndMinSize(rc)
if err != nil {
fyne.LogError("Failed to load image", err)
return
}
rc = io.NopCloser(r)
}
if i.File != "" || i.Resource != nil {
size := i.Size()
width := size.Width
height := size.Height
if width == 0 || height == 0 {
return
}
if i.isSVG {
tex, err := i.renderSVG(width, height)
if err != nil {
fyne.LogError("Failed to render SVG", err)
return
}
i.Image = tex
} else {
if rc == nil {
return
}
img, _, err := image.Decode(rc)
if err != nil {
fyne.LogError("Failed to render image", err)
return
}
i.Image = img
}
}
Refresh(i)
}
// Resize on an image will scale the content or reposition it according to FillMode.
// It will normally cause a Refresh to ensure the pixels are recalculated.
func (i *Image) Resize(s fyne.Size) {
if s == i.Size() {
return
}
i.baseObject.Resize(s)
if i.FillMode == ImageFillOriginal && i.size.Height > 2 { // we can just ask for a GPU redraw to align
Refresh(i)
return
}
i.baseObject.Resize(s)
if i.isSVG || i.Image == nil {
i.Refresh() // we need to rasterise at the new size
} else {
Refresh(i) // just re-size using GPU scaling
}
}
// 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 NewImageFromFile(uri.Path())
}
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 := io.ReadAll(read)
if err != nil {
fyne.LogError("Unable to read image data", err)
return nil
}
res := &fyne.StaticResource{
StaticName: name,
StaticContent: data,
}
return NewImageFromResource(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}
}
func (i *Image) name() string {
if i.Resource != nil {
return i.Resource.Name()
} else if i.File != "" {
return i.File
}
return ""
}
func (i *Image) updateReader() (io.ReadCloser, error) {
i.isSVG = false
if i.Resource != nil {
i.isSVG = svg.IsResourceSVG(i.Resource)
return io.NopCloser(bytes.NewReader(i.Resource.Content())), nil
} else if i.File != "" {
var err error
fd, err := os.Open(i.File)
if err != nil {
return nil, err
}
i.isSVG = svg.IsFileSVG(i.File)
return fd, nil
}
return nil, nil
}
func (i *Image) updateAspectAndMinSize(reader io.Reader) (io.Reader, error) {
var pixWidth, pixHeight int
if reader != nil {
r, width, height, aspect, err := i.imageDetailsFromReader(reader)
if err != nil {
return nil, err
}
reader = r
i.aspect = aspect
pixWidth, pixHeight = width, height
} else if i.Image != nil {
original := i.Image.Bounds().Size()
i.aspect = float32(original.X) / float32(original.Y)
pixWidth, pixHeight = original.X, original.Y
} else {
return nil, errors.New("no matching image source")
}
if i.FillMode == ImageFillOriginal {
i.SetMinSize(scale.ToFyneSize(i, pixWidth, pixHeight))
}
return reader, nil
}
func (i *Image) imageDetailsFromReader(source io.Reader) (reader io.Reader, width, height int, aspect float32, err error) {
if source == nil {
return nil, 0, 0, 0, errors.New("no matching reading reader")
}
if i.isSVG {
var err error
i.icon, err = svg.NewDecoder(source)
if err != nil {
return nil, 0, 0, 0, err
}
config := i.icon.Config()
width, height = config.Width, config.Height
aspect = config.Aspect
} else {
var buf bytes.Buffer
tee := io.TeeReader(source, &buf)
reader = io.MultiReader(&buf, source)
config, _, err := image.DecodeConfig(tee)
if err != nil {
return nil, 0, 0, 0, err
}
width, height = config.Width, config.Height
aspect = float32(width) / float32(height)
}
return
}
func (i *Image) renderSVG(width, height float32) (image.Image, error) {
c := fyne.CurrentApp().Driver().CanvasForObject(i)
screenWidth, screenHeight := int(width), int(height)
if c != nil {
// We want real output pixel count not just the screen coordinate space (i.e. macOS Retina)
screenWidth, screenHeight = c.PixelCoordinateForPosition(fyne.Position{X: width, Y: height})
}
tex := cache.GetSvg(i.name(), screenWidth, screenHeight)
if tex != nil {
return tex, nil
}
var err error
tex, err = i.icon.Draw(screenWidth, screenHeight)
if err != nil {
return nil, err
}
cache.SetSvg(i.name(), tex, screenWidth, screenHeight)
return tex, nil
}

102
vendor/fyne.io/fyne/v2/canvas/line.go generated vendored Normal file

@ -0,0 +1,102 @@
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 bottom-right 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, then 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))
repaint(l)
}
// 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
repaint(l)
}
// Refresh causes this line to be redrawn with its configured 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,
}
}

196
vendor/fyne.io/fyne/v2/canvas/raster.go generated vendored Normal file

@ -0,0 +1,196 @@
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
}
// Hide will set this raster to not be visible
func (r *Raster) Hide() {
r.baseObject.Hide()
repaint(r)
}
// Move the raster to a new position, relative to its parent / canvas
func (r *Raster) Move(pos fyne.Position) {
r.baseObject.Move(pos)
repaint(r)
}
// 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 raster to be redrawn with its configured 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
},
}
}

64
vendor/fyne.io/fyne/v2/canvas/rectangle.go generated vendored Normal file

@ -0,0 +1,64 @@
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
// The radius of the rectangle corners
//
// Since: 2.4
CornerRadius float32
}
// Hide will set this rectangle to not be visible
func (r *Rectangle) Hide() {
r.baseObject.Hide()
repaint(r)
}
// Move the rectangle to a new position, relative to its parent / canvas
func (r *Rectangle) Move(pos fyne.Position) {
r.baseObject.Move(pos)
repaint(r)
}
// Refresh causes this rectangle to be redrawn with its configured 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,
}
}

76
vendor/fyne.io/fyne/v2/canvas/text.go generated vendored Normal file

@ -0,0 +1,76 @@
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
}
// Hide will set this text to not be visible
func (t *Text) Hide() {
t.baseObject.Hide()
repaint(t)
}
// 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)
}
// Move the text to a new position, relative to its parent / canvas
func (t *Text) Move(pos fyne.Position) {
t.baseObject.Move(pos)
repaint(t)
}
// Resize on a text updates the new size of this object, which may not result in a visual change, depending on alignment.
func (t *Text) Resize(s fyne.Size) {
if s == t.Size() {
return
}
t.baseObject.Resize(s)
Refresh(t)
}
// 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(fyne.Size) {
// no-op
}
// Refresh causes this text to be redrawn with its configured state.
func (t *Text) Refresh() {
Refresh(t)
}
// NewText returns a new Text implementation
func NewText(text string, color color.Color) *Text {
size := float32(0)
if fyne.CurrentApp() != nil { // nil app possible if app not started
size = fyne.CurrentApp().Settings().Theme().Size("text") // manually name the size to avoid import loop
}
return &Text{
Color: color,
Text: text,
TextSize: size,
}
}

107
vendor/fyne.io/fyne/v2/canvasobject.go generated vendored Normal file

@ -0,0 +1,107 @@
package fyne
// CanvasObject describes any graphical object that can be added to a canvas.
// Objects have a size and position that can be controlled through this API.
// MinSize is used to determine the minimum size which this object should be displayed.
// An object will be visible by default but can be hidden with Hide() and re-shown with Show().
//
// Note: If this object is controlled as part of a Layout you should not call
// Resize(Size) or Move(Position).
type CanvasObject interface {
// geometry
// MinSize returns the minimum size this object needs to be drawn.
MinSize() Size
// Move moves this object to the given position relative to its parent.
// This should only be called if your object is not in a container with a layout manager.
Move(Position)
// Position returns the current position of the object relative to its parent.
Position() Position
// Resize resizes this object to the given size.
// This should only be called if your object is not in a container with a layout manager.
Resize(Size)
// Size returns the current size of this object.
Size() Size
// visibility
// Hide hides this object.
Hide()
// Visible returns whether this object is visible or not.
Visible() bool
// Show shows this object.
Show()
// Refresh must be called if this object should be redrawn because its inner state changed.
Refresh()
}
// Disableable describes any CanvasObject that can be disabled.
// This is primarily used with objects that also implement the Tappable interface.
type Disableable interface {
Enable()
Disable()
Disabled() bool
}
// DoubleTappable describes any CanvasObject that can also be double tapped.
type DoubleTappable interface {
DoubleTapped(*PointEvent)
}
// Draggable indicates that a CanvasObject can be dragged.
// This is used for any item that the user has indicated should be moved across the screen.
type Draggable interface {
Dragged(*DragEvent)
DragEnd()
}
// Focusable describes any CanvasObject that can respond to being focused.
// It will receive the FocusGained and FocusLost events appropriately.
// When focused it will also have TypedRune called as text is input and
// TypedKey called when other keys are pressed.
//
// Note: You must not change canvas state (including overlays or focus) in FocusGained or FocusLost
// or you would end up with a dead-lock.
type Focusable interface {
// FocusGained is a hook called by the focus handling logic after this object gained the focus.
FocusGained()
// FocusLost is a hook called by the focus handling logic after this object lost the focus.
FocusLost()
// TypedRune is a hook called by the input handling logic on text input events if this object is focused.
TypedRune(rune)
// TypedKey is a hook called by the input handling logic on key events if this object is focused.
TypedKey(*KeyEvent)
}
// Scrollable describes any CanvasObject that can also be scrolled.
// This is mostly used to implement the widget.ScrollContainer.
type Scrollable interface {
Scrolled(*ScrollEvent)
}
// SecondaryTappable describes a CanvasObject that can be right-clicked or long-tapped.
type SecondaryTappable interface {
TappedSecondary(*PointEvent)
}
// Shortcutable describes any CanvasObject that can respond to shortcut commands (quit, cut, copy, and paste).
type Shortcutable interface {
TypedShortcut(Shortcut)
}
// Tabbable describes any object that needs to accept the Tab key presses.
//
// Since: 2.1
type Tabbable interface {
// AcceptsTab() is a hook called by the key press handling logic.
// If it returns true then the Tab key events will be sent using TypedKey.
AcceptsTab() bool
}
// Tappable describes any CanvasObject that can also be tapped.
// This should be implemented by buttons etc that wish to handle pointer interactions.
type Tappable interface {
Tapped(*PointEvent)
}

9
vendor/fyne.io/fyne/v2/clipboard.go generated vendored Normal file

@ -0,0 +1,9 @@
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)
}

39
vendor/fyne.io/fyne/v2/cloud.go generated vendored Normal file

@ -0,0 +1,39 @@
package fyne
// CloudProvider specifies the identifying information of a cloud provider.
// This information is mostly used by the `fyne.io/cloud ShowSettings' user flow.
//
// Since: 2.3
type CloudProvider interface {
// ProviderDescription returns a more detailed description of this cloud provider.
ProviderDescription() string
// ProviderIcon returns an icon resource that is associated with the given cloud service.
ProviderIcon() Resource
// ProviderName returns the name of this cloud provider, usually the name of the service it uses.
ProviderName() string
// Cleanup is called when this provider is no longer used and should be disposed.
// This is guaranteed to execute before a new provider is `Setup`
Cleanup(App)
// Setup is called when this provider is being used for the first time.
// Returning an error will exit the cloud setup process, though it can be retried.
Setup(App) error
}
// CloudProviderPreferences interface defines the functionality that a cloud provider will include if it is capable
// of synchronizing user preferences.
//
// Since: 2.3
type CloudProviderPreferences interface {
// CloudPreferences returns a preference provider that will sync values to the cloud this provider uses.
CloudPreferences(App) Preferences
}
// CloudProviderStorage interface defines the functionality that a cloud provider will include if it is capable
// of synchronizing user documents.
//
// Since: 2.3
type CloudProviderStorage interface {
// CloudStorage returns a storage provider that will sync documents to the cloud this provider uses.
CloudStorage(App) Storage
}

211
vendor/fyne.io/fyne/v2/container.go generated vendored Normal file

@ -0,0 +1,211 @@
package fyne
import "sync"
// 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
lock sync.Mutex
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.lock.Lock()
defer c.lock.Unlock()
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
repaint(c)
}
// 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
repaint(c)
}
// 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) {
c.lock.Lock()
defer c.lock.Unlock()
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)
}
// repaint instructs the containing canvas to redraw, even if nothing changed.
// This method is a duplicate of what is in `canvas/canvas.go` to avoid a dependency loop or public API.
func repaint(obj *Container) {
if CurrentApp() == nil || CurrentApp().Driver() == nil {
return
}
c := CurrentApp().Driver().CanvasForObject(obj)
if c != nil {
if paint, ok := c.(interface{ SetDirty() }); ok {
paint.SetDirty()
}
}
}

463
vendor/fyne.io/fyne/v2/container/apptabs.go generated vendored Normal file

@ -0,0 +1,463 @@
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()),
},
appTabs: t,
}
r.action = r.buildOverflowTabsButton()
r.tabs = t
// 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
}
// DisableIndex disables the TabItem at the specified index.
//
// Since: 2.3
func (t *AppTabs) DisableIndex(i int) {
disableIndex(t, i)
}
// DisableItem disables the specified TabItem.
//
// Since: 2.3
func (t *AppTabs) DisableItem(item *TabItem) {
disableItem(t, item)
}
// EnableIndex enables the TabItem at the specified index.
//
// Since: 2.3
func (t *AppTabs) EnableIndex(i int) {
enableIndex(t, i)
}
// EnableItem enables the specified TabItem.
//
// Since: 2.3
func (t *AppTabs) EnableItem(item *TabItem) {
enableItem(t, item)
}
// 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)
}
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
if len(r.appTabs.Items) == 0 {
r.updateTabs(0)
} else {
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]
if item.button == nil {
item.button = &tabButton{
onTapped: func() { r.appTabs.Select(item) },
}
}
button := 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.NewStackLayout()
} else {
tabCount = max
r.action.Show()
// Set layout of tab bar containing tab buttons and overflow action
if r.appTabs.location == TabLocationLeading || r.appTabs.location == TabLocationTrailing {
r.bar.Layout = layout.NewBorderLayout(nil, r.action, nil, nil)
} else {
r.bar.Layout = layout.NewBorderLayout(nil, nil, nil, r.action)
}
}
buttons := r.buildTabButtons(tabCount)
r.bar.Objects = []fyne.CanvasObject{buttons}
if a := r.action; a != nil {
r.bar.Objects = append(r.bar.Objects, a)
}
r.bar.Refresh()
}

20
vendor/fyne.io/fyne/v2/container/container.go generated vendored Normal file

@ -0,0 +1,20 @@
// 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.Container{Layout: layout, Objects: 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.Container{Objects: objects}
}

498
vendor/fyne.io/fyne/v2/container/doctabs.go generated vendored Normal file

@ -0,0 +1,498 @@
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()),
},
docTabs: t,
scroller: NewScroll(&fyne.Container{}),
}
r.action = r.buildAllTabsButton()
r.create = r.buildCreateTabsButton()
r.tabs = t
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
}
// DisableIndex disables the TabItem at the specified index.
//
// Since: 2.3
func (t *DocTabs) DisableIndex(i int) {
disableIndex(t, i)
}
// DisableItem disables the specified TabItem.
//
// Since: 2.3
func (t *DocTabs) DisableItem(item *TabItem) {
disableItem(t, item)
}
// EnableIndex enables the TabItem at the specified index.
//
// Since: 2.3
func (t *DocTabs) EnableIndex(i int) {
enableIndex(t, i)
}
// EnableItem enables the specified TabItem.
//
// Since: 2.3
func (t *DocTabs) EnableItem(item *TabItem) {
enableItem(t, item)
}
// 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)
}
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)
// lay out buttons before updating indicator, which is relative to their position
buttons := r.scroller.Content.(*fyne.Container)
buttons.Layout.Layout(buttons.Objects, buttons.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]
if item.button == nil {
item.button = &tabButton{
onTapped: func() { r.docTabs.Select(item) },
onClosed: func() { r.docTabs.close(item) },
}
}
button := 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)
// https://github.com/fyne-io/fyne/issues/3909
// very dirty temporary fix to this crash!
if r.docTabs.current < 0 || r.docTabs.current >= len(buttons.Objects) {
return
}
button := buttons.Objects[r.docTabs.current]
pos := button.Position()
size := button.Size()
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.moveIndicator(fyne.NewPos(0, 0), fyne.NewSize(0, 0), animate)
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()
minSize := a.MinSize()
if minSize.Width > selectedSize.Width {
selectedSize = minSize
}
}
} else {
selected := buttons[r.docTabs.current]
selectedPos = selected.Position()
selectedSize = selected.Size()
minSize := selected.MinSize()
if minSize.Width > selectedSize.Width {
selectedSize = minSize
}
}
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()
}

121
vendor/fyne.io/fyne/v2/container/layouts.go generated vendored Normal file

@ -0,0 +1,121 @@
package container // import "fyne.io/fyne/v2/container"
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/internal"
"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)
}
if len(objects) == 1 && objects[0] == nil {
internal.LogHint("Border layout requires only 4 parameters, optional items cannot be nil")
all = all[1:]
}
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 and always displayed
// at their horizontal MinSize. Use a different layout if the objects are intended
// to be larger then their horizontal MinSize.
//
// 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
//
// Deprecated: Use container.NewStack() instead.
func NewMax(objects ...fyne.CanvasObject) *fyne.Container {
return NewStack(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...)
}
// NewStack returns a new container that stacks objects on top of each other.
// Objects at the end of the container will be stacked on top of objects before.
// Having only a single object has no impact as CanvasObjects will
// fill the available space even without a Stack.
//
// Since: 2.4
func NewStack(objects ...fyne.CanvasObject) *fyne.Container {
return New(layout.NewStackLayout(), 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 and always displayed
// at their vertical MinSize. Use a different layout if the objects are intended
// to be larger then their vertical MinSize.
//
// Since: 1.4
func NewVBox(objects ...fyne.CanvasObject) *fyne.Container {
return New(layout.NewVBoxLayout(), objects...)
}

55
vendor/fyne.io/fyne/v2/container/scroll.go generated vendored Normal file

@ -0,0 +1,55 @@
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)
}

369
vendor/fyne.io/fyne/v2/container/split.go generated vendored Normal file

@ -0,0 +1,369 @@
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.minLeadingWidth(), r.minTrailingWidth())
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.minLeadingHeight(), r.minTrailingHeight())
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)
}
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)
}
func (r *splitContainerRenderer) minLeadingWidth() float32 {
if r.split.Leading.Visible() {
return r.split.Leading.MinSize().Width
}
return 0
}
func (r *splitContainerRenderer) minLeadingHeight() float32 {
if r.split.Leading.Visible() {
return r.split.Leading.MinSize().Height
}
return 0
}
func (r *splitContainerRenderer) minTrailingWidth() float32 {
if r.split.Trailing.Visible() {
return r.split.Trailing.MinSize().Width
}
return 0
}
func (r *splitContainerRenderer) minTrailingHeight() float32 {
if r.split.Trailing.Visible() {
return r.split.Trailing.MinSize().Height
}
return 0
}
// 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
startDragOff *fyne.Position
currentDragPos fyne.Position
}
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() {
d.startDragOff = nil
}
func (d *divider) Dragged(e *fyne.DragEvent) {
if d.startDragOff == nil {
d.currentDragPos = d.Position().Add(e.Position)
start := e.Position.Subtract(e.Dragged)
d.startDragOff = &start
} else {
d.currentDragPos = d.currentDragPos.Add(e.Dragged)
}
x, y := d.currentDragPos.Components()
var offset, leadingRatio, trailingRatio float64
if d.split.Horizontal {
widthFree := float64(d.split.Size().Width - dividerThickness())
leadingRatio = float64(d.split.Leading.MinSize().Width) / widthFree
trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Width) / widthFree)
offset = float64(x-d.startDragOff.X) / widthFree
} else {
heightFree := float64(d.split.Size().Height - dividerThickness())
leadingRatio = float64(d.split.Leading.MinSize().Height) / heightFree
trailingRatio = 1. - (float64(d.split.Trailing.MinSize().Height) / heightFree)
offset = float64(y-d.startDragOff.Y) / heightFree
}
if offset < leadingRatio {
offset = leadingRatio
}
if offset > trailingRatio {
offset = trailingRatio
}
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
}

849
vendor/fyne.io/fyne/v2/container/tabs.go generated vendored Normal file

@ -0,0 +1,849 @@
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
button *tabButton
}
// Disabled returns whether or not the TabItem is disabled.
//
// Since: 2.3
func (ti *TabItem) Disabled() bool {
if ti.button != nil {
return ti.button.Disabled()
}
return false
}
func (ti *TabItem) disable() {
if ti.button != nil {
ti.button.Disable()
}
}
func (ti *TabItem) enable() {
if ti.button != nil {
ti.button.Enable()
}
}
// 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 internal.HintsEnabled && 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)
}
}
func disableIndex(t baseTabs, index int) {
items := t.items()
if index < 0 || index >= len(items) {
return
}
item := items[index]
item.disable()
if selected(t) == item {
// the disabled tab is currently selected, so select the first enabled tab
for i, it := range items {
if !it.Disabled() {
selectIndex(t, i)
break
}
}
}
if selected(t) == item {
selectIndex(t, -1) // no other tab is able to be selected
}
}
func disableItem(t baseTabs, item *TabItem) {
for i, it := range t.items() {
if it == item {
disableIndex(t, i)
return
}
}
}
func enableIndex(t baseTabs, index int) {
items := t.items()
if index < 0 || index >= len(items) {
return
}
item := items[index]
item.enable()
}
func enableItem(t baseTabs, item *TabItem) {
for i, it := range t.items() {
if it == item {
enableIndex(t, i)
return
}
}
}
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
tabs baseTabs
}
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()
r.indicator.CornerRadius = theme.SelectionRadiusSize()
for _, tab := range r.tabs.items() {
tab.Content.Refresh()
}
}
func (r *baseTabsRenderer) layout(t baseTabs, size fyne.Size) {
var (
barPos, dividerPos, contentPos fyne.Position
barSize, dividerSize, contentSize fyne.Size
)
barMin := r.bar.MinSize()
padding := theme.Padding()
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, padding)
contentPos = fyne.NewPos(0, barHeight+padding)
contentSize = fyne.NewSize(size.Width, size.Height-barHeight-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(padding, size.Height)
contentPos = fyne.NewPos(barWidth+theme.Padding(), 0)
contentSize = fyne.NewSize(size.Width-barWidth-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-padding)
dividerSize = fyne.NewSize(size.Width, padding)
contentPos = fyne.NewPos(0, 0)
contentSize = fyne.NewSize(size.Width, size.Height-barHeight-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-padding, 0)
dividerSize = fyne.NewSize(padding, size.Height)
contentPos = fyne.NewPos(0, 0)
contentSize = fyne.NewSize(size.Width-barWidth-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 {
pad := theme.Padding()
buttonPad := pad
barMin := r.bar.MinSize()
tabsMin := r.bar.Objects[0].MinSize()
accessory := r.bar.Objects[1]
accessoryMin := accessory.MinSize()
if scroll, ok := r.bar.Objects[0].(*Scroll); ok && len(scroll.Content.(*fyne.Container).Objects) == 0 {
tabsMin = fyne.Size{} // scroller forces 32 where we don't need any space
buttonPad = 0
} else if group, ok := r.bar.Objects[0].(*fyne.Container); ok && len(group.Objects) > 0 {
tabsMin = group.Objects[0].MinSize()
buttonPad = 0
}
if !accessory.Visible() || accessoryMin.Width == 0 {
buttonPad = 0
accessoryMin = fyne.Size{}
}
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+pad,
fyne.Max(contentMin.Height, accessoryMin.Height+buttonPad+tabsMin.Height))
default:
return fyne.NewSize(fyne.Max(contentMin.Width, accessoryMin.Width+buttonPad+tabsMin.Width),
barMin.Height+contentMin.Height+pad)
}
}
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 && fyne.CurrentApp().Settings().ShowAnimations() {
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.DisableableWidget
hovered bool
icon fyne.Resource
iconPosition buttonIconPosition
importance widget.Importance
onTapped func()
onClosed func()
text string
textAlignment fyne.TextAlign
}
func (b *tabButton) CreateRenderer() fyne.WidgetRenderer {
b.ExtendBaseWidget(b)
background := canvas.NewRectangle(theme.HoverColor())
background.CornerRadius = theme.SelectionRadiusSize()
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) {
if b.Disabled() {
return
}
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() {
iconSize := r.iconSize()
var iconOffset fyne.Position
if r.button.iconPosition == buttonIconTop {
iconOffset = fyne.NewPos((innerSize.Width-iconSize)/2, 0)
} else {
iconOffset = fyne.NewPos(0, (innerSize.Height-iconSize)/2)
}
r.icon.Resize(fyne.NewSquareSize(iconSize))
r.icon.Move(innerOffset.Add(iconOffset))
labelShift = 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))
}
inlineIconSize := theme.IconInlineSize()
r.close.Move(fyne.NewPos(size.Width-inlineIconSize-theme.Padding(), (size.Height-inlineIconSize)/2))
r.close.Resize(fyne.NewSquareSize(inlineIconSize))
}
func (r *tabButtonRenderer) MinSize() fyne.Size {
var contentWidth, contentHeight float32
textSize := r.label.MinSize()
iconSize := r.iconSize()
padding := theme.Padding()
if r.button.iconPosition == buttonIconTop {
contentWidth = fyne.Max(textSize.Width, iconSize)
if r.icon.Visible() {
contentHeight += iconSize
}
if r.label.Text != "" {
if r.icon.Visible() {
contentHeight += padding
}
contentHeight += textSize.Height
}
} else {
contentHeight = fyne.Max(textSize.Height, iconSize)
if r.icon.Visible() {
contentWidth += iconSize
}
if r.label.Text != "" {
if r.icon.Visible() {
contentWidth += padding
}
contentWidth += textSize.Width
}
}
if r.button.onClosed != nil {
inlineIconSize := theme.IconInlineSize()
contentWidth += inlineIconSize + padding
contentHeight = fyne.Max(contentHeight, inlineIconSize)
}
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.button.Disabled() {
r.background.FillColor = theme.HoverColor()
r.background.CornerRadius = theme.SelectionRadiusSize()
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.Disabled() {
if r.button.importance == widget.HighImportance {
r.label.Color = theme.PrimaryColor()
} else {
r.label.Color = theme.ForegroundColor()
}
} else {
r.label.Color = theme.DisabledColor()
}
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 {
if r.button.iconPosition == buttonIconTop {
return 2 * theme.IconInlineSize()
}
return theme.IconInlineSize()
}
func (r *tabButtonRenderer) padding() fyne.Size {
padding := theme.InnerPadding()
if r.label.Text != "" && r.button.iconPosition == buttonIconInline {
return fyne.NewSquareSize(padding * 2)
}
return fyne.NewSize(padding, padding*2)
}
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.CornerRadius = theme.SelectionRadiusSize()
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.NewSquareSize(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.CornerRadius = theme.SelectionRadiusSize()
r.background.Show()
} else {
r.background.Hide()
}
r.background.Refresh()
switch res := r.icon.Resource.(type) {
case *theme.ThemedResource:
if r.button.parent.importance == widget.HighImportance {
r.icon.Resource = theme.NewPrimaryThemedResource(res)
}
case *theme.PrimaryThemedResource:
if r.button.parent.importance != widget.HighImportance {
r.icon.Resource = res.Original()
}
}
r.icon.Refresh()
}
func mismatchedTabItems(items []*TabItem) bool {
var hasText, hasIcon bool
for _, tab := range items {
hasText = hasText || tab.Text != ""
hasIcon = hasIcon || tab.Icon != nil
}
mismatch := false
for _, tab := range items {
if (hasText && tab.Text == "") || (hasIcon && tab.Icon == nil) {
mismatch = true
break
}
}
return mismatch
}
func moreIcon(t baseTabs) fyne.Resource {
if l := t.tabLocation(); l == TabLocationLeading || l == TabLocationTrailing {
return theme.MoreVerticalIcon()
}
return theme.MoreHorizontalIcon()
}

178
vendor/fyne.io/fyne/v2/data/binding/binding.go generated vendored Normal file

@ -0,0 +1,178 @@
//go:generate go run gen.go
// Package binding provides support for binding data to widgets.
package binding
import (
"errors"
"reflect"
"sync"
"fyne.io/fyne/v2"
)
var (
errKeyNotFound = errors.New("key not found")
errOutOfBounds = errors.New("index out of bounds")
errParseFailed = errors.New("format did not match 1 value")
// As an optimisation we connect any listeners asking for the same key, so that there is only 1 per preference item.
prefBinds = newPreferencesMap()
)
// DataItem is the base interface for all bindable data items.
//
// Since: 2.0
type DataItem interface {
// AddListener attaches a new change listener to this DataItem.
// Listeners are called each time the data inside this DataItem changes.
// Additionally the listener will be triggered upon successful connection to get the current value.
AddListener(DataListener)
// RemoveListener will detach the specified change listener from the DataItem.
// Disconnected listener will no longer be triggered when changes occur.
RemoveListener(DataListener)
}
// DataListener is any object that can register for changes in a bindable DataItem.
// See NewDataListener to define a new listener using just an inline function.
//
// Since: 2.0
type DataListener interface {
DataChanged()
}
// NewDataListener is a helper function that creates a new listener type from a simple callback function.
//
// Since: 2.0
func NewDataListener(fn func()) DataListener {
return &listener{fn}
}
type listener struct {
callback func()
}
func (l *listener) DataChanged() {
l.callback()
}
type base struct {
listeners sync.Map // map[DataListener]bool
lock sync.RWMutex
}
// AddListener allows a data listener to be informed of changes to this item.
func (b *base) AddListener(l DataListener) {
b.listeners.Store(l, true)
queueItem(l.DataChanged)
}
// RemoveListener should be called if the listener is no longer interested in being informed of data change events.
func (b *base) RemoveListener(l DataListener) {
b.listeners.Delete(l)
}
func (b *base) trigger() {
b.listeners.Range(func(key, _ interface{}) bool {
queueItem(key.(DataListener).DataChanged)
return true
})
}
// Untyped supports binding a interface{} value.
//
// Since: 2.1
type Untyped interface {
DataItem
Get() (interface{}, error)
Set(interface{}) error
}
// NewUntyped returns a bindable interface{} value that is managed internally.
//
// Since: 2.1
func NewUntyped() Untyped {
var blank interface{} = nil
v := &blank
return &boundUntyped{val: reflect.ValueOf(v).Elem()}
}
type boundUntyped struct {
base
val reflect.Value
}
func (b *boundUntyped) Get() (interface{}, error) {
b.lock.RLock()
defer b.lock.RUnlock()
return b.val.Interface(), nil
}
func (b *boundUntyped) Set(val interface{}) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.val.Interface() == val {
return nil
}
b.val.Set(reflect.ValueOf(val))
b.trigger()
return nil
}
// ExternalUntyped supports binding a interface{} value to an external value.
//
// Since: 2.1
type ExternalUntyped interface {
Untyped
Reload() error
}
// BindUntyped returns a bindable interface{} value that is bound to an external type.
// The parameter must be a pointer to the type you wish to bind.
//
// Since: 2.1
func BindUntyped(v interface{}) ExternalUntyped {
t := reflect.TypeOf(v)
if t.Kind() != reflect.Ptr {
fyne.LogError("Invalid type passed to BindUntyped, must be a pointer", nil)
v = nil
}
if v == nil {
var blank interface{}
v = &blank // never allow a nil value pointer
}
b := &boundExternalUntyped{}
b.val = reflect.ValueOf(v).Elem()
b.old = b.val.Interface()
return b
}
type boundExternalUntyped struct {
boundUntyped
old interface{}
}
func (b *boundExternalUntyped) Set(val interface{}) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.old == val {
return nil
}
b.val.Set(reflect.ValueOf(val))
b.old = val
b.trigger()
return nil
}
func (b *boundExternalUntyped) Reload() error {
return b.Set(b.val.Interface())
}

647
vendor/fyne.io/fyne/v2/data/binding/binditems.go generated vendored Normal file

@ -0,0 +1,647 @@
// auto-generated
// **** THIS FILE IS AUTO-GENERATED, PLEASE DO NOT EDIT IT **** //
package binding
import (
"bytes"
"fyne.io/fyne/v2"
)
// Bool supports binding a bool value.
//
// Since: 2.0
type Bool interface {
DataItem
Get() (bool, error)
Set(bool) error
}
// ExternalBool supports binding a bool value to an external value.
//
// Since: 2.0
type ExternalBool interface {
Bool
Reload() error
}
// NewBool returns a bindable bool value that is managed internally.
//
// Since: 2.0
func NewBool() Bool {
var blank bool = false
return &boundBool{val: &blank}
}
// BindBool returns a new bindable value that controls the contents of the provided bool variable.
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
//
// Since: 2.0
func BindBool(v *bool) ExternalBool {
if v == nil {
var blank bool = false
v = &blank // never allow a nil value pointer
}
b := &boundExternalBool{}
b.val = v
b.old = *v
return b
}
type boundBool struct {
base
val *bool
}
func (b *boundBool) Get() (bool, error) {
b.lock.RLock()
defer b.lock.RUnlock()
if b.val == nil {
return false, nil
}
return *b.val, nil
}
func (b *boundBool) Set(val bool) error {
b.lock.Lock()
defer b.lock.Unlock()
if *b.val == val {
return nil
}
*b.val = val
b.trigger()
return nil
}
type boundExternalBool struct {
boundBool
old bool
}
func (b *boundExternalBool) Set(val bool) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.old == val {
return nil
}
*b.val = val
b.old = val
b.trigger()
return nil
}
func (b *boundExternalBool) Reload() error {
return b.Set(*b.val)
}
// Bytes supports binding a []byte value.
//
// Since: 2.2
type Bytes interface {
DataItem
Get() ([]byte, error)
Set([]byte) error
}
// ExternalBytes supports binding a []byte value to an external value.
//
// Since: 2.2
type ExternalBytes interface {
Bytes
Reload() error
}
// NewBytes returns a bindable []byte value that is managed internally.
//
// Since: 2.2
func NewBytes() Bytes {
var blank []byte = nil
return &boundBytes{val: &blank}
}
// BindBytes returns a new bindable value that controls the contents of the provided []byte variable.
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
//
// Since: 2.2
func BindBytes(v *[]byte) ExternalBytes {
if v == nil {
var blank []byte = nil
v = &blank // never allow a nil value pointer
}
b := &boundExternalBytes{}
b.val = v
b.old = *v
return b
}
type boundBytes struct {
base
val *[]byte
}
func (b *boundBytes) Get() ([]byte, error) {
b.lock.RLock()
defer b.lock.RUnlock()
if b.val == nil {
return nil, nil
}
return *b.val, nil
}
func (b *boundBytes) Set(val []byte) error {
b.lock.Lock()
defer b.lock.Unlock()
if bytes.Equal(*b.val, val) {
return nil
}
*b.val = val
b.trigger()
return nil
}
type boundExternalBytes struct {
boundBytes
old []byte
}
func (b *boundExternalBytes) Set(val []byte) error {
b.lock.Lock()
defer b.lock.Unlock()
if bytes.Equal(b.old, val) {
return nil
}
*b.val = val
b.old = val
b.trigger()
return nil
}
func (b *boundExternalBytes) Reload() error {
return b.Set(*b.val)
}
// Float supports binding a float64 value.
//
// Since: 2.0
type Float interface {
DataItem
Get() (float64, error)
Set(float64) error
}
// ExternalFloat supports binding a float64 value to an external value.
//
// Since: 2.0
type ExternalFloat interface {
Float
Reload() error
}
// NewFloat returns a bindable float64 value that is managed internally.
//
// Since: 2.0
func NewFloat() Float {
var blank float64 = 0.0
return &boundFloat{val: &blank}
}
// BindFloat returns a new bindable value that controls the contents of the provided float64 variable.
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
//
// Since: 2.0
func BindFloat(v *float64) ExternalFloat {
if v == nil {
var blank float64 = 0.0
v = &blank // never allow a nil value pointer
}
b := &boundExternalFloat{}
b.val = v
b.old = *v
return b
}
type boundFloat struct {
base
val *float64
}
func (b *boundFloat) Get() (float64, error) {
b.lock.RLock()
defer b.lock.RUnlock()
if b.val == nil {
return 0.0, nil
}
return *b.val, nil
}
func (b *boundFloat) Set(val float64) error {
b.lock.Lock()
defer b.lock.Unlock()
if *b.val == val {
return nil
}
*b.val = val
b.trigger()
return nil
}
type boundExternalFloat struct {
boundFloat
old float64
}
func (b *boundExternalFloat) Set(val float64) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.old == val {
return nil
}
*b.val = val
b.old = val
b.trigger()
return nil
}
func (b *boundExternalFloat) Reload() error {
return b.Set(*b.val)
}
// Int supports binding a int value.
//
// Since: 2.0
type Int interface {
DataItem
Get() (int, error)
Set(int) error
}
// ExternalInt supports binding a int value to an external value.
//
// Since: 2.0
type ExternalInt interface {
Int
Reload() error
}
// NewInt returns a bindable int value that is managed internally.
//
// Since: 2.0
func NewInt() Int {
var blank int = 0
return &boundInt{val: &blank}
}
// BindInt returns a new bindable value that controls the contents of the provided int variable.
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
//
// Since: 2.0
func BindInt(v *int) ExternalInt {
if v == nil {
var blank int = 0
v = &blank // never allow a nil value pointer
}
b := &boundExternalInt{}
b.val = v
b.old = *v
return b
}
type boundInt struct {
base
val *int
}
func (b *boundInt) Get() (int, error) {
b.lock.RLock()
defer b.lock.RUnlock()
if b.val == nil {
return 0, nil
}
return *b.val, nil
}
func (b *boundInt) Set(val int) error {
b.lock.Lock()
defer b.lock.Unlock()
if *b.val == val {
return nil
}
*b.val = val
b.trigger()
return nil
}
type boundExternalInt struct {
boundInt
old int
}
func (b *boundExternalInt) Set(val int) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.old == val {
return nil
}
*b.val = val
b.old = val
b.trigger()
return nil
}
func (b *boundExternalInt) Reload() error {
return b.Set(*b.val)
}
// Rune supports binding a rune value.
//
// Since: 2.0
type Rune interface {
DataItem
Get() (rune, error)
Set(rune) error
}
// ExternalRune supports binding a rune value to an external value.
//
// Since: 2.0
type ExternalRune interface {
Rune
Reload() error
}
// NewRune returns a bindable rune value that is managed internally.
//
// Since: 2.0
func NewRune() Rune {
var blank rune = rune(0)
return &boundRune{val: &blank}
}
// BindRune returns a new bindable value that controls the contents of the provided rune variable.
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
//
// Since: 2.0
func BindRune(v *rune) ExternalRune {
if v == nil {
var blank rune = rune(0)
v = &blank // never allow a nil value pointer
}
b := &boundExternalRune{}
b.val = v
b.old = *v
return b
}
type boundRune struct {
base
val *rune
}
func (b *boundRune) Get() (rune, error) {
b.lock.RLock()
defer b.lock.RUnlock()
if b.val == nil {
return rune(0), nil
}
return *b.val, nil
}
func (b *boundRune) Set(val rune) error {
b.lock.Lock()
defer b.lock.Unlock()
if *b.val == val {
return nil
}
*b.val = val
b.trigger()
return nil
}
type boundExternalRune struct {
boundRune
old rune
}
func (b *boundExternalRune) Set(val rune) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.old == val {
return nil
}
*b.val = val
b.old = val
b.trigger()
return nil
}
func (b *boundExternalRune) Reload() error {
return b.Set(*b.val)
}
// String supports binding a string value.
//
// Since: 2.0
type String interface {
DataItem
Get() (string, error)
Set(string) error
}
// ExternalString supports binding a string value to an external value.
//
// Since: 2.0
type ExternalString interface {
String
Reload() error
}
// NewString returns a bindable string value that is managed internally.
//
// Since: 2.0
func NewString() String {
var blank string = ""
return &boundString{val: &blank}
}
// BindString returns a new bindable value that controls the contents of the provided string variable.
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
//
// Since: 2.0
func BindString(v *string) ExternalString {
if v == nil {
var blank string = ""
v = &blank // never allow a nil value pointer
}
b := &boundExternalString{}
b.val = v
b.old = *v
return b
}
type boundString struct {
base
val *string
}
func (b *boundString) Get() (string, error) {
b.lock.RLock()
defer b.lock.RUnlock()
if b.val == nil {
return "", nil
}
return *b.val, nil
}
func (b *boundString) Set(val string) error {
b.lock.Lock()
defer b.lock.Unlock()
if *b.val == val {
return nil
}
*b.val = val
b.trigger()
return nil
}
type boundExternalString struct {
boundString
old string
}
func (b *boundExternalString) Set(val string) error {
b.lock.Lock()
defer b.lock.Unlock()
if b.old == val {
return nil
}
*b.val = val
b.old = val
b.trigger()
return nil
}
func (b *boundExternalString) Reload() error {
return b.Set(*b.val)
}
// URI supports binding a fyne.URI value.
//
// Since: 2.1
type URI interface {
DataItem
Get() (fyne.URI, error)
Set(fyne.URI) error
}
// ExternalURI supports binding a fyne.URI value to an external value.
//
// Since: 2.1
type ExternalURI interface {
URI
Reload() error
}
// NewURI returns a bindable fyne.URI value that is managed internally.
//
// Since: 2.1
func NewURI() URI {
var blank fyne.URI = fyne.URI(nil)
return &boundURI{val: &blank}
}
// BindURI returns a new bindable value that controls the contents of the provided fyne.URI variable.
// If your code changes the content of the variable this refers to you should call Reload() to inform the bindings.
//
// Since: 2.1
func BindURI(v *fyne.URI) ExternalURI {
if v == nil {
var blank fyne.URI = fyne.URI(nil)
v = &blank // never allow a nil value pointer
}
b := &boundExternalURI{}
b.val = v
b.old = *v
return b
}
type boundURI struct {
base
val *fyne.URI
}
func (b *boundURI) Get() (fyne.URI, error) {
b.lock.RLock()
defer b.lock.RUnlock()
if b.val == nil {
return fyne.URI(nil), nil
}
return *b.val, nil
}
func (b *boundURI) Set(val fyne.URI) error {
b.lock.Lock()
defer b.lock.Unlock()
if compareURI(*b.val, val) {
return nil
}
*b.val = val
b.trigger()
return nil
}
type boundExternalURI struct {
boundURI
old fyne.URI
}
func (b *boundExternalURI) Set(val fyne.URI) error {
b.lock.Lock()
defer b.lock.Unlock()
if compareURI(b.old, val) {
return nil
}
*b.val = val
b.old = val
b.trigger()
return nil
}
func (b *boundExternalURI) Reload() error {
return b.Set(*b.val)
}

1786
vendor/fyne.io/fyne/v2/data/binding/bindlists.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

1816
vendor/fyne.io/fyne/v2/data/binding/bindtrees.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

118
vendor/fyne.io/fyne/v2/data/binding/bool.go generated vendored Normal file

@ -0,0 +1,118 @@
package binding
type not struct {
Bool
}
var _ Bool = (*not)(nil)
// Not returns a Bool binding that invert the value of the given data binding.
// This is providing the logical Not boolean operation as a data binding.
//
// Since 2.4
func Not(data Bool) Bool {
return &not{Bool: data}
}
func (n *not) Get() (bool, error) {
v, err := n.Bool.Get()
return !v, err
}
func (n *not) Set(value bool) error {
return n.Bool.Set(!value)
}
type and struct {
booleans
}
var _ Bool = (*and)(nil)
// And returns a Bool binding that return true when all the passed Bool binding are
// true and false otherwise. It does apply a logical and boolean operation on all passed
// Bool bindings. This binding is two way. In case of a Set, it will propagate the value
// identically to all the Bool bindings used for its construction.
//
// Since 2.4
func And(data ...Bool) Bool {
return &and{booleans: booleans{data: data}}
}
func (a *and) Get() (bool, error) {
for _, d := range a.data {
v, err := d.Get()
if err != nil {
return false, err
}
if !v {
return false, nil
}
}
return true, nil
}
func (a *and) Set(value bool) error {
for _, d := range a.data {
err := d.Set(value)
if err != nil {
return err
}
}
return nil
}
type or struct {
booleans
}
var _ Bool = (*or)(nil)
// Or returns a Bool binding that return true when at least one of the passed Bool binding
// is true and false otherwise. It does apply a logical or boolean operation on all passed
// Bool bindings. This binding is two way. In case of a Set, it will propagate the value
// identically to all the Bool bindings used for its construction.
//
// Since 2.4
func Or(data ...Bool) Bool {
return &or{booleans: booleans{data: data}}
}
func (o *or) Get() (bool, error) {
for _, d := range o.data {
v, err := d.Get()
if err != nil {
return false, err
}
if v {
return true, nil
}
}
return false, nil
}
func (o *or) Set(value bool) error {
for _, d := range o.data {
err := d.Set(value)
if err != nil {
return err
}
}
return nil
}
type booleans struct {
data []Bool
}
func (g *booleans) AddListener(listener DataListener) {
for _, d := range g.data {
d.AddListener(listener)
}
}
func (g *booleans) RemoveListener(listener DataListener) {
for _, d := range g.data {
d.RemoveListener(listener)
}
}

@ -0,0 +1,13 @@
package binding
import "fyne.io/fyne/v2"
func compareURI(v1, v2 fyne.URI) bool {
if v1 == nil && v1 == v2 {
return true
}
if v1 == nil || v2 == nil {
return false
}
return v1.String() == v2.String()
}

638
vendor/fyne.io/fyne/v2/data/binding/convert.go generated vendored Normal file

@ -0,0 +1,638 @@
// auto-generated
// **** THIS FILE IS AUTO-GENERATED, PLEASE DO NOT EDIT IT **** //
package binding
import (
"fmt"
"fyne.io/fyne/v2"
)
type stringFromBool struct {
base
format string
from Bool
}
// BoolToString creates a binding that connects a Bool data item to a String.
// Changes to the Bool will be pushed to the String and setting the string will parse and set the
// Bool if the parse was successful.
//
// Since: 2.0
func BoolToString(v Bool) String {
str := &stringFromBool{from: v}
v.AddListener(str)
return str
}
// BoolToStringWithFormat creates a binding that connects a Bool data item to a String and is
// presented using the specified format. Changes to the Bool will be pushed to the String and setting
// the string will parse and set the Bool if the string matches the format and its parse was successful.
//
// Since: 2.0
func BoolToStringWithFormat(v Bool, format string) String {
if format == "%t" { // Same as not using custom formatting.
return BoolToString(v)
}
str := &stringFromBool{from: v, format: format}
v.AddListener(str)
return str
}
func (s *stringFromBool) Get() (string, error) {
val, err := s.from.Get()
if err != nil {
return "", err
}
if s.format != "" {
return fmt.Sprintf(s.format, val), nil
}
return formatBool(val), nil
}
func (s *stringFromBool) Set(str string) error {
var val bool
if s.format != "" {
safe := stripFormatPrecision(s.format)
n, err := fmt.Sscanf(str, safe+" ", &val) // " " denotes match to end of string
if err != nil {
return err
}
if n != 1 {
return errParseFailed
}
} else {
new, err := parseBool(str)
if err != nil {
return err
}
val = new
}
old, err := s.from.Get()
if err != nil {
return err
}
if val == old {
return nil
}
if err = s.from.Set(val); err != nil {
return err
}
s.DataChanged()
return nil
}
func (s *stringFromBool) DataChanged() {
s.lock.RLock()
defer s.lock.RUnlock()
s.trigger()
}
type stringFromFloat struct {
base
format string
from Float
}
// FloatToString creates a binding that connects a Float data item to a String.
// Changes to the Float will be pushed to the String and setting the string will parse and set the
// Float if the parse was successful.
//
// Since: 2.0
func FloatToString(v Float) String {
str := &stringFromFloat{from: v}
v.AddListener(str)
return str
}
// FloatToStringWithFormat creates a binding that connects a Float data item to a String and is
// presented using the specified format. Changes to the Float will be pushed to the String and setting
// the string will parse and set the Float if the string matches the format and its parse was successful.
//
// Since: 2.0
func FloatToStringWithFormat(v Float, format string) String {
if format == "%f" { // Same as not using custom formatting.
return FloatToString(v)
}
str := &stringFromFloat{from: v, format: format}
v.AddListener(str)
return str
}
func (s *stringFromFloat) Get() (string, error) {
val, err := s.from.Get()
if err != nil {
return "", err
}
if s.format != "" {
return fmt.Sprintf(s.format, val), nil
}
return formatFloat(val), nil
}
func (s *stringFromFloat) Set(str string) error {
var val float64
if s.format != "" {
safe := stripFormatPrecision(s.format)
n, err := fmt.Sscanf(str, safe+" ", &val) // " " denotes match to end of string
if err != nil {
return err
}
if n != 1 {
return errParseFailed
}
} else {
new, err := parseFloat(str)
if err != nil {
return err
}
val = new
}
old, err := s.from.Get()
if err != nil {
return err
}
if val == old {
return nil
}
if err = s.from.Set(val); err != nil {
return err
}
s.DataChanged()
return nil
}
func (s *stringFromFloat) DataChanged() {
s.lock.RLock()
defer s.lock.RUnlock()
s.trigger()
}
type stringFromInt struct {
base
format string
from Int
}
// IntToString creates a binding that connects a Int data item to a String.
// Changes to the Int will be pushed to the String and setting the string will parse and set the
// Int if the parse was successful.
//
// Since: 2.0
func IntToString(v Int) String {
str := &stringFromInt{from: v}
v.AddListener(str)
return str
}
// IntToStringWithFormat creates a binding that connects a Int data item to a String and is
// presented using the specified format. Changes to the Int will be pushed to the String and setting
// the string will parse and set the Int if the string matches the format and its parse was successful.
//
// Since: 2.0
func IntToStringWithFormat(v Int, format string) String {
if format == "%d" { // Same as not using custom formatting.
return IntToString(v)
}
str := &stringFromInt{from: v, format: format}
v.AddListener(str)
return str
}
func (s *stringFromInt) Get() (string, error) {
val, err := s.from.Get()
if err != nil {
return "", err
}
if s.format != "" {
return fmt.Sprintf(s.format, val), nil
}
return formatInt(val), nil
}
func (s *stringFromInt) Set(str string) error {
var val int
if s.format != "" {
safe := stripFormatPrecision(s.format)
n, err := fmt.Sscanf(str, safe+" ", &val) // " " denotes match to end of string
if err != nil {
return err
}
if n != 1 {
return errParseFailed
}
} else {
new, err := parseInt(str)
if err != nil {
return err
}
val = new
}
old, err := s.from.Get()
if err != nil {
return err
}
if val == old {
return nil
}
if err = s.from.Set(val); err != nil {
return err
}
s.DataChanged()
return nil
}
func (s *stringFromInt) DataChanged() {
s.lock.RLock()
defer s.lock.RUnlock()
s.trigger()
}
type stringFromURI struct {
base
from URI
}
// URIToString creates a binding that connects a URI data item to a String.
// Changes to the URI will be pushed to the String and setting the string will parse and set the
// URI if the parse was successful.
//
// Since: 2.1
func URIToString(v URI) String {
str := &stringFromURI{from: v}
v.AddListener(str)
return str
}
func (s *stringFromURI) Get() (string, error) {
val, err := s.from.Get()
if err != nil {
return "", err
}
return uriToString(val)
}
func (s *stringFromURI) Set(str string) error {
val, err := uriFromString(str)
if err != nil {
return err
}
old, err := s.from.Get()
if err != nil {
return err
}
if val == old {
return nil
}
if err = s.from.Set(val); err != nil {
return err
}
s.DataChanged()
return nil
}
func (s *stringFromURI) DataChanged() {
s.lock.RLock()
defer s.lock.RUnlock()
s.trigger()
}
type stringToBool struct {
base
format string
from String
}
// StringToBool creates a binding that connects a String data item to a Bool.
// Changes to the String will be parsed and pushed to the Bool if the parse was successful, and setting
// the Bool update the String binding.
//
// Since: 2.0
func StringToBool(str String) Bool {
v := &stringToBool{from: str}
str.AddListener(v)
return v
}
// StringToBoolWithFormat creates a binding that connects a String data item to a Bool and is
// presented using the specified format. Changes to the Bool will be parsed and if the format matches and
// the parse is successful it will be pushed to the String. Setting the Bool will push a formatted value
// into the String.
//
// Since: 2.0
func StringToBoolWithFormat(str String, format string) Bool {
if format == "%t" { // Same as not using custom format.
return StringToBool(str)
}
v := &stringToBool{from: str, format: format}
str.AddListener(v)
return v
}
func (s *stringToBool) Get() (bool, error) {
str, err := s.from.Get()
if str == "" || err != nil {
return false, err
}
var val bool
if s.format != "" {
n, err := fmt.Sscanf(str, s.format+" ", &val) // " " denotes match to end of string
if err != nil {
return false, err
}
if n != 1 {
return false, errParseFailed
}
} else {
new, err := parseBool(str)
if err != nil {
return false, err
}
val = new
}
return val, nil
}
func (s *stringToBool) Set(val bool) error {
var str string
if s.format != "" {
str = fmt.Sprintf(s.format, val)
} else {
str = formatBool(val)
}
old, err := s.from.Get()
if str == old {
return err
}
if err = s.from.Set(str); err != nil {
return err
}
s.DataChanged()
return nil
}
func (s *stringToBool) DataChanged() {
s.lock.RLock()
defer s.lock.RUnlock()
s.trigger()
}
type stringToFloat struct {
base
format string
from String
}
// StringToFloat creates a binding that connects a String data item to a Float.
// Changes to the String will be parsed and pushed to the Float if the parse was successful, and setting
// the Float update the String binding.
//
// Since: 2.0
func StringToFloat(str String) Float {
v := &stringToFloat{from: str}
str.AddListener(v)
return v
}
// StringToFloatWithFormat creates a binding that connects a String data item to a Float and is
// presented using the specified format. Changes to the Float will be parsed and if the format matches and
// the parse is successful it will be pushed to the String. Setting the Float will push a formatted value
// into the String.
//
// Since: 2.0
func StringToFloatWithFormat(str String, format string) Float {
if format == "%f" { // Same as not using custom format.
return StringToFloat(str)
}
v := &stringToFloat{from: str, format: format}
str.AddListener(v)
return v
}
func (s *stringToFloat) Get() (float64, error) {
str, err := s.from.Get()
if str == "" || err != nil {
return 0.0, err
}
var val float64
if s.format != "" {
n, err := fmt.Sscanf(str, s.format+" ", &val) // " " denotes match to end of string
if err != nil {
return 0.0, err
}
if n != 1 {
return 0.0, errParseFailed
}
} else {
new, err := parseFloat(str)
if err != nil {
return 0.0, err
}
val = new
}
return val, nil
}
func (s *stringToFloat) Set(val float64) error {
var str string
if s.format != "" {
str = fmt.Sprintf(s.format, val)
} else {
str = formatFloat(val)
}
old, err := s.from.Get()
if str == old {
return err
}
if err = s.from.Set(str); err != nil {
return err
}
s.DataChanged()
return nil
}
func (s *stringToFloat) DataChanged() {
s.lock.RLock()
defer s.lock.RUnlock()
s.trigger()
}
type stringToInt struct {
base
format string
from String
}
// StringToInt creates a binding that connects a String data item to a Int.
// Changes to the String will be parsed and pushed to the Int if the parse was successful, and setting
// the Int update the String binding.
//
// Since: 2.0
func StringToInt(str String) Int {
v := &stringToInt{from: str}
str.AddListener(v)
return v
}
// StringToIntWithFormat creates a binding that connects a String data item to a Int and is
// presented using the specified format. Changes to the Int will be parsed and if the format matches and
// the parse is successful it will be pushed to the String. Setting the Int will push a formatted value
// into the String.
//
// Since: 2.0
func StringToIntWithFormat(str String, format string) Int {
if format == "%d" { // Same as not using custom format.
return StringToInt(str)
}
v := &stringToInt{from: str, format: format}
str.AddListener(v)
return v
}
func (s *stringToInt) Get() (int, error) {
str, err := s.from.Get()
if str == "" || err != nil {
return 0, err
}
var val int
if s.format != "" {
n, err := fmt.Sscanf(str, s.format+" ", &val) // " " denotes match to end of string
if err != nil {
return 0, err
}
if n != 1 {
return 0, errParseFailed
}
} else {
new, err := parseInt(str)
if err != nil {
return 0, err
}
val = new
}
return val, nil
}
func (s *stringToInt) Set(val int) error {
var str string
if s.format != "" {
str = fmt.Sprintf(s.format, val)
} else {
str = formatInt(val)
}
old, err := s.from.Get()
if str == old {
return err
}
if err = s.from.Set(str); err != nil {
return err
}
s.DataChanged()
return nil
}
func (s *stringToInt) DataChanged() {
s.lock.RLock()
defer s.lock.RUnlock()
s.trigger()
}
type stringToURI struct {
base
from String
}
// StringToURI creates a binding that connects a String data item to a URI.
// Changes to the String will be parsed and pushed to the URI if the parse was successful, and setting
// the URI update the String binding.
//
// Since: 2.1
func StringToURI(str String) URI {
v := &stringToURI{from: str}
str.AddListener(v)
return v
}
func (s *stringToURI) Get() (fyne.URI, error) {
str, err := s.from.Get()
if str == "" || err != nil {
return fyne.URI(nil), err
}
return uriFromString(str)
}
func (s *stringToURI) Set(val fyne.URI) error {
str, err := uriToString(val)
if err != nil {
return err
}
old, err := s.from.Get()
if str == old {
return err
}
if err = s.from.Set(str); err != nil {
return err
}
s.DataChanged()
return nil
}
func (s *stringToURI) DataChanged() {
s.lock.RLock()
defer s.lock.RUnlock()
s.trigger()
}

103
vendor/fyne.io/fyne/v2/data/binding/convert_helper.go generated vendored Normal file

@ -0,0 +1,103 @@
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)
}

43
vendor/fyne.io/fyne/v2/data/binding/listbinding.go generated vendored Normal file

@ -0,0 +1,43 @@
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) {
b.lock.RLock()
defer b.lock.RUnlock()
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 {
b.lock.RLock()
defer b.lock.RUnlock()
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:]...)
}

522
vendor/fyne.io/fyne/v2/data/binding/mapbinding.go generated vendored Normal file

@ -0,0 +1,522 @@
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 := b.items[key]
if !found {
b.setItem(key, bindUntypedMapValue(b.val, key, b.updateExternal))
changed = true
}
}
// remove old
for key := range b.items {
_, found := (*b.val)[key]
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}
}

104
vendor/fyne.io/fyne/v2/data/binding/pref_helper.go generated vendored Normal file

@ -0,0 +1,104 @@
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
appPrefs fyne.Preferences // the main application prefs, to check if it changed...
}
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(fyne.CurrentApp().Preferences()) })
return binds.(*preferenceBindings)
}
func (m *preferencesMap) getBindings(p fyne.Preferences) *preferenceBindings {
if p == fyne.CurrentApp().Preferences() {
if m.appPrefs == nil {
m.appPrefs = p
} else if m.appPrefs != p {
m.migratePreferences(m.appPrefs, p)
}
}
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()
}
}
func (m *preferencesMap) migratePreferences(src, dst fyne.Preferences) {
old, loaded := m.prefs.Load(src)
if !loaded {
return
}
m.prefs.Store(dst, old)
m.prefs.Delete(src)
m.appPrefs = dst
binds := m.getBindings(dst)
if binds == nil {
return
}
for _, b := range binds.list() {
if backed, ok := b.(interface{ replaceProvider(fyne.Preferences) }); ok {
backed.replaceProvider(dst)
}
}
m.preferencesChanged(dst)
}

244
vendor/fyne.io/fyne/v2/data/binding/preference.go generated vendored Normal file

@ -0,0 +1,244 @@
// 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()
}
func (b *prefBoundBool) replaceProvider(p fyne.Preferences) {
b.p = p
}
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()
}
func (b *prefBoundFloat) replaceProvider(p fyne.Preferences) {
b.p = p
}
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()
}
func (b *prefBoundInt) replaceProvider(p fyne.Preferences) {
b.p = p
}
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()
}
func (b *prefBoundString) replaceProvider(p fyne.Preferences) {
b.p = p
}

30
vendor/fyne.io/fyne/v2/data/binding/queue.go generated vendored Normal file

@ -0,0 +1,30 @@
package binding
import (
"sync"
"fyne.io/fyne/v2/internal/async"
)
var (
once sync.Once
queue *async.UnboundedFuncChan
)
func queueItem(f func()) {
once.Do(func() {
queue = async.NewUnboundedFuncChan()
go func() {
for f := range queue.Out() {
f()
}
}()
})
queue.In() <- f
}
func waitForItems() {
done := make(chan struct{})
queue.In() <- func() { close(done) }
<-done
}

218
vendor/fyne.io/fyne/v2/data/binding/sprintf.go generated vendored Normal file

@ -0,0 +1,218 @@
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)
}

92
vendor/fyne.io/fyne/v2/data/binding/treebinding.go generated vendored Normal file

@ -0,0 +1,92 @@
package binding
// DataTreeRootID const is the value used as ID for the root of any tree binding.
const DataTreeRootID = ""
// DataTree is the base interface for all bindable data trees.
//
// Since: 2.4
type DataTree interface {
DataItem
GetItem(id string) (DataItem, error)
ChildIDs(string) []string
}
type treeBase struct {
base
ids map[string][]string
items map[string]DataItem
}
// GetItem returns the DataItem at the specified id.
func (t *treeBase) GetItem(id string) (DataItem, error) {
t.lock.RLock()
defer t.lock.RUnlock()
if item, ok := t.items[id]; ok {
return item, nil
}
return nil, errOutOfBounds
}
// ChildIDs returns the ordered IDs of items in this data tree that are children of the specified ID.
func (t *treeBase) ChildIDs(id string) []string {
t.lock.RLock()
defer t.lock.RUnlock()
if ids, ok := t.ids[id]; ok {
return ids
}
return []string{}
}
func (t *treeBase) appendItem(i DataItem, id, parent string) {
t.items[id] = i
ids, ok := t.ids[parent]
if !ok {
ids = make([]string, 0)
}
for _, in := range ids {
if in == id {
return
}
}
t.ids[parent] = append(ids, id)
}
func (t *treeBase) deleteItem(id, parent string) {
delete(t.items, id)
ids, ok := t.ids[parent]
if !ok {
return
}
off := -1
for i, id2 := range ids {
if id2 == id {
off = i
break
}
}
if off == -1 {
return
}
t.ids[parent] = append(ids[:off], ids[off+1:]...)
}
func parentIDFor(id string, ids map[string][]string) string {
for parent, list := range ids {
for _, child := range list {
if child == id {
return parent
}
}
}
return ""
}

39
vendor/fyne.io/fyne/v2/device.go generated vendored Normal file

@ -0,0 +1,39 @@
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()
}

32
vendor/fyne.io/fyne/v2/driver.go generated vendored Normal file

@ -0,0 +1,32 @@
package fyne
// Driver defines an abstract concept of a Fyne render driver.
// Any implementation must provide at least these methods.
type Driver interface {
// CreateWindow creates a new UI Window.
CreateWindow(string) Window
// AllWindows returns a slice containing all app windows.
AllWindows() []Window
// RenderedTextSize returns the size required to render the given string of specified
// font size and style. It also returns the height to text baseline, measured from the top.
RenderedTextSize(text string, fontSize float32, style TextStyle) (size Size, baseline float32)
// CanvasForObject returns the canvas that is associated with a given CanvasObject.
CanvasForObject(CanvasObject) Canvas
// AbsolutePositionForObject returns the position of a given CanvasObject relative to the top/left of a canvas.
AbsolutePositionForObject(CanvasObject) Position
// Device returns the device that the application is currently running on.
Device() Device
// Run starts the main event loop of the driver.
Run()
// Quit closes the driver and open windows, then exit the application.
// On some some operating systems this does nothing, for example iOS and Android.
Quit()
// StartAnimation registers a new animation with this driver and requests it be started.
StartAnimation(*Animation)
// StopAnimation stops an animation and unregisters from this driver.
StopAnimation(*Animation)
}

11
vendor/fyne.io/fyne/v2/driver/desktop/app.go generated vendored Normal file

@ -0,0 +1,11 @@
package desktop
import "fyne.io/fyne/v2"
// App defines the desktop specific extensions to a fyne.App.
//
// Since: 2.2
type App interface {
SetSystemTrayMenu(menu *fyne.Menu)
SetSystemTrayIcon(icon fyne.Resource)
}

11
vendor/fyne.io/fyne/v2/driver/desktop/canvas.go generated vendored Normal file

@ -0,0 +1,11 @@
package desktop
import "fyne.io/fyne/v2"
// Canvas defines the desktop specific extensions to a fyne.Canvas.
type Canvas interface {
OnKeyDown() func(*fyne.KeyEvent)
SetOnKeyDown(func(*fyne.KeyEvent))
OnKeyUp() func(*fyne.KeyEvent)
SetOnKeyUp(func(*fyne.KeyEvent))
}

47
vendor/fyne.io/fyne/v2/driver/desktop/cursor.go generated vendored Normal file

@ -0,0 +1,47 @@
package desktop
import "image"
// Cursor interface is used for objects that desire a specific cursor.
//
// Since: 2.0
type Cursor interface {
// Image returns the image for the given cursor, or nil if none should be shown.
// It also returns the x and y pixels that should act as the hot-spot (measured from top left corner).
Image() (image.Image, int, int)
}
// StandardCursor represents a standard Fyne cursor.
// These values were previously of type `fyne.Cursor`.
//
// Since: 2.0
type StandardCursor int
// Image is not used for any of the StandardCursor types.
//
// Since: 2.0
func (d StandardCursor) Image() (image.Image, int, int) {
return nil, 0, 0
}
const (
// DefaultCursor is the default cursor typically an arrow
DefaultCursor StandardCursor = iota
// TextCursor is the cursor often used to indicate text selection
TextCursor
// CrosshairCursor is the cursor often used to indicate bitmaps
CrosshairCursor
// PointerCursor is the cursor often used to indicate a link
PointerCursor
// HResizeCursor is the cursor often used to indicate horizontal resize
HResizeCursor
// VResizeCursor is the cursor often used to indicate vertical resize
VResizeCursor
// HiddenCursor will cause the cursor to not be shown
HiddenCursor
)
// Cursorable describes any CanvasObject that needs a cursor change
type Cursorable interface {
Cursor() Cursor
}

15
vendor/fyne.io/fyne/v2/driver/desktop/driver.go generated vendored Normal file

@ -0,0 +1,15 @@
// Package desktop provides desktop specific driver functionality.
package desktop
import "fyne.io/fyne/v2"
// Driver represents the extended capabilities of a desktop driver
type Driver interface {
// Create a new borderless window that is centered on screen
CreateSplashWindow() fyne.Window
// Gets the set of key modifiers that are currently active
//
// Since: 2.4
CurrentKeyModifiers() fyne.KeyModifier
}

66
vendor/fyne.io/fyne/v2/driver/desktop/key.go generated vendored Normal file

@ -0,0 +1,66 @@
package desktop
import (
"fyne.io/fyne/v2"
)
const (
// KeyNone represents no key
KeyNone fyne.KeyName = ""
// KeyShiftLeft represents the left shift key
KeyShiftLeft fyne.KeyName = "LeftShift"
// KeyShiftRight represents the right shift key
KeyShiftRight fyne.KeyName = "RightShift"
// KeyControlLeft represents the left control key
KeyControlLeft fyne.KeyName = "LeftControl"
// KeyControlRight represents the right control key
KeyControlRight fyne.KeyName = "RightControl"
// KeyAltLeft represents the left alt key
KeyAltLeft fyne.KeyName = "LeftAlt"
// KeyAltRight represents the right alt key
KeyAltRight fyne.KeyName = "RightAlt"
// KeySuperLeft represents the left "Windows" key (or "Command" key on macOS)
KeySuperLeft fyne.KeyName = "LeftSuper"
// KeySuperRight represents the right "Windows" key (or "Command" key on macOS)
KeySuperRight fyne.KeyName = "RightSuper"
// KeyMenu represents the left or right menu / application key
KeyMenu fyne.KeyName = "Menu"
// KeyPrintScreen represents the key used to cause a screen capture
KeyPrintScreen fyne.KeyName = "PrintScreen"
// KeyCapsLock represents the caps lock key, tapping once is the down event then again is the up
KeyCapsLock fyne.KeyName = "CapsLock"
)
// Modifier captures any key modifiers (shift etc.) pressed during a key event
//
// Deprecated: Use fyne.KeyModifier instead.
type Modifier = fyne.KeyModifier
const (
// ShiftModifier represents a shift key being held
//
// Deprecated: Use fyne.KeyModifierShift instead.
ShiftModifier = fyne.KeyModifierShift
// ControlModifier represents the ctrl key being held
//
// Deprecated: Use fyne.KeyModifierControl instead.
ControlModifier = fyne.KeyModifierControl
// AltModifier represents either alt keys being held
//
// Deprecated: Use fyne.KeyModifierAlt instead.
AltModifier = fyne.KeyModifierAlt
// SuperModifier represents either super keys being held
//
// Deprecated: Use fyne.KeyModifierSuper instead.
SuperModifier = fyne.KeyModifierSuper
)
// Keyable describes any focusable canvas object that can accept desktop key events.
// This is the traditional key down and up event that is not applicable to all devices.
type Keyable interface {
fyne.Focusable
KeyDown(*fyne.KeyEvent)
KeyUp(*fyne.KeyEvent)
}

58
vendor/fyne.io/fyne/v2/driver/desktop/mouse.go generated vendored Normal file

@ -0,0 +1,58 @@
package desktop
import "fyne.io/fyne/v2"
// MouseButton represents a single button in a desktop MouseEvent
type MouseButton int
const (
// MouseButtonPrimary is the most common mouse button - on some systems the only one.
// This will normally be on the left side of a mouse.
//
// Since: 2.0
MouseButtonPrimary MouseButton = 1 << iota
// MouseButtonSecondary is the secondary button on most mouse input devices.
// This will normally be on the right side of a mouse.
//
// Since: 2.0
MouseButtonSecondary
// MouseButtonTertiary is the middle button on the mouse, assuming it has one.
//
// Since: 2.0
MouseButtonTertiary
// LeftMouseButton is the most common mouse button - on some systems the only one.
//
// Deprecated: use MouseButtonPrimary which will adapt to mouse configuration.
LeftMouseButton = MouseButtonPrimary
// RightMouseButton is the secondary button on most mouse input devices.
//
// Deprecated: use MouseButtonSecondary which will adapt to mouse configuration.
RightMouseButton = MouseButtonSecondary
)
// MouseEvent contains data relating to desktop mouse events
type MouseEvent struct {
fyne.PointEvent
Button MouseButton
Modifier fyne.KeyModifier
}
// Mouseable represents desktop mouse events that can be sent to CanvasObjects
type Mouseable interface {
MouseDown(*MouseEvent)
MouseUp(*MouseEvent)
}
// Hoverable is used when a canvas object wishes to know if a pointer device moves over it.
type Hoverable interface {
// MouseIn is a hook that is called if the mouse pointer enters the element.
MouseIn(*MouseEvent)
// MouseMoved is a hook that is called if the mouse pointer moved over the element.
MouseMoved(*MouseEvent)
// MouseOut is a hook that is called if the mouse pointer leaves the element.
MouseOut()
}

61
vendor/fyne.io/fyne/v2/driver/desktop/shortcut.go generated vendored Normal file

@ -0,0 +1,61 @@
package desktop
import (
"runtime"
"strings"
"fyne.io/fyne/v2"
)
// Declare conformity with Shortcut interface
var _ fyne.Shortcut = (*CustomShortcut)(nil)
var _ fyne.KeyboardShortcut = (*CustomShortcut)(nil)
// CustomShortcut describes a shortcut desktop event.
type CustomShortcut struct {
fyne.KeyName
Modifier fyne.KeyModifier
}
// Key returns the key name of this shortcut.
// @implements KeyboardShortcut
func (cs *CustomShortcut) Key() fyne.KeyName {
return cs.KeyName
}
// Mod returns the modifier of this shortcut.
// @implements KeyboardShortcut
func (cs *CustomShortcut) Mod() fyne.KeyModifier {
return cs.Modifier
}
// ShortcutName returns the shortcut name associated to the event
func (cs *CustomShortcut) ShortcutName() string {
id := &strings.Builder{}
id.WriteString("CustomDesktop:")
id.WriteString(modifierToString(cs.Modifier))
id.WriteString("+")
id.WriteString(string(cs.KeyName))
return id.String()
}
func modifierToString(mods fyne.KeyModifier) string {
s := []string{}
if (mods & fyne.KeyModifierShift) != 0 {
s = append(s, string("Shift"))
}
if (mods & fyne.KeyModifierControl) != 0 {
s = append(s, string("Control"))
}
if (mods & fyne.KeyModifierAlt) != 0 {
s = append(s, string("Alt"))
}
if (mods & fyne.KeyModifierSuper) != 0 {
if runtime.GOOS == "darwin" {
s = append(s, string("Command"))
} else {
s = append(s, string("Super"))
}
}
return strings.Join(s, "+")
}

12
vendor/fyne.io/fyne/v2/driver/mobile/device.go generated vendored Normal file

@ -0,0 +1,12 @@
// Package mobile provides mobile specific driver functionality.
package mobile
// Device describes functionality only available on mobile
type Device interface {
// Request that the mobile device show the touch screen keyboard (standard layout)
ShowVirtualKeyboard()
// Request that the mobile device show the touch screen keyboard (custom layout)
ShowVirtualKeyboardType(KeyboardType)
// Request that the mobile device dismiss the touch screen keyboard
HideVirtualKeyboard()
}

10
vendor/fyne.io/fyne/v2/driver/mobile/driver.go generated vendored Normal file

@ -0,0 +1,10 @@
// Package mobile provides desktop specific mobile functionality.
package mobile
// Driver represents the extended capabilities of a mobile driver
//
// Since: 2.4
type Driver interface {
// GoBack asks the OS to go to the previous app / activity, where supported
GoBack()
}

10
vendor/fyne.io/fyne/v2/driver/mobile/key.go generated vendored Normal file

@ -0,0 +1,10 @@
package mobile
import (
"fyne.io/fyne/v2"
)
const (
// KeyBack represents the back button which may be hardware or software
KeyBack fyne.KeyName = "Back"
)

Some files were not shown because too many files have changed in this diff Show More