mirror of
https://github.com/AynaLivePlayer/AynaLivePlayer.git
synced 2025-12-09 11:48:13 +08:00
Compare commits
298 Commits
v0.9.0
...
6b4fbf6951
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6b4fbf6951 | ||
|
|
a81eb4a131 | ||
|
|
f13577f890 | ||
|
|
82662c308a | ||
|
|
eb564402ce | ||
|
|
f6f306edc3 | ||
|
|
a26b58b083 | ||
|
|
d85ea3aaa2 | ||
|
|
093c10092b | ||
|
|
4f53f870f3 | ||
|
|
5a699a1e2e | ||
|
|
7f6a6e7af5 | ||
|
|
3aebdb00f9 | ||
|
|
0b50f45f98 | ||
|
|
575e1863fd | ||
|
|
59f0d3f69f | ||
|
|
1e538646f4 | ||
|
|
5c016ae145 | ||
|
|
162779f25e | ||
|
|
d2c89b031f | ||
|
|
ccbe2117e0 | ||
|
|
466e4a761e | ||
|
|
07d0a5debc | ||
|
|
7e680bc6bf | ||
|
|
45878dfe99 | ||
|
|
b136a374c4 | ||
|
|
6bf17a0dcb | ||
|
|
da4ae97923 | ||
|
|
2bab6044bf | ||
|
|
2cf80698d9 | ||
|
|
4ac75dd882 | ||
|
|
6af984cfbb | ||
|
|
82ced0b9a9 | ||
|
|
d11a5a5f76 | ||
|
|
df771ed9f8 | ||
|
|
3bd8945247 | ||
|
|
c31342c708 | ||
|
|
46ea1968b6 | ||
|
|
d4f0c3438c | ||
|
|
1a0e053377 | ||
|
|
7b87efb076 | ||
|
|
3f5623f117 | ||
|
|
261166e67d | ||
|
|
dc727935a8 | ||
|
|
68a172aa4f | ||
|
|
bab3a14d2f | ||
|
|
ee99bd939e | ||
|
|
0934bd2d45 | ||
|
|
7de62ef6ba | ||
|
|
af6ef96754 | ||
|
|
09bacbf7da | ||
|
|
21fe844b5f | ||
|
|
72bdef6a91 | ||
|
|
c554f1effc | ||
|
|
8244380a6a | ||
|
|
0bbb9f378f | ||
|
|
eafb074398 | ||
|
|
7661e966be | ||
|
|
06df1a1cc4 | ||
|
|
5b4104664e | ||
|
|
2b99f7b23f | ||
|
|
3b58f6f972 | ||
|
|
374be8ef03 | ||
|
|
f4aa9b4ac9 | ||
|
|
1fa82c2a01 | ||
|
|
cdc3c5d118 | ||
|
|
c7b70740f6 | ||
|
|
dd71c3b9ba | ||
|
|
45e4c15b8d | ||
|
|
da81d43584 | ||
|
|
82df68fa8a | ||
|
|
f897dd0800 | ||
|
|
0ea07dc295 | ||
|
|
edf3b679cc | ||
|
|
041b678859 | ||
|
|
1b58c7bc08 | ||
|
|
826e868916 | ||
|
|
32eab60cad | ||
|
|
83fbefaf68 | ||
|
|
f62e9ea43a | ||
|
|
3456bfcb8c | ||
|
|
df614a16f6 | ||
|
|
8a96506703 | ||
|
|
53a6ad4aed | ||
|
|
387e96e27e | ||
|
|
8da41f81e1 | ||
|
|
42c80f3ea8 | ||
|
|
e311c40f5e | ||
|
|
cbba7dd1b3 | ||
|
|
e598ed676c | ||
|
|
62a122a9e5 | ||
|
|
98028885b8 | ||
|
|
64c7780b60 | ||
|
|
d40ef5d79d | ||
|
|
32e688bb91 | ||
|
|
df501be65a | ||
|
|
fdf12bde26 | ||
|
|
cbde61d99b | ||
|
|
2b300af7b7 | ||
|
|
1bbaf478bb | ||
|
|
72589a5eaa | ||
|
|
0424513d86 | ||
|
|
f7b6a5eafb | ||
|
|
1c5e85010b | ||
|
|
d9581c0bab | ||
|
|
8114e69b9f | ||
|
|
b178f729a2 | ||
|
|
3d5286b319 | ||
|
|
5afbe695f2 | ||
|
|
f36f56cd80 | ||
|
|
97faa1a538 | ||
|
|
93e8d4d846 | ||
|
|
4e3dd6400f | ||
|
|
27a2f1f3a8 | ||
|
|
bc85f86f98 | ||
|
|
c1940c0a98 | ||
|
|
4e6659935e | ||
|
|
9941fc6993 | ||
|
|
e7c499ca7f | ||
|
|
e61f096bb2 | ||
|
|
c1c230a5f0 | ||
|
|
a2420db1d9 | ||
|
|
0e4a429a19 | ||
|
|
d69f953508 | ||
|
|
04406bf006 | ||
|
|
b5c2a0ce96 | ||
|
|
13ffb609a9 | ||
|
|
2e165d5d0e | ||
|
|
0fe77febdf | ||
|
|
539e8eabe3 | ||
|
|
7960299f09 | ||
|
|
e7f7ddfe4a | ||
|
|
23d6944a52 | ||
|
|
ef994defb9 | ||
|
|
2056bd310a | ||
|
|
46c2e2710e | ||
|
|
156901f14c | ||
|
|
9f8b103be1 | ||
|
|
25bc0f01ca | ||
|
|
c5d3fb407b | ||
|
|
5d507598e6 | ||
|
|
5480ebdd86 | ||
|
|
0e3527c166 | ||
|
|
48e5d2f516 | ||
|
|
b7f10431d6 | ||
|
|
0c976acf80 | ||
|
|
a06412e833 | ||
|
|
7e089913be | ||
|
|
61596a5b79 | ||
|
|
e44285841f | ||
|
|
93b3bbbb1f | ||
|
|
29509af04e | ||
|
|
6073719d69 | ||
|
|
c9d84b1134 | ||
|
|
4a76cbdb8b | ||
|
|
4cf79a0ab9 | ||
|
|
56b484257b | ||
|
|
e8953f1e56 | ||
|
|
1aace54b92 | ||
|
|
0e64d1dc91 | ||
|
|
ea05fcd902 | ||
|
|
afa2729282 | ||
|
|
36cdfd0824 | ||
|
|
ce042bad0a | ||
|
|
3959eac396 | ||
|
|
2ab27b1b57 | ||
|
|
7316818093 | ||
|
|
fb43dcab5d | ||
|
|
fa7448e2c5 | ||
|
|
5d002becc4 | ||
|
|
a2d674666a | ||
|
|
a6cdf3c903 | ||
|
|
a892ec3043 | ||
|
|
2bc19e2e0c | ||
|
|
552f115a33 | ||
|
|
92376bdd12 | ||
|
|
9142d74f19 | ||
|
|
65aba16f44 | ||
|
|
37f0dadd8b | ||
|
|
c57b7c992f | ||
|
|
e47b48bb42 | ||
|
|
deec6157b9 | ||
|
|
7634bd5864 | ||
|
|
50d3172816 | ||
|
|
e466e21bc8 | ||
|
|
7a57a1dcc0 | ||
|
|
627e5ff9bc | ||
|
|
ce1322fbde | ||
|
|
8db6f49c7b | ||
|
|
161920686f | ||
|
|
6b595e453b | ||
|
|
36df238ac1 | ||
|
|
92f36057a4 | ||
|
|
b175efa6e3 | ||
|
|
4aa5071699 | ||
|
|
318e062198 | ||
|
|
16c9d1c10e | ||
|
|
23d59417d5 | ||
|
|
c6a8a2b272 | ||
|
|
61342ebfa2 | ||
|
|
1df1f3a609 | ||
|
|
891f3d2879 | ||
|
|
0dbb9976d7 | ||
|
|
eabd9fcea3 | ||
|
|
ec110cca9c | ||
|
|
f6fd6d362b | ||
|
|
6ec3987b50 | ||
|
|
812dee0145 | ||
|
|
16f0aa34a2 | ||
|
|
03862237b4 | ||
|
|
05322ab0b3 | ||
|
|
f97c460c46 | ||
|
|
8d24ac8cba | ||
|
|
f19babe30a | ||
|
|
9b55e42811 | ||
|
|
963dedbe65 | ||
|
|
b031bdd3df | ||
|
|
aeca816774 | ||
|
|
9b6f681d4a | ||
|
|
46ea45580c | ||
|
|
da96f711ae | ||
|
|
399f09ba9f | ||
|
|
2326ef6955 | ||
|
|
23db890d47 | ||
|
|
5145680a04 | ||
|
|
d12a0155b0 | ||
|
|
ac8633b4a7 | ||
|
|
9b9895e654 | ||
|
|
f54e01f7ea | ||
|
|
f24b3e73fb | ||
|
|
68c7c591ff | ||
|
|
95a0a97264 | ||
|
|
e5076667db | ||
|
|
726ac8b449 | ||
|
|
88066bd3b9 | ||
|
|
d514f96c28 | ||
|
|
9a277482b4 | ||
|
|
39db106a74 | ||
|
|
0e5140f907 | ||
|
|
3786355997 | ||
|
|
a5656b8ef0 | ||
|
|
884a5afcb5 | ||
|
|
bec84790fd | ||
|
|
e7775019f7 | ||
|
|
3fb7941433 | ||
|
|
b56a1073a6 | ||
|
|
eba37c04bc | ||
|
|
de3d2f6c66 | ||
|
|
a8091247ec | ||
|
|
9c0b711aa0 | ||
|
|
58d7ebd43d | ||
|
|
ee775dee8d | ||
|
|
5d27040c8b | ||
|
|
2169817afa | ||
|
|
e9149474de | ||
|
|
5cc5948a85 | ||
|
|
92ea73ff5a | ||
|
|
e567b5b47b | ||
|
|
802961576c | ||
|
|
0ad0296e51 | ||
|
|
f484b28d0e | ||
|
|
e2fa2bdb8e | ||
|
|
e01d067cbc | ||
|
|
44203f9887 | ||
|
|
ccb3ccbc00 | ||
|
|
d64cac9b1b | ||
|
|
6ef4ba3fe8 | ||
|
|
d30db8a95a | ||
|
|
30a2f9ebea | ||
|
|
6ddcb507f5 | ||
|
|
fbf8c7f149 | ||
|
|
139f331a14 | ||
|
|
c9cd78b0eb | ||
|
|
e7325b6383 | ||
|
|
fd872c1f5b | ||
|
|
119862a023 | ||
|
|
d52f97429d | ||
|
|
084050fcf7 | ||
|
|
f926f15606 | ||
|
|
8d73a3c284 | ||
|
|
c993684497 | ||
|
|
0ae52e349d | ||
|
|
cb092366f3 | ||
|
|
18df9ff64c | ||
|
|
02cc9de11a | ||
|
|
6f2349e17b | ||
|
|
9d99a74faf | ||
|
|
1e18ca1ff2 | ||
|
|
9ec4057412 | ||
|
|
c47d338a9e | ||
|
|
0498d2dbf3 | ||
|
|
eac8b7b775 | ||
|
|
d20c39ace3 | ||
|
|
a8d5e9d772 | ||
|
|
b6645cc575 | ||
|
|
4c0b407475 | ||
|
|
c0c83ef82a | ||
|
|
f4b080da25 |
132
.github/workflows/build.yml
vendored
Normal file
132
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
timeout-minutes: 20
|
||||
|
||||
env:
|
||||
GOOS: windows
|
||||
GOARCH: amd64
|
||||
EXECUTABLE: AynaLivePlayer.exe
|
||||
CGO_CFLAGS: "-I${{ github.workspace }}/libmpv/include -I${{ github.workspace }}/libvlc/VideoLAN.LibVLC.Windows.3.0.21/build/x64/include"
|
||||
CGO_LDFLAGS: "-L${{ github.workspace }}/libmpv -L${{ github.workspace }}/libvlc/VideoLAN.LibVLC.Windows.3.0.21/build/x64"
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install 7-Zip
|
||||
run: choco install 7zip
|
||||
|
||||
- name: Download libmpv
|
||||
uses: robinraju/release-downloader@v1
|
||||
with:
|
||||
repository: "shinchiro/mpv-winbuild-cmake"
|
||||
latest: true
|
||||
fileName: "mpv-dev-x86_64-[0-9]*.7z"
|
||||
extract: false
|
||||
out-file-path: "libmpv"
|
||||
|
||||
- name: Extract libmpv
|
||||
run: |
|
||||
7z x "libmpv/mpv-dev-x86_64-*.7z" -o"libmpv"
|
||||
|
||||
- name: Setup NuGet.exe
|
||||
uses: nuget/setup-nuget@v2
|
||||
with:
|
||||
nuget-version: 'latest'
|
||||
|
||||
- name: Install VLC dependency
|
||||
run: nuget install VideoLAN.LibVLC.Windows -OutputDirectory ${{ github.workspace }}/libvlc -Version 3.0.21
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Install Go Dependencies
|
||||
run: |
|
||||
go mod tidy
|
||||
go install fyne.io/tools/cmd/fyne@latest
|
||||
|
||||
- name: Bundle assets
|
||||
run: |
|
||||
fyne bundle --name resImageIcon --package resource ./assets/icon2.png > ./resource/bundle.go
|
||||
|
||||
- name: Build application
|
||||
run: |
|
||||
go build -tags="mpvOnly,nosource" -v -o ./AynaLivePlayerMpvNoSource.exe -ldflags -H=windowsgui app/main.go
|
||||
go build -tags="vlcOnly,nosource" -v -o ./AynaLivePlayerVlcNoSource.exe -ldflags -H=windowsgui app/main.go
|
||||
go build -tags="nosource" -v -o ./AynaLivePlayerAllPlayerNoSource.exe -ldflags -H=windowsgui app/main.go
|
||||
go build -tags="mpvOnly" -v -o ./AynaLivePlayerMpv.exe -ldflags -H=windowsgui app/main.go
|
||||
go build -tags="vlcOnly" -v -o ./AynaLivePlayerVlc.exe -ldflags -H=windowsgui app/main.go
|
||||
go build -v -o ./AynaLivePlayerAllPlayer.exe -ldflags -H=windowsgui app/main.go
|
||||
|
||||
- name: Upload artifact (NoSource)
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-build-nosource
|
||||
path: |
|
||||
./AynaLivePlayerMpvNoSource.exe
|
||||
./AynaLivePlayerVlcNoSource.exe
|
||||
./AynaLivePlayerAllPlayerNoSource.exe
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows-build
|
||||
path: |
|
||||
./AynaLivePlayerMpv.exe
|
||||
./AynaLivePlayerVlc.exe
|
||||
./AynaLivePlayerAllPlayer.exe
|
||||
|
||||
build-ubuntu:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
|
||||
env:
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
EXECUTABLE: AynaLivePlayer
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libvlc-dev vlc libmpv-dev libgl-dev libxcursor-dev libxrandr-dev libxinerama-dev libxi-dev libgl1-mesa-dev xorg-dev
|
||||
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
|
||||
- name: Install Go Dependencies
|
||||
run: |
|
||||
go mod tidy
|
||||
go install fyne.io/tools/cmd/fyne@latest
|
||||
|
||||
- name: Bundle assets
|
||||
run: |
|
||||
fyne bundle --name resImageIcon --package resource ./assets/icon.png > ./resource/bundle.go
|
||||
|
||||
- name: Build application
|
||||
run: go build -o ./${{ env.EXECUTABLE }} app/main.go
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ubuntu-build
|
||||
path: ./${{ env.EXECUTABLE }}
|
||||
11
.gitignore
vendored
11
.gitignore
vendored
@@ -1,3 +1,12 @@
|
||||
.idea
|
||||
assets/webinfo/*.html
|
||||
assets/webinfo/assets
|
||||
assets/webinfo/assets
|
||||
resource/bundle.go
|
||||
music
|
||||
/txtinfo/
|
||||
CMakeCache.txt
|
||||
/config/
|
||||
/release/
|
||||
log.txt
|
||||
config.ini
|
||||
config.ini.bak
|
||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
[submodule "pkg/miaosic"]
|
||||
path = pkg/miaosic
|
||||
url = git@github.com:AynaLivePlayer/miaosic.git
|
||||
[submodule "pkg/liveroom-sdk"]
|
||||
path = pkg/liveroom-sdk
|
||||
url = git@github.com:AynaLivePlayer/liveroom-sdk.git
|
||||
58
Makefile
Normal file
58
Makefile
Normal file
@@ -0,0 +1,58 @@
|
||||
EXECUTABLE=AynaLivePlayer
|
||||
WINDOWS=$(EXECUTABLE).exe
|
||||
LINUX=$(EXECUTABLE)_linux
|
||||
DARWIN=$(EXECUTABLE)_darwin
|
||||
|
||||
ifeq ($(OS),Windows_NT)
|
||||
RM = del /Q /F
|
||||
RRM = rmdir /Q /S
|
||||
MKDIR = mkdir
|
||||
COPY = XCOPY /Y
|
||||
MOVE = move
|
||||
else
|
||||
RM = rm -f
|
||||
RRM = rm -rf
|
||||
MKDIR = mkdir
|
||||
COPY = cp -r
|
||||
MOVE = mv
|
||||
endif
|
||||
|
||||
bundle:
|
||||
fyne bundle --name resImageIcon --package resource ./assets/icon.png > ./resource/bundle.go
|
||||
fyne bundle --append --name resFontMSYaHei --package resource ./assets/msyh0.ttf >> ./resource/bundle.go
|
||||
fyne bundle --append --name resFontMSYaHeiBold --package resource ./assets/msyhbd0.ttf >> ./resource/bundle.go
|
||||
# fyne bundle --append --name resFontMSYaHei --package resource ./assets/msyh.ttc >> ./resource/bundle.go
|
||||
# fyne bundle --append --name resFontMSYaHeiBold --package resource ./assets/msyhbd.ttc >> ./resource/bundle.go
|
||||
|
||||
prebuild: bundle
|
||||
$(RRM) ./release
|
||||
$(MKDIR) ./release
|
||||
$(MKDIR) ./release/assets
|
||||
$(COPY) LICENSE.md ./release/LICENSE.md
|
||||
$(COPY) ./assets/translation.json ./release/assets/translation.json
|
||||
$(COPY) ./assets/config ./release/config
|
||||
$(COPY) ./music ./release/music
|
||||
go mod tidy
|
||||
|
||||
|
||||
$(LINUX): prebuild
|
||||
env GOOS=linux GOARCH=amd64 go build -o ./release/$(LINUX) app/main.go
|
||||
$(MOVE) ./release/$(LINUX) ./release/$(EXECUTABLE)
|
||||
|
||||
$(WINDOWS): prebuild
|
||||
env GOOS=windows GOARCH=amd64 go build -o ./release/$(WINDOWS) -ldflags -H=windowsgui app/main.go
|
||||
|
||||
$(DARWIN): prebuild
|
||||
env GOOS=darwin GOARCH=amd64 go build -o ./release/$(DARWIN) app/main.go
|
||||
$(MOVE) ./release/$(LINUX) ./release/$(EXECUTABLE)
|
||||
|
||||
|
||||
windows: $(WINDOWS) ## Build for Windows
|
||||
$(COPY) ./assets/windows/mpv-2.dll ./release/mpv-2.dll
|
||||
|
||||
linux: $(LINUX) ## Build for Linux
|
||||
|
||||
darwin: $(DARWIN) ## Build for Darwin (macOS)
|
||||
|
||||
clean:
|
||||
$(RRM) ./release
|
||||
39
README.md
39
README.md
@@ -8,11 +8,50 @@ QQ group: 621035845
|
||||
|
||||
## build
|
||||
|
||||
|
||||
> outdated, please refer to workflow file
|
||||
|
||||
```
|
||||
go build -o AynaLivePlayer.exe -ldflags -H=windowsgui app/gui/main.go
|
||||
```
|
||||
|
||||
## packaging
|
||||
|
||||
> outdated, please refer to workflow file
|
||||
|
||||
```
|
||||
fyne package --src path_to_gui --exe AynaLivePlayer.exe --appVersion 0.8.4 --icon path_to_icon
|
||||
```
|
||||
|
||||
## Windows build guide
|
||||
|
||||
> outdated, please refer to workflow file
|
||||
|
||||
1. install golang [link](https://go.dev/doc/install)
|
||||
2. install chocolatey [link](https://chocolatey.org/install)
|
||||
3. install required packages
|
||||
```
|
||||
choco install git
|
||||
choco install mingw
|
||||
```
|
||||
4. install fyne
|
||||
```
|
||||
go install fyne.io/fyne/v2/cmd/fyne@latest
|
||||
```
|
||||
5. clone this repo
|
||||
```bash
|
||||
git clone --recurse-submodules git@github.com:AynaLivePlayer/AynaLivePlayer.git
|
||||
```
|
||||
if you are using https links
|
||||
```
|
||||
git clone https://github.com/AynaLivePlayer/AynaLivePlayer.git
|
||||
git submodule set-url pkg/miaosic https://github.com/AynaLivePlayer/miaosic.git
|
||||
git submodule set-url pkg/liveroom-sdk https://github.com/AynaLivePlayer/liveroom-sdk.git
|
||||
git submodule update
|
||||
```
|
||||
6. now you can build (please check makefile for more details)
|
||||
```powershell
|
||||
$env:CGO_LDFLAGS="-LC:\Users\Admin\Desktop\AynaLivePlayer\libmpv\lib";$env:CGO_CFLAGS="-IC:\Users\Admin\Desktop\AynaLivePlayer\libmpv\include"
|
||||
# ... more setup, see makefile
|
||||
go build -o AynaLivePlayer.exe -ldflags -H=windowsgui app/main.go
|
||||
```
|
||||
@@ -1,23 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/logger"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Printf("BiliAudioBot Revive %s\n", config.Version)
|
||||
logger.Logger.SetLevel(logrus.DebugLevel)
|
||||
fmt.Println("Please enter room id")
|
||||
var roomid string
|
||||
|
||||
// Taking input from user
|
||||
fmt.Scanln(&roomid)
|
||||
controller.Initialize()
|
||||
controller.SetDanmuClient(roomid)
|
||||
ch := make(chan int)
|
||||
<-ch
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
[Details]
|
||||
Icon = "../../theme/icons/fyne.png"
|
||||
Name = "Fyne Demo"
|
||||
ID = "io.fyne.demo"
|
||||
Build = 6
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 42 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 41 KiB |
File diff suppressed because one or more lines are too long
@@ -1,43 +0,0 @@
|
||||
package data
|
||||
|
||||
//go:generate fyne bundle -package data -o bundled.go assets
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
// ThemedResource is a resource wrapper that will return an appropriate resource
|
||||
// for the currently selected theme.
|
||||
type ThemedResource struct {
|
||||
dark, light fyne.Resource
|
||||
}
|
||||
|
||||
func isLight() bool {
|
||||
r, g, b, _ := theme.ForegroundColor().RGBA()
|
||||
return r < 0xaaaa && g < 0xaaaa && b < 0xaaaa
|
||||
}
|
||||
|
||||
// Name returns the underlying resource name (used for caching)
|
||||
func (res *ThemedResource) Name() string {
|
||||
if isLight() {
|
||||
return res.light.Name()
|
||||
}
|
||||
return res.dark.Name()
|
||||
}
|
||||
|
||||
// Content returns the underlying content of the correct resource for the current theme
|
||||
func (res *ThemedResource) Content() []byte {
|
||||
if isLight() {
|
||||
return res.light.Content()
|
||||
}
|
||||
return res.dark.Content()
|
||||
}
|
||||
|
||||
// NewThemedResource creates a resource that adapts to the current theme setting.
|
||||
func NewThemedResource(dark, light fyne.Resource) *ThemedResource {
|
||||
return &ThemedResource{dark, light}
|
||||
}
|
||||
|
||||
// FyneScene contains the full fyne logo with background design
|
||||
var FyneScene = NewThemedResource(resourceFynescenedarkPng, resourceFynescenelightPng)
|
||||
@@ -1,203 +0,0 @@
|
||||
// Package main provides various examples of Fyne API capabilities.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/cmd/fyne_demo/tutorials"
|
||||
"fyne.io/fyne/v2/cmd/fyne_settings/settings"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
const preferenceCurrentTutorial = "currentTutorial"
|
||||
|
||||
var topWindow fyne.Window
|
||||
|
||||
func main() {
|
||||
a := app.NewWithID("io.fyne.demo")
|
||||
a.SetIcon(theme.FyneLogo())
|
||||
logLifecycle(a)
|
||||
w := a.NewWindow("Fyne Demo")
|
||||
topWindow = w
|
||||
|
||||
w.SetMainMenu(makeMenu(a, w))
|
||||
w.SetMaster()
|
||||
|
||||
content := container.NewMax()
|
||||
title := widget.NewLabel("Component name")
|
||||
intro := widget.NewLabel("An introduction would probably go\nhere, as well as a")
|
||||
intro.Wrapping = fyne.TextWrapWord
|
||||
setTutorial := func(t tutorials.Tutorial) {
|
||||
if fyne.CurrentDevice().IsMobile() {
|
||||
child := a.NewWindow(t.Title)
|
||||
topWindow = child
|
||||
child.SetContent(t.View(topWindow))
|
||||
child.Show()
|
||||
child.SetOnClosed(func() {
|
||||
topWindow = w
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
title.SetText(t.Title)
|
||||
intro.SetText(t.Intro)
|
||||
|
||||
content.Objects = []fyne.CanvasObject{t.View(w)}
|
||||
content.Refresh()
|
||||
}
|
||||
|
||||
tutorial := container.NewBorder(
|
||||
container.NewVBox(title, widget.NewSeparator(), intro), nil, nil, nil, content)
|
||||
if fyne.CurrentDevice().IsMobile() {
|
||||
w.SetContent(makeNav(setTutorial, false))
|
||||
} else {
|
||||
split := container.NewHSplit(makeNav(setTutorial, true), tutorial)
|
||||
split.Offset = 0.2
|
||||
w.SetContent(split)
|
||||
}
|
||||
w.Resize(fyne.NewSize(640, 460))
|
||||
w.ShowAndRun()
|
||||
}
|
||||
|
||||
func logLifecycle(a fyne.App) {
|
||||
a.Lifecycle().SetOnStarted(func() {
|
||||
log.Println("Lifecycle: Started")
|
||||
})
|
||||
a.Lifecycle().SetOnStopped(func() {
|
||||
log.Println("Lifecycle: Stopped")
|
||||
})
|
||||
a.Lifecycle().SetOnEnteredForeground(func() {
|
||||
log.Println("Lifecycle: Entered Foreground")
|
||||
})
|
||||
a.Lifecycle().SetOnExitedForeground(func() {
|
||||
log.Println("Lifecycle: Exited Foreground")
|
||||
})
|
||||
}
|
||||
|
||||
func makeMenu(a fyne.App, w fyne.Window) *fyne.MainMenu {
|
||||
newItem := fyne.NewMenuItem("New", nil)
|
||||
checkedItem := fyne.NewMenuItem("Checked", nil)
|
||||
checkedItem.Checked = true
|
||||
disabledItem := fyne.NewMenuItem("Disabled", nil)
|
||||
disabledItem.Disabled = true
|
||||
otherItem := fyne.NewMenuItem("Other", nil)
|
||||
otherItem.ChildMenu = fyne.NewMenu("",
|
||||
fyne.NewMenuItem("Project", func() { fmt.Println("Menu New->Other->Project") }),
|
||||
fyne.NewMenuItem("Mail", func() { fmt.Println("Menu New->Other->Mail") }),
|
||||
)
|
||||
newItem.ChildMenu = fyne.NewMenu("",
|
||||
fyne.NewMenuItem("File", func() { fmt.Println("Menu New->File") }),
|
||||
fyne.NewMenuItem("Directory", func() { fmt.Println("Menu New->Directory") }),
|
||||
otherItem,
|
||||
)
|
||||
settingsItem := fyne.NewMenuItem("Settings", func() {
|
||||
w := a.NewWindow("Fyne Settings")
|
||||
w.SetContent(settings.NewSettings().LoadAppearanceScreen(w))
|
||||
w.Resize(fyne.NewSize(480, 480))
|
||||
w.Show()
|
||||
})
|
||||
|
||||
cutItem := fyne.NewMenuItem("Cut", func() {
|
||||
shortcutFocused(&fyne.ShortcutCut{
|
||||
Clipboard: w.Clipboard(),
|
||||
}, w)
|
||||
})
|
||||
copyItem := fyne.NewMenuItem("Copy", func() {
|
||||
shortcutFocused(&fyne.ShortcutCopy{
|
||||
Clipboard: w.Clipboard(),
|
||||
}, w)
|
||||
})
|
||||
pasteItem := fyne.NewMenuItem("Paste", func() {
|
||||
shortcutFocused(&fyne.ShortcutPaste{
|
||||
Clipboard: w.Clipboard(),
|
||||
}, w)
|
||||
})
|
||||
findItem := fyne.NewMenuItem("Find", func() { fmt.Println("Menu Find") })
|
||||
|
||||
helpMenu := fyne.NewMenu("Help",
|
||||
fyne.NewMenuItem("Documentation", func() {
|
||||
u, _ := url.Parse("https://developer.fyne.io")
|
||||
_ = a.OpenURL(u)
|
||||
}),
|
||||
fyne.NewMenuItem("Support", func() {
|
||||
u, _ := url.Parse("https://fyne.io/support/")
|
||||
_ = a.OpenURL(u)
|
||||
}),
|
||||
fyne.NewMenuItemSeparator(),
|
||||
fyne.NewMenuItem("Sponsor", func() {
|
||||
u, _ := url.Parse("https://fyne.io/sponsor/")
|
||||
_ = a.OpenURL(u)
|
||||
}))
|
||||
|
||||
// a quit item will be appended to our first (File) menu
|
||||
file := fyne.NewMenu("File", newItem, checkedItem, disabledItem)
|
||||
if !fyne.CurrentDevice().IsMobile() {
|
||||
file.Items = append(file.Items, fyne.NewMenuItemSeparator(), settingsItem)
|
||||
}
|
||||
return fyne.NewMainMenu(
|
||||
file,
|
||||
fyne.NewMenu("Edit", cutItem, copyItem, pasteItem, fyne.NewMenuItemSeparator(), findItem),
|
||||
helpMenu,
|
||||
)
|
||||
}
|
||||
|
||||
func makeNav(setTutorial func(tutorial tutorials.Tutorial), loadPrevious bool) fyne.CanvasObject {
|
||||
a := fyne.CurrentApp()
|
||||
|
||||
tree := &widget.Tree{
|
||||
ChildUIDs: func(uid string) []string {
|
||||
return tutorials.TutorialIndex[uid]
|
||||
},
|
||||
IsBranch: func(uid string) bool {
|
||||
children, ok := tutorials.TutorialIndex[uid]
|
||||
|
||||
return ok && len(children) > 0
|
||||
},
|
||||
CreateNode: func(branch bool) fyne.CanvasObject {
|
||||
return widget.NewLabel("Collection Widgets")
|
||||
},
|
||||
UpdateNode: func(uid string, branch bool, obj fyne.CanvasObject) {
|
||||
t, ok := tutorials.Tutorials[uid]
|
||||
if !ok {
|
||||
fyne.LogError("Missing tutorial panel: "+uid, nil)
|
||||
return
|
||||
}
|
||||
obj.(*widget.Label).SetText(t.Title)
|
||||
},
|
||||
OnSelected: func(uid string) {
|
||||
if t, ok := tutorials.Tutorials[uid]; ok {
|
||||
a.Preferences().SetString(preferenceCurrentTutorial, uid)
|
||||
setTutorial(t)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
if loadPrevious {
|
||||
currentPref := a.Preferences().StringWithFallback(preferenceCurrentTutorial, "welcome")
|
||||
tree.Select(currentPref)
|
||||
}
|
||||
|
||||
themes := fyne.NewContainerWithLayout(layout.NewGridLayout(2),
|
||||
widget.NewButton("Dark", func() {
|
||||
a.Settings().SetTheme(theme.DarkTheme())
|
||||
}),
|
||||
widget.NewButton("Light", func() {
|
||||
a.Settings().SetTheme(theme.LightTheme())
|
||||
}),
|
||||
)
|
||||
|
||||
return container.NewBorder(nil, themes, nil, nil, tree)
|
||||
}
|
||||
|
||||
func shortcutFocused(s fyne.Shortcut, w fyne.Window) {
|
||||
if focused, ok := w.Canvas().Focused().(fyne.Shortcutable); ok {
|
||||
focused.TypedShortcut(s)
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func scaleString(c fyne.Canvas) string {
|
||||
return strconv.FormatFloat(float64(c.Scale()), 'f', 2, 32)
|
||||
}
|
||||
|
||||
func texScaleString(c fyne.Canvas) string {
|
||||
pixels, _ := c.PixelCoordinateForPosition(fyne.NewPos(1, 1))
|
||||
texScale := float32(pixels) / c.Scale()
|
||||
return strconv.FormatFloat(float64(texScale), 'f', 2, 32)
|
||||
}
|
||||
|
||||
func prependTo(g *fyne.Container, s string) {
|
||||
g.Objects = append([]fyne.CanvasObject{widget.NewLabel(s)}, g.Objects...)
|
||||
g.Refresh()
|
||||
}
|
||||
|
||||
func setScaleText(scale, tex *widget.Label, win fyne.Window) {
|
||||
for scale.Visible() {
|
||||
scale.SetText(scaleString(win.Canvas()))
|
||||
tex.SetText(texScaleString(win.Canvas()))
|
||||
|
||||
time.Sleep(time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// advancedScreen loads a panel that shows details and settings that are a bit
|
||||
// more detailed than normally needed.
|
||||
func advancedScreen(win fyne.Window) fyne.CanvasObject {
|
||||
scale := widget.NewLabel("")
|
||||
tex := widget.NewLabel("")
|
||||
|
||||
screen := widget.NewCard("Screen info", "", widget.NewForm(
|
||||
&widget.FormItem{Text: "Scale", Widget: scale},
|
||||
&widget.FormItem{Text: "Texture Scale", Widget: tex},
|
||||
))
|
||||
|
||||
go setScaleText(scale, tex, win)
|
||||
|
||||
label := widget.NewLabel("Just type...")
|
||||
generic := container.NewVBox()
|
||||
desk := container.NewVBox()
|
||||
|
||||
genericCard := widget.NewCard("", "Generic", container.NewVScroll(generic))
|
||||
deskCard := widget.NewCard("", "Desktop", container.NewVScroll(desk))
|
||||
|
||||
win.Canvas().SetOnTypedRune(func(r rune) {
|
||||
prependTo(generic, "Rune: "+string(r))
|
||||
})
|
||||
win.Canvas().SetOnTypedKey(func(ev *fyne.KeyEvent) {
|
||||
prependTo(generic, "Key : "+string(ev.Name))
|
||||
})
|
||||
if deskCanvas, ok := win.Canvas().(desktop.Canvas); ok {
|
||||
deskCanvas.SetOnKeyDown(func(ev *fyne.KeyEvent) {
|
||||
prependTo(desk, "KeyDown: "+string(ev.Name))
|
||||
})
|
||||
deskCanvas.SetOnKeyUp(func(ev *fyne.KeyEvent) {
|
||||
prependTo(desk, "KeyUp : "+string(ev.Name))
|
||||
})
|
||||
}
|
||||
|
||||
return container.NewHBox(
|
||||
container.NewVBox(screen,
|
||||
widget.NewButton("Custom Theme", func() {
|
||||
fyne.CurrentApp().Settings().SetTheme(newCustomTheme())
|
||||
}),
|
||||
widget.NewButton("Fullscreen", func() {
|
||||
win.SetFullScreen(!win.FullScreen())
|
||||
}),
|
||||
),
|
||||
container.NewBorder(label, nil, nil, nil,
|
||||
container.NewGridWithColumns(2, genericCard, deskCard),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func makeAnimationScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
curves := makeAnimationCurves()
|
||||
curves.Move(fyne.NewPos(0, 140+theme.Padding()))
|
||||
return fyne.NewContainerWithoutLayout(makeAnimationCanvas(), curves)
|
||||
}
|
||||
|
||||
func makeAnimationCanvas() fyne.CanvasObject {
|
||||
rect := canvas.NewRectangle(color.Black)
|
||||
rect.Resize(fyne.NewSize(410, 140))
|
||||
|
||||
a := canvas.NewColorRGBAAnimation(theme.PrimaryColorNamed(theme.ColorBlue), theme.PrimaryColorNamed(theme.ColorGreen),
|
||||
time.Second*3, func(c color.Color) {
|
||||
rect.FillColor = c
|
||||
canvas.Refresh(rect)
|
||||
})
|
||||
a.RepeatCount = fyne.AnimationRepeatForever
|
||||
a.AutoReverse = true
|
||||
a.Start()
|
||||
|
||||
var a2 *fyne.Animation
|
||||
i := widget.NewIcon(theme.CheckButtonCheckedIcon())
|
||||
a2 = canvas.NewPositionAnimation(fyne.NewPos(0, 0), fyne.NewPos(350, 80), time.Second*3, func(p fyne.Position) {
|
||||
i.Move(p)
|
||||
|
||||
width := 10 + (p.X / 7)
|
||||
i.Resize(fyne.NewSize(width, width))
|
||||
})
|
||||
a2.RepeatCount = fyne.AnimationRepeatForever
|
||||
a2.AutoReverse = true
|
||||
a2.Curve = fyne.AnimationLinear
|
||||
a2.Start()
|
||||
|
||||
running := true
|
||||
var toggle *widget.Button
|
||||
toggle = widget.NewButton("Stop", func() {
|
||||
if running {
|
||||
a.Stop()
|
||||
a2.Stop()
|
||||
toggle.SetText("Start")
|
||||
} else {
|
||||
a.Start()
|
||||
a2.Start()
|
||||
toggle.SetText("Stop")
|
||||
}
|
||||
running = !running
|
||||
})
|
||||
toggle.Resize(toggle.MinSize())
|
||||
toggle.Move(fyne.NewPos(152, 54))
|
||||
return fyne.NewContainerWithoutLayout(rect, i, toggle)
|
||||
}
|
||||
|
||||
func makeAnimationCurves() fyne.CanvasObject {
|
||||
label1, box1, a1 := makeAnimationCurveItem("EaseInOut", fyne.AnimationEaseInOut, 0)
|
||||
label2, box2, a2 := makeAnimationCurveItem("EaseIn", fyne.AnimationEaseIn, 30+theme.Padding())
|
||||
label3, box3, a3 := makeAnimationCurveItem("EaseOut", fyne.AnimationEaseOut, 60+theme.Padding()*2)
|
||||
label4, box4, a4 := makeAnimationCurveItem("Linear", fyne.AnimationLinear, 90+theme.Padding()*3)
|
||||
|
||||
start := widget.NewButton("Compare", func() {
|
||||
a1.Start()
|
||||
a2.Start()
|
||||
a3.Start()
|
||||
a4.Start()
|
||||
})
|
||||
start.Resize(start.MinSize())
|
||||
start.Move(fyne.NewPos(0, 120+theme.Padding()*4))
|
||||
return fyne.NewContainerWithoutLayout(label1, label2, label3, label4, box1, box2, box3, box4, start)
|
||||
}
|
||||
|
||||
func makeAnimationCurveItem(label string, curve fyne.AnimationCurve, yOff float32) (
|
||||
text *widget.Label, box fyne.CanvasObject, anim *fyne.Animation) {
|
||||
text = widget.NewLabel(label)
|
||||
text.Alignment = fyne.TextAlignCenter
|
||||
text.Resize(fyne.NewSize(380, 30))
|
||||
text.Move(fyne.NewPos(0, yOff))
|
||||
box = newThemedBox()
|
||||
box.Resize(fyne.NewSize(30, 30))
|
||||
box.Move(fyne.NewPos(0, yOff))
|
||||
|
||||
anim = canvas.NewPositionAnimation(
|
||||
fyne.NewPos(0, yOff), fyne.NewPos(380, yOff), time.Second, func(p fyne.Position) {
|
||||
box.Move(p)
|
||||
box.Refresh()
|
||||
})
|
||||
anim.Curve = curve
|
||||
anim.AutoReverse = true
|
||||
anim.RepeatCount = 1
|
||||
return
|
||||
}
|
||||
|
||||
// themedBox is a simple box that change its background color according
|
||||
// to the selected theme
|
||||
type themedBox struct {
|
||||
widget.BaseWidget
|
||||
}
|
||||
|
||||
func newThemedBox() *themedBox {
|
||||
b := &themedBox{}
|
||||
b.ExtendBaseWidget(b)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *themedBox) CreateRenderer() fyne.WidgetRenderer {
|
||||
b.ExtendBaseWidget(b)
|
||||
bg := canvas.NewRectangle(theme.ForegroundColor())
|
||||
return &themedBoxRenderer{bg: bg, objects: []fyne.CanvasObject{bg}}
|
||||
}
|
||||
|
||||
type themedBoxRenderer struct {
|
||||
bg *canvas.Rectangle
|
||||
objects []fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (r *themedBoxRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (r *themedBoxRenderer) Layout(size fyne.Size) {
|
||||
r.bg.Resize(size)
|
||||
}
|
||||
|
||||
func (r *themedBoxRenderer) MinSize() fyne.Size {
|
||||
return r.bg.MinSize()
|
||||
}
|
||||
|
||||
func (r *themedBoxRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects
|
||||
}
|
||||
|
||||
func (r *themedBoxRenderer) Refresh() {
|
||||
r.bg.FillColor = theme.ForegroundColor()
|
||||
r.bg.Refresh()
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/binding"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func bindingScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
f := 0.2
|
||||
data := binding.BindFloat(&f)
|
||||
label := widget.NewLabelWithData(binding.FloatToStringWithFormat(data, "Float value: %0.2f"))
|
||||
entry := widget.NewEntryWithData(binding.FloatToString(data))
|
||||
floats := container.NewGridWithColumns(2, label, entry)
|
||||
|
||||
slide := widget.NewSliderWithData(0, 1, data)
|
||||
slide.Step = 0.01
|
||||
bar := widget.NewProgressBarWithData(data)
|
||||
|
||||
buttons := container.NewGridWithColumns(4,
|
||||
widget.NewButton("0%", func() {
|
||||
data.Set(0)
|
||||
}),
|
||||
widget.NewButton("30%", func() {
|
||||
data.Set(0.3)
|
||||
}),
|
||||
widget.NewButton("70%", func() {
|
||||
data.Set(0.7)
|
||||
}),
|
||||
widget.NewButton("100%", func() {
|
||||
data.Set(1)
|
||||
}))
|
||||
|
||||
boolData := binding.NewBool()
|
||||
check := widget.NewCheckWithData("Check me!", boolData)
|
||||
checkLabel := widget.NewLabelWithData(binding.BoolToString(boolData))
|
||||
checkEntry := widget.NewEntryWithData(binding.BoolToString(boolData))
|
||||
checks := container.NewGridWithColumns(3, check, checkLabel, checkEntry)
|
||||
item := container.NewVBox(floats, slide, bar, buttons, widget.NewSeparator(), checks, widget.NewSeparator())
|
||||
|
||||
dataList := binding.BindFloatList(&[]float64{0.1, 0.2, 0.3})
|
||||
|
||||
button := widget.NewButton("Append", func() {
|
||||
dataList.Append(float64(dataList.Length()+1) / 10)
|
||||
})
|
||||
|
||||
list := widget.NewListWithData(dataList,
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewBorder(nil, nil, nil, widget.NewButton("+", nil),
|
||||
widget.NewLabel("item x.y"))
|
||||
},
|
||||
func(item binding.DataItem, obj fyne.CanvasObject) {
|
||||
f := item.(binding.Float)
|
||||
text := obj.(*fyne.Container).Objects[0].(*widget.Label)
|
||||
text.Bind(binding.FloatToStringWithFormat(f, "item %0.1f"))
|
||||
|
||||
btn := obj.(*fyne.Container).Objects[1].(*widget.Button)
|
||||
btn.OnTapped = func() {
|
||||
val, _ := f.Get()
|
||||
_ = f.Set(val + 1)
|
||||
}
|
||||
})
|
||||
|
||||
formStruct := struct {
|
||||
Name, Email string
|
||||
Subscribe bool
|
||||
}{}
|
||||
|
||||
formData := binding.BindStruct(&formStruct)
|
||||
form := newFormWithData(formData)
|
||||
form.OnSubmit = func() {
|
||||
fmt.Println("Struct:\n", formStruct)
|
||||
}
|
||||
|
||||
listPanel := container.NewBorder(nil, button, nil, nil, list)
|
||||
return container.NewBorder(item, nil, nil, nil, container.NewGridWithColumns(2, listPanel, form))
|
||||
}
|
||||
|
||||
func newFormWithData(data binding.DataMap) *widget.Form {
|
||||
keys := data.Keys()
|
||||
items := make([]*widget.FormItem, len(keys))
|
||||
for i, k := range keys {
|
||||
data, err := data.GetItem(k)
|
||||
if err != nil {
|
||||
items[i] = widget.NewFormItem(k, widget.NewLabel(err.Error()))
|
||||
}
|
||||
items[i] = widget.NewFormItem(k, createBoundItem(data))
|
||||
}
|
||||
|
||||
return widget.NewForm(items...)
|
||||
}
|
||||
|
||||
func createBoundItem(v binding.DataItem) fyne.CanvasObject {
|
||||
switch val := v.(type) {
|
||||
case binding.Bool:
|
||||
return widget.NewCheckWithData("", val)
|
||||
case binding.Float:
|
||||
s := widget.NewSliderWithData(0, 1, val)
|
||||
s.Step = 0.01
|
||||
return s
|
||||
case binding.Int:
|
||||
return widget.NewEntryWithData(binding.IntToString(val))
|
||||
case binding.String:
|
||||
return widget.NewEntryWithData(val)
|
||||
default:
|
||||
return widget.NewLabel("")
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
func rgbGradient(x, y, w, h int) color.Color {
|
||||
g := int(float32(x) / float32(w) * float32(255))
|
||||
b := int(float32(y) / float32(h) * float32(255))
|
||||
|
||||
return color.NRGBA{uint8(255 - b), uint8(g), uint8(b), 0xff}
|
||||
}
|
||||
|
||||
// canvasScreen loads a graphics example panel for the demo app
|
||||
func canvasScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
gradient := canvas.NewHorizontalGradient(color.NRGBA{0x80, 0, 0, 0xff}, color.NRGBA{0, 0x80, 0, 0xff})
|
||||
go func() {
|
||||
for {
|
||||
time.Sleep(time.Second)
|
||||
|
||||
gradient.Angle += 45
|
||||
if gradient.Angle >= 360 {
|
||||
gradient.Angle -= 360
|
||||
}
|
||||
canvas.Refresh(gradient)
|
||||
}
|
||||
}()
|
||||
|
||||
return fyne.NewContainerWithLayout(layout.NewGridWrapLayout(fyne.NewSize(90, 90)),
|
||||
canvas.NewImageFromResource(theme.FyneLogo()),
|
||||
&canvas.Rectangle{FillColor: color.NRGBA{0x80, 0, 0, 0xff},
|
||||
StrokeColor: color.NRGBA{0xff, 0xff, 0xff, 0xff},
|
||||
StrokeWidth: 1},
|
||||
&canvas.Line{StrokeColor: color.NRGBA{0, 0, 0x80, 0xff}, StrokeWidth: 5},
|
||||
&canvas.Circle{StrokeColor: color.NRGBA{0, 0, 0x80, 0xff},
|
||||
FillColor: color.NRGBA{0x30, 0x30, 0x30, 0x60},
|
||||
StrokeWidth: 2},
|
||||
canvas.NewText("Text", color.NRGBA{0, 0x80, 0, 0xff}),
|
||||
canvas.NewRasterWithPixels(rgbGradient),
|
||||
gradient,
|
||||
canvas.NewRadialGradient(color.NRGBA{0x80, 0, 0, 0xff}, color.NRGBA{0, 0x80, 0x80, 0xff}),
|
||||
)
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// collectionScreen loads a tab panel for collection widgets
|
||||
func collectionScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
content := container.NewVBox(
|
||||
widget.NewLabelWithStyle("func Length() int", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}),
|
||||
widget.NewLabelWithStyle("func CreateItem() fyne.CanvasObject", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}),
|
||||
widget.NewLabelWithStyle("func UpdateItem(ListItemID, fyne.CanvasObject)", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}),
|
||||
widget.NewLabelWithStyle("func OnSelected(ListItemID)", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}),
|
||||
widget.NewLabelWithStyle("func OnUnselected(ListItemID)", fyne.TextAlignLeading, fyne.TextStyle{Monospace: true}))
|
||||
return container.NewCenter(content)
|
||||
}
|
||||
|
||||
func makeListTab(_ fyne.Window) fyne.CanvasObject {
|
||||
data := make([]string, 1000)
|
||||
for i := range data {
|
||||
data[i] = "Test Item " + strconv.Itoa(i)
|
||||
}
|
||||
|
||||
icon := widget.NewIcon(nil)
|
||||
label := widget.NewLabel("Select An Item From The List")
|
||||
hbox := container.NewHBox(icon, label)
|
||||
|
||||
list := widget.NewList(
|
||||
func() int {
|
||||
return len(data)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return container.NewHBox(widget.NewIcon(theme.DocumentIcon()), widget.NewLabel("Template Object"))
|
||||
},
|
||||
func(id widget.ListItemID, item fyne.CanvasObject) {
|
||||
item.(*fyne.Container).Objects[1].(*widget.Label).SetText(data[id])
|
||||
},
|
||||
)
|
||||
list.OnSelected = func(id widget.ListItemID) {
|
||||
label.SetText(data[id])
|
||||
icon.SetResource(theme.DocumentIcon())
|
||||
}
|
||||
list.OnUnselected = func(id widget.ListItemID) {
|
||||
label.SetText("Select An Item From The List")
|
||||
icon.SetResource(nil)
|
||||
}
|
||||
list.Select(125)
|
||||
|
||||
return container.NewHSplit(list, container.NewCenter(hbox))
|
||||
}
|
||||
|
||||
func makeTableTab(_ fyne.Window) fyne.CanvasObject {
|
||||
t := widget.NewTable(
|
||||
func() (int, int) { return 500, 150 },
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("Cell 000, 000")
|
||||
},
|
||||
func(id widget.TableCellID, cell fyne.CanvasObject) {
|
||||
label := cell.(*widget.Label)
|
||||
switch id.Col {
|
||||
case 0:
|
||||
label.SetText(fmt.Sprintf("%d", id.Row+1))
|
||||
case 1:
|
||||
label.SetText("A longer cell")
|
||||
default:
|
||||
label.SetText(fmt.Sprintf("Cell %d, %d", id.Row+1, id.Col+1))
|
||||
}
|
||||
})
|
||||
t.SetColumnWidth(0, 34)
|
||||
t.SetColumnWidth(1, 102)
|
||||
return t
|
||||
}
|
||||
|
||||
func makeTreeTab(_ fyne.Window) fyne.CanvasObject {
|
||||
data := map[string][]string{
|
||||
"": {"A"},
|
||||
"A": {"B", "D", "H", "J", "L", "O", "P", "S", "V"},
|
||||
"B": {"C"},
|
||||
"C": {"abc"},
|
||||
"D": {"E"},
|
||||
"E": {"F", "G"},
|
||||
"F": {"adef"},
|
||||
"G": {"adeg"},
|
||||
"H": {"I"},
|
||||
"I": {"ahi"},
|
||||
"O": {"ao"},
|
||||
"P": {"Q"},
|
||||
"Q": {"R"},
|
||||
"R": {"apqr"},
|
||||
"S": {"T"},
|
||||
"T": {"U"},
|
||||
"U": {"astu"},
|
||||
"V": {"W"},
|
||||
"W": {"X"},
|
||||
"X": {"Y"},
|
||||
"Y": {"Z"},
|
||||
"Z": {"avwxyz"},
|
||||
}
|
||||
|
||||
tree := widget.NewTreeWithStrings(data)
|
||||
tree.OnSelected = func(id string) {
|
||||
fmt.Println("Tree node selected:", id)
|
||||
}
|
||||
tree.OnUnselected = func(id string) {
|
||||
fmt.Println("Tree node unselected:", id)
|
||||
}
|
||||
tree.OpenBranch("A")
|
||||
tree.OpenBranch("D")
|
||||
tree.OpenBranch("E")
|
||||
tree.OpenBranch("L")
|
||||
tree.OpenBranch("M")
|
||||
return tree
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strconv"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
// containerScreen loads a tab panel for containers
|
||||
func containerScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
content := container.NewBorder(
|
||||
widget.NewLabelWithStyle("Top", fyne.TextAlignCenter, fyne.TextStyle{}),
|
||||
widget.NewLabelWithStyle("Bottom", fyne.TextAlignCenter, fyne.TextStyle{}),
|
||||
widget.NewLabel("Left"),
|
||||
widget.NewLabel("Right"),
|
||||
widget.NewLabel("Border Container"))
|
||||
return container.NewCenter(content)
|
||||
}
|
||||
|
||||
func makeAppTabsTab(_ fyne.Window) fyne.CanvasObject {
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem("Tab 1", widget.NewLabel("Content of tab 1")),
|
||||
container.NewTabItem("Tab 2 bigger", widget.NewLabel("Content of tab 2")),
|
||||
container.NewTabItem("Tab 3", widget.NewLabel("Content of tab 3")),
|
||||
)
|
||||
for i := 4; i <= 12; i++ {
|
||||
tabs.Append(container.NewTabItem(fmt.Sprintf("Tab %d", i), widget.NewLabel(fmt.Sprintf("Content of tab %d", i))))
|
||||
}
|
||||
locations := makeTabLocationSelect(tabs.SetTabLocation)
|
||||
return container.NewBorder(locations, nil, nil, nil, tabs)
|
||||
}
|
||||
|
||||
func makeBorderLayout(_ fyne.Window) fyne.CanvasObject {
|
||||
top := makeCell()
|
||||
bottom := makeCell()
|
||||
left := makeCell()
|
||||
right := makeCell()
|
||||
middle := widget.NewLabelWithStyle("BorderLayout", fyne.TextAlignCenter, fyne.TextStyle{})
|
||||
|
||||
return container.NewBorder(top, bottom, left, right, middle)
|
||||
}
|
||||
|
||||
func makeBoxLayout(_ fyne.Window) fyne.CanvasObject {
|
||||
top := makeCell()
|
||||
bottom := makeCell()
|
||||
middle := widget.NewLabel("BoxLayout")
|
||||
center := makeCell()
|
||||
right := makeCell()
|
||||
|
||||
col := container.NewVBox(top, middle, bottom)
|
||||
|
||||
return container.NewHBox(col, center, right)
|
||||
}
|
||||
|
||||
func makeButtonList(count int) []fyne.CanvasObject {
|
||||
var items []fyne.CanvasObject
|
||||
for i := 1; i <= count; i++ {
|
||||
index := i // capture
|
||||
items = append(items, widget.NewButton("Button "+strconv.Itoa(index), func() {
|
||||
fmt.Println("Tapped", index)
|
||||
}))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
|
||||
func makeCell() fyne.CanvasObject {
|
||||
rect := canvas.NewRectangle(&color.NRGBA{128, 128, 128, 255})
|
||||
rect.SetMinSize(fyne.NewSize(30, 30))
|
||||
return rect
|
||||
}
|
||||
|
||||
func makeCenterLayout(_ fyne.Window) fyne.CanvasObject {
|
||||
middle := widget.NewButton("CenterLayout", func() {})
|
||||
|
||||
return container.NewCenter(middle)
|
||||
}
|
||||
|
||||
func makeDocTabsTab(_ fyne.Window) fyne.CanvasObject {
|
||||
tabs := container.NewDocTabs(
|
||||
container.NewTabItem("Doc 1", widget.NewLabel("Content of document 1")),
|
||||
container.NewTabItem("Doc 2 bigger", widget.NewLabel("Content of document 2")),
|
||||
container.NewTabItem("Doc 3", widget.NewLabel("Content of document 3")),
|
||||
)
|
||||
i := 3
|
||||
tabs.CreateTab = func() *container.TabItem {
|
||||
i++
|
||||
return container.NewTabItem(fmt.Sprintf("Doc %d", i), widget.NewLabel(fmt.Sprintf("Content of document %d", i)))
|
||||
}
|
||||
locations := makeTabLocationSelect(tabs.SetTabLocation)
|
||||
return container.NewBorder(locations, nil, nil, nil, tabs)
|
||||
}
|
||||
|
||||
func makeGridLayout(_ fyne.Window) fyne.CanvasObject {
|
||||
box1 := makeCell()
|
||||
box2 := widget.NewLabel("Grid")
|
||||
box3 := makeCell()
|
||||
box4 := makeCell()
|
||||
|
||||
return container.NewGridWithColumns(2,
|
||||
box1, box2, box3, box4)
|
||||
}
|
||||
|
||||
func makeScrollTab(_ fyne.Window) fyne.CanvasObject {
|
||||
hlist := makeButtonList(20)
|
||||
vlist := makeButtonList(50)
|
||||
|
||||
horiz := container.NewHScroll(container.NewHBox(hlist...))
|
||||
vert := container.NewVScroll(container.NewVBox(vlist...))
|
||||
|
||||
return container.NewAdaptiveGrid(2,
|
||||
container.NewBorder(horiz, nil, nil, nil, vert),
|
||||
makeScrollBothTab())
|
||||
}
|
||||
|
||||
func makeScrollBothTab() fyne.CanvasObject {
|
||||
logo := canvas.NewImageFromResource(theme.FyneLogo())
|
||||
logo.SetMinSize(fyne.NewSize(800, 800))
|
||||
|
||||
scroll := container.NewScroll(logo)
|
||||
scroll.Resize(fyne.NewSize(400, 400))
|
||||
|
||||
return scroll
|
||||
}
|
||||
|
||||
func makeSplitTab(_ fyne.Window) fyne.CanvasObject {
|
||||
left := widget.NewMultiLineEntry()
|
||||
left.Wrapping = fyne.TextWrapWord
|
||||
left.SetText("Long text is looooooooooooooong")
|
||||
right := container.NewVSplit(
|
||||
widget.NewLabel("Label"),
|
||||
widget.NewButton("Button", func() { fmt.Println("button tapped!") }),
|
||||
)
|
||||
return container.NewHSplit(container.NewVScroll(left), right)
|
||||
}
|
||||
|
||||
func makeTabLocationSelect(callback func(container.TabLocation)) *widget.Select {
|
||||
locations := widget.NewSelect([]string{"Top", "Bottom", "Leading", "Trailing"}, func(s string) {
|
||||
callback(map[string]container.TabLocation{
|
||||
"Top": container.TabLocationTop,
|
||||
"Bottom": container.TabLocationBottom,
|
||||
"Leading": container.TabLocationLeading,
|
||||
"Trailing": container.TabLocationTrailing,
|
||||
}[s])
|
||||
})
|
||||
locations.SetSelected("Top")
|
||||
return locations
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// Tutorial defines the data structure for a tutorial
|
||||
type Tutorial struct {
|
||||
Title, Intro string
|
||||
View func(w fyne.Window) fyne.CanvasObject
|
||||
}
|
||||
|
||||
var (
|
||||
// Tutorials defines the metadata for each tutorial
|
||||
Tutorials = map[string]Tutorial{
|
||||
"welcome": {"Welcome", "", welcomeScreen},
|
||||
"canvas": {"Canvas",
|
||||
"See the canvas capabilities.",
|
||||
canvasScreen,
|
||||
},
|
||||
"animations": {"Animations",
|
||||
"See how to animate components.",
|
||||
makeAnimationScreen,
|
||||
},
|
||||
"icons": {"Theme Icons",
|
||||
"Browse the embedded icons.",
|
||||
iconScreen,
|
||||
},
|
||||
"containers": {"Containers",
|
||||
"Containers group other widgets and canvas objects, organising according to their layout.\n" +
|
||||
"Standard containers are illustrated in this section, but developers can also provide custom " +
|
||||
"layouts using the fyne.NewContainerWithLayout() constructor.",
|
||||
containerScreen,
|
||||
},
|
||||
"apptabs": {"AppTabs",
|
||||
"A container to help divide up an application into functional areas.",
|
||||
makeAppTabsTab,
|
||||
},
|
||||
"border": {"Border",
|
||||
"A container that positions items around a central content.",
|
||||
makeBorderLayout,
|
||||
},
|
||||
"box": {"Box",
|
||||
"A container arranges items in horizontal or vertical list.",
|
||||
makeBoxLayout,
|
||||
},
|
||||
"center": {"Center",
|
||||
"A container to that centers child elements.",
|
||||
makeCenterLayout,
|
||||
},
|
||||
"doctabs": {"DocTabs",
|
||||
"A container to display a single document from a set of many.",
|
||||
makeDocTabsTab,
|
||||
},
|
||||
"grid": {"Grid",
|
||||
"A container that arranges all items in a grid.",
|
||||
makeGridLayout,
|
||||
},
|
||||
"split": {"Split",
|
||||
"A split container divides the container in two pieces that the user can resize.",
|
||||
makeSplitTab,
|
||||
},
|
||||
"scroll": {"Scroll",
|
||||
"A container that provides scrolling for it's content.",
|
||||
makeScrollTab,
|
||||
},
|
||||
"widgets": {"Widgets",
|
||||
"In this section you can see the features available in the toolkit widget set.\n" +
|
||||
"Expand the tree on the left to browse the individual tutorial elements.",
|
||||
widgetScreen,
|
||||
},
|
||||
"accordion": {"Accordion",
|
||||
"Expand or collapse content panels.",
|
||||
makeAccordionTab,
|
||||
},
|
||||
"button": {"Button",
|
||||
"Simple widget for user tap handling.",
|
||||
makeButtonTab,
|
||||
},
|
||||
"card": {"Card",
|
||||
"Group content and widgets.",
|
||||
makeCardTab,
|
||||
},
|
||||
"entry": {"Entry",
|
||||
"Different ways to use the entry widget.",
|
||||
makeEntryTab,
|
||||
},
|
||||
"form": {"Form",
|
||||
"Gathering input widgets for data submission.",
|
||||
makeFormTab,
|
||||
},
|
||||
"input": {"Input",
|
||||
"A collection of widgets for user input.",
|
||||
makeInputTab,
|
||||
},
|
||||
"text": {"Text",
|
||||
"Text handling widgets.",
|
||||
makeTextTab,
|
||||
},
|
||||
"toolbar": {"Toolbar",
|
||||
"A row of shortcut icons for common tasks.",
|
||||
makeToolbarTab,
|
||||
},
|
||||
"progress": {"Progress",
|
||||
"Show duration or the need to wait for a task.",
|
||||
makeProgressTab,
|
||||
},
|
||||
"collections": {"Collections",
|
||||
"Collection widgets provide an efficient way to present lots of content.\n" +
|
||||
"The List, Table, and Tree provide a cache and re-use mechanism that make it possible to scroll through thousands of elements.\n" +
|
||||
"Use this for large data sets or for collections that can expand as users scroll.",
|
||||
collectionScreen,
|
||||
},
|
||||
"list": {"List",
|
||||
"A vertical arrangement of cached elements with the same styling.",
|
||||
makeListTab,
|
||||
},
|
||||
"table": {"Table",
|
||||
"A two dimensional cached collection of cells.",
|
||||
makeTableTab,
|
||||
},
|
||||
"tree": {"Tree",
|
||||
"A tree based arrangement of cached elements with the same styling.",
|
||||
makeTreeTab,
|
||||
},
|
||||
"dialogs": {"Dialogs",
|
||||
"Work with dialogs.",
|
||||
dialogScreen,
|
||||
},
|
||||
"windows": {"Windows",
|
||||
"Window function demo.",
|
||||
windowScreen,
|
||||
},
|
||||
"binding": {"Data Binding",
|
||||
"Connecting widgets to a data source.",
|
||||
bindingScreen},
|
||||
"advanced": {"Advanced",
|
||||
"Debug and advanced information.",
|
||||
advancedScreen,
|
||||
},
|
||||
}
|
||||
|
||||
// TutorialIndex defines how our tutorials should be laid out in the index tree
|
||||
TutorialIndex = map[string][]string{
|
||||
"": {"welcome", "canvas", "animations", "icons", "widgets", "collections", "containers", "dialogs", "windows", "binding", "advanced"},
|
||||
"collections": {"list", "table", "tree"},
|
||||
"containers": {"apptabs", "border", "box", "center", "doctabs", "grid", "scroll", "split"},
|
||||
"widgets": {"accordion", "button", "card", "entry", "form", "input", "progress", "text", "toolbar"},
|
||||
}
|
||||
)
|
||||
@@ -1,185 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"image/color"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/validation"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/storage"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func confirmCallback(response bool) {
|
||||
fmt.Println("Responded with", response)
|
||||
}
|
||||
|
||||
func colorPicked(c color.Color, w fyne.Window) {
|
||||
log.Println("Color picked:", c)
|
||||
rectangle := canvas.NewRectangle(c)
|
||||
size := 2 * theme.IconInlineSize()
|
||||
rectangle.SetMinSize(fyne.NewSize(size, size))
|
||||
dialog.ShowCustom("Color Picked", "Ok", rectangle, w)
|
||||
}
|
||||
|
||||
// dialogScreen loads demos of the dialogs we support
|
||||
func dialogScreen(win fyne.Window) fyne.CanvasObject {
|
||||
return container.NewVScroll(container.NewVBox(
|
||||
widget.NewButton("Info", func() {
|
||||
dialog.ShowInformation("Information", "You should know this thing...", win)
|
||||
}),
|
||||
widget.NewButton("Error", func() {
|
||||
err := errors.New("a dummy error message")
|
||||
dialog.ShowError(err, win)
|
||||
}),
|
||||
widget.NewButton("Confirm", func() {
|
||||
cnf := dialog.NewConfirm("Confirmation", "Are you enjoying this demo?", confirmCallback, win)
|
||||
cnf.SetDismissText("Nah")
|
||||
cnf.SetConfirmText("Oh Yes!")
|
||||
cnf.Show()
|
||||
}),
|
||||
widget.NewButton("File Open With Filter (.jpg or .png)", func() {
|
||||
fd := dialog.NewFileOpen(func(reader fyne.URIReadCloser, err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, win)
|
||||
return
|
||||
}
|
||||
if reader == nil {
|
||||
log.Println("Cancelled")
|
||||
return
|
||||
}
|
||||
|
||||
imageOpened(reader)
|
||||
}, win)
|
||||
fd.SetFilter(storage.NewExtensionFileFilter([]string{".png", ".jpg", ".jpeg"}))
|
||||
fd.Show()
|
||||
}),
|
||||
widget.NewButton("File Save", func() {
|
||||
dialog.ShowFileSave(func(writer fyne.URIWriteCloser, err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, win)
|
||||
return
|
||||
}
|
||||
if writer == nil {
|
||||
log.Println("Cancelled")
|
||||
return
|
||||
}
|
||||
|
||||
fileSaved(writer, win)
|
||||
}, win)
|
||||
}),
|
||||
widget.NewButton("Folder Open", func() {
|
||||
dialog.ShowFolderOpen(func(list fyne.ListableURI, err error) {
|
||||
if err != nil {
|
||||
dialog.ShowError(err, win)
|
||||
return
|
||||
}
|
||||
if list == nil {
|
||||
log.Println("Cancelled")
|
||||
return
|
||||
}
|
||||
|
||||
children, err := list.List()
|
||||
if err != nil {
|
||||
dialog.ShowError(err, win)
|
||||
return
|
||||
}
|
||||
out := fmt.Sprintf("Folder %s (%d children):\n%s", list.Name(), len(children), list.String())
|
||||
dialog.ShowInformation("Folder Open", out, win)
|
||||
}, win)
|
||||
}),
|
||||
widget.NewButton("Color Picker", func() {
|
||||
picker := dialog.NewColorPicker("Pick a Color", "What is your favorite color?", func(c color.Color) {
|
||||
colorPicked(c, win)
|
||||
}, win)
|
||||
picker.Show()
|
||||
}),
|
||||
widget.NewButton("Advanced Color Picker", func() {
|
||||
picker := dialog.NewColorPicker("Pick a Color", "What is your favorite color?", func(c color.Color) {
|
||||
colorPicked(c, win)
|
||||
}, win)
|
||||
picker.Advanced = true
|
||||
picker.Show()
|
||||
}),
|
||||
widget.NewButton("Form Dialog (Login Form)", func() {
|
||||
username := widget.NewEntry()
|
||||
username.Validator = validation.NewRegexp(`^[A-Za-z0-9_-]+$`, "username can only contain letters, numbers, '_', and '-'")
|
||||
password := widget.NewPasswordEntry()
|
||||
password.Validator = validation.NewRegexp(`^[A-Za-z0-9_-]+$`, "password can only contain letters, numbers, '_', and '-'")
|
||||
remember := false
|
||||
items := []*widget.FormItem{
|
||||
widget.NewFormItem("Username", username),
|
||||
widget.NewFormItem("Password", password),
|
||||
widget.NewFormItem("Remember me", widget.NewCheck("", func(checked bool) {
|
||||
remember = checked
|
||||
})),
|
||||
}
|
||||
|
||||
dialog.ShowForm("Login...", "Log In", "Cancel", items, func(b bool) {
|
||||
if !b {
|
||||
return
|
||||
}
|
||||
var rememberText string
|
||||
if remember {
|
||||
rememberText = "and remember this login"
|
||||
}
|
||||
|
||||
log.Println("Please Authenticate", username.Text, password.Text, rememberText)
|
||||
}, win)
|
||||
}),
|
||||
))
|
||||
}
|
||||
|
||||
func imageOpened(f fyne.URIReadCloser) {
|
||||
if f == nil {
|
||||
log.Println("Cancelled")
|
||||
return
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
showImage(f)
|
||||
}
|
||||
|
||||
func fileSaved(f fyne.URIWriteCloser, w fyne.Window) {
|
||||
defer f.Close()
|
||||
_, err := f.Write([]byte("Written by Fyne demo\n"))
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
err = f.Close()
|
||||
if err != nil {
|
||||
dialog.ShowError(err, w)
|
||||
}
|
||||
log.Println("Saved to...", f.URI())
|
||||
}
|
||||
|
||||
func loadImage(f fyne.URIReadCloser) *canvas.Image {
|
||||
data, err := ioutil.ReadAll(f)
|
||||
if err != nil {
|
||||
fyne.LogError("Failed to load image data", err)
|
||||
return nil
|
||||
}
|
||||
res := fyne.NewStaticResource(f.URI().Name(), data)
|
||||
|
||||
return canvas.NewImageFromResource(res)
|
||||
}
|
||||
|
||||
func showImage(f fyne.URIReadCloser) {
|
||||
img := loadImage(f)
|
||||
if img == nil {
|
||||
return
|
||||
}
|
||||
img.FillMode = canvas.ImageFillOriginal
|
||||
|
||||
w := fyne.CurrentApp().NewWindow(f.URI().Name())
|
||||
w.SetContent(container.NewScroll(img))
|
||||
w.Resize(fyne.NewSize(320, 240))
|
||||
w.Show()
|
||||
}
|
||||
@@ -1,195 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type iconInfo struct {
|
||||
name string
|
||||
icon fyne.Resource
|
||||
}
|
||||
|
||||
type browser struct {
|
||||
current int
|
||||
icons []iconInfo
|
||||
|
||||
name *widget.Select
|
||||
icon *widget.Icon
|
||||
}
|
||||
|
||||
func (b *browser) setIcon(index int) {
|
||||
if index < 0 || index > len(b.icons)-1 {
|
||||
return
|
||||
}
|
||||
b.current = index
|
||||
|
||||
b.name.SetSelected(b.icons[index].name)
|
||||
b.icon.SetResource(b.icons[index].icon)
|
||||
}
|
||||
|
||||
// iconScreen loads a panel that shows the various icons available in Fyne
|
||||
func iconScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
b := &browser{}
|
||||
b.icons = loadIcons()
|
||||
|
||||
prev := widget.NewButtonWithIcon("", theme.NavigateBackIcon(), func() {
|
||||
b.setIcon(b.current - 1)
|
||||
})
|
||||
next := widget.NewButtonWithIcon("", theme.NavigateNextIcon(), func() {
|
||||
b.setIcon(b.current + 1)
|
||||
})
|
||||
b.name = widget.NewSelect(iconList(b.icons), func(name string) {
|
||||
for i, icon := range b.icons {
|
||||
if icon.name == name {
|
||||
if b.current != i {
|
||||
b.setIcon(i)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
b.name.SetSelected(b.icons[b.current].name)
|
||||
buttons := container.NewHBox(prev, next)
|
||||
bar := container.NewBorder(nil, nil, buttons, nil, b.name)
|
||||
|
||||
background := canvas.NewRasterWithPixels(checkerPattern)
|
||||
background.SetMinSize(fyne.NewSize(280, 280))
|
||||
b.icon = widget.NewIcon(b.icons[b.current].icon)
|
||||
|
||||
return fyne.NewContainerWithLayout(layout.NewBorderLayout(
|
||||
bar, nil, nil, nil), bar, background, b.icon)
|
||||
}
|
||||
|
||||
func checkerPattern(x, y, _, _ int) color.Color {
|
||||
x /= 20
|
||||
y /= 20
|
||||
|
||||
if x%2 == y%2 {
|
||||
return theme.BackgroundColor()
|
||||
}
|
||||
|
||||
return theme.ShadowColor()
|
||||
}
|
||||
|
||||
func iconList(icons []iconInfo) []string {
|
||||
ret := make([]string, len(icons))
|
||||
for i, icon := range icons {
|
||||
ret[i] = icon.name
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func loadIcons() []iconInfo {
|
||||
return []iconInfo{
|
||||
{"CancelIcon", theme.CancelIcon()},
|
||||
{"ConfirmIcon", theme.ConfirmIcon()},
|
||||
{"DeleteIcon", theme.DeleteIcon()},
|
||||
{"SearchIcon", theme.SearchIcon()},
|
||||
{"SearchReplaceIcon", theme.SearchReplaceIcon()},
|
||||
|
||||
{"CheckButtonIcon", theme.CheckButtonIcon()},
|
||||
{"CheckButtonCheckedIcon", theme.CheckButtonCheckedIcon()},
|
||||
{"RadioButtonIcon", theme.RadioButtonIcon()},
|
||||
{"RadioButtonCheckedIcon", theme.RadioButtonCheckedIcon()},
|
||||
|
||||
{"ColorAchromaticIcon", theme.ColorAchromaticIcon()},
|
||||
{"ColorChromaticIcon", theme.ColorChromaticIcon()},
|
||||
{"ColorPaletteIcon", theme.ColorPaletteIcon()},
|
||||
|
||||
{"ContentAddIcon", theme.ContentAddIcon()},
|
||||
{"ContentRemoveIcon", theme.ContentRemoveIcon()},
|
||||
{"ContentClearIcon", theme.ContentClearIcon()},
|
||||
{"ContentCutIcon", theme.ContentCutIcon()},
|
||||
{"ContentCopyIcon", theme.ContentCopyIcon()},
|
||||
{"ContentPasteIcon", theme.ContentPasteIcon()},
|
||||
{"ContentRedoIcon", theme.ContentRedoIcon()},
|
||||
{"ContentUndoIcon", theme.ContentUndoIcon()},
|
||||
|
||||
{"InfoIcon", theme.InfoIcon()},
|
||||
{"ErrorIcon", theme.ErrorIcon()},
|
||||
{"QuestionIcon", theme.QuestionIcon()},
|
||||
{"WarningIcon", theme.WarningIcon()},
|
||||
|
||||
{"DocumentIcon", theme.DocumentIcon()},
|
||||
{"DocumentCreateIcon", theme.DocumentCreateIcon()},
|
||||
{"DocumentPrintIcon", theme.DocumentPrintIcon()},
|
||||
{"DocumentSaveIcon", theme.DocumentSaveIcon()},
|
||||
|
||||
{"FileIcon", theme.FileIcon()},
|
||||
{"FileApplicationIcon", theme.FileApplicationIcon()},
|
||||
{"FileAudioIcon", theme.FileAudioIcon()},
|
||||
{"FileImageIcon", theme.FileImageIcon()},
|
||||
{"FileTextIcon", theme.FileTextIcon()},
|
||||
{"FileVideoIcon", theme.FileVideoIcon()},
|
||||
{"FolderIcon", theme.FolderIcon()},
|
||||
{"FolderNewIcon", theme.FolderNewIcon()},
|
||||
{"FolderOpenIcon", theme.FolderOpenIcon()},
|
||||
{"ComputerIcon", theme.ComputerIcon()},
|
||||
{"HomeIcon", theme.HomeIcon()},
|
||||
{"HelpIcon", theme.HelpIcon()},
|
||||
{"HistoryIcon", theme.HistoryIcon()},
|
||||
{"SettingsIcon", theme.SettingsIcon()},
|
||||
{"StorageIcon", theme.StorageIcon()},
|
||||
{"DownloadIcon", theme.DownloadIcon()},
|
||||
{"UploadIcon", theme.UploadIcon()},
|
||||
|
||||
{"ViewFullScreenIcon", theme.ViewFullScreenIcon()},
|
||||
{"ViewRestoreIcon", theme.ViewRestoreIcon()},
|
||||
{"ViewRefreshIcon", theme.ViewRefreshIcon()},
|
||||
{"VisibilityIcon", theme.VisibilityIcon()},
|
||||
{"VisibilityOffIcon", theme.VisibilityOffIcon()},
|
||||
{"ZoomFitIcon", theme.ZoomFitIcon()},
|
||||
{"ZoomInIcon", theme.ZoomInIcon()},
|
||||
{"ZoomOutIcon", theme.ZoomOutIcon()},
|
||||
|
||||
{"MoreHorizontalIcon", theme.MoreHorizontalIcon()},
|
||||
{"MoreVerticalIcon", theme.MoreVerticalIcon()},
|
||||
|
||||
{"MoveDownIcon", theme.MoveDownIcon()},
|
||||
{"MoveUpIcon", theme.MoveUpIcon()},
|
||||
|
||||
{"NavigateBackIcon", theme.NavigateBackIcon()},
|
||||
{"NavigateNextIcon", theme.NavigateNextIcon()},
|
||||
|
||||
{"Menu", theme.MenuIcon()},
|
||||
{"MenuExpand", theme.MenuExpandIcon()},
|
||||
{"MenuDropDown", theme.MenuDropDownIcon()},
|
||||
{"MenuDropUp", theme.MenuDropUpIcon()},
|
||||
|
||||
{"MailAttachmentIcon", theme.MailAttachmentIcon()},
|
||||
{"MailComposeIcon", theme.MailComposeIcon()},
|
||||
{"MailForwardIcon", theme.MailForwardIcon()},
|
||||
{"MailReplyIcon", theme.MailReplyIcon()},
|
||||
{"MailReplyAllIcon", theme.MailReplyAllIcon()},
|
||||
{"MailSendIcon", theme.MailSendIcon()},
|
||||
|
||||
{"MediaFastForward", theme.MediaFastForwardIcon()},
|
||||
{"MediaFastRewind", theme.MediaFastRewindIcon()},
|
||||
{"MediaPause", theme.MediaPauseIcon()},
|
||||
{"MediaPlay", theme.MediaPlayIcon()},
|
||||
{"MediaStop", theme.MediaStopIcon()},
|
||||
{"MediaRecord", theme.MediaRecordIcon()},
|
||||
{"MediaReplay", theme.MediaReplayIcon()},
|
||||
{"MediaSkipNext", theme.MediaSkipNextIcon()},
|
||||
{"MediaSkipPrevious", theme.MediaSkipPreviousIcon()},
|
||||
|
||||
{"VolumeDown", theme.VolumeDownIcon()},
|
||||
{"VolumeMute", theme.VolumeMuteIcon()},
|
||||
{"VolumeUp", theme.VolumeUpIcon()},
|
||||
|
||||
{"AccountIcon", theme.AccountIcon()},
|
||||
{"LoginIcon", theme.LoginIcon()},
|
||||
{"LogoutIcon", theme.LogoutIcon()},
|
||||
|
||||
{"ListIcon", theme.ListIcon()},
|
||||
{"GridIcon", theme.GridIcon()},
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
)
|
||||
|
||||
var (
|
||||
purple = &color.NRGBA{R: 128, G: 0, B: 128, A: 255}
|
||||
orange = &color.NRGBA{R: 198, G: 123, B: 0, A: 255}
|
||||
grey = &color.Gray{Y: 123}
|
||||
)
|
||||
|
||||
// customTheme is a simple demonstration of a bespoke theme loaded by a Fyne app.
|
||||
type customTheme struct {
|
||||
}
|
||||
|
||||
func (customTheme) Color(c fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color {
|
||||
switch c {
|
||||
case theme.ColorNameBackground:
|
||||
return purple
|
||||
case theme.ColorNameButton, theme.ColorNameDisabled:
|
||||
return color.Black
|
||||
case theme.ColorNamePlaceHolder, theme.ColorNameScrollBar:
|
||||
return grey
|
||||
case theme.ColorNamePrimary, theme.ColorNameHover, theme.ColorNameFocus:
|
||||
return orange
|
||||
case theme.ColorNameShadow:
|
||||
return &color.RGBA{R: 0xcc, G: 0xcc, B: 0xcc, A: 0xcc}
|
||||
default:
|
||||
return color.White
|
||||
}
|
||||
}
|
||||
|
||||
func (customTheme) Font(style fyne.TextStyle) fyne.Resource {
|
||||
return theme.DarkTheme().Font(style)
|
||||
}
|
||||
|
||||
func (customTheme) Icon(n fyne.ThemeIconName) fyne.Resource {
|
||||
return theme.DefaultTheme().Icon(n)
|
||||
}
|
||||
|
||||
func (customTheme) Size(s fyne.ThemeSizeName) float32 {
|
||||
switch s {
|
||||
case theme.SizeNamePadding:
|
||||
return 8
|
||||
case theme.SizeNameInlineIcon:
|
||||
return 20
|
||||
case theme.SizeNameScrollBar:
|
||||
return 10
|
||||
case theme.SizeNameScrollBarSmall:
|
||||
return 5
|
||||
case theme.SizeNameText:
|
||||
return 18
|
||||
case theme.SizeNameHeadingText:
|
||||
return 30
|
||||
case theme.SizeNameSubHeadingText:
|
||||
return 25
|
||||
case theme.SizeNameCaptionText:
|
||||
return 15
|
||||
case theme.SizeNameInputBorder:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func newCustomTheme() fyne.Theme {
|
||||
return &customTheme{}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/cmd/fyne_demo/data"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func parseURL(urlStr string) *url.URL {
|
||||
link, err := url.Parse(urlStr)
|
||||
if err != nil {
|
||||
fyne.LogError("Could not parse URL", err)
|
||||
}
|
||||
|
||||
return link
|
||||
}
|
||||
|
||||
func welcomeScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
logo := canvas.NewImageFromResource(data.FyneScene)
|
||||
logo.FillMode = canvas.ImageFillContain
|
||||
if fyne.CurrentDevice().IsMobile() {
|
||||
logo.SetMinSize(fyne.NewSize(171, 125))
|
||||
} else {
|
||||
logo.SetMinSize(fyne.NewSize(228, 167))
|
||||
}
|
||||
|
||||
return container.NewCenter(container.NewVBox(
|
||||
widget.NewLabelWithStyle("Welcome to the Fyne toolkit demo app", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}),
|
||||
logo,
|
||||
container.NewHBox(
|
||||
widget.NewHyperlink("fyne.io", parseURL("https://fyne.io/")),
|
||||
widget.NewLabel("-"),
|
||||
widget.NewHyperlink("documentation", parseURL("https://developer.fyne.io/")),
|
||||
widget.NewLabel("-"),
|
||||
widget.NewHyperlink("sponsor", parseURL("https://fyne.io/sponsor/")),
|
||||
),
|
||||
))
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/validation"
|
||||
"fyne.io/fyne/v2/driver/mobile"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
const (
|
||||
loremIpsum = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque quis consectetur nisi. Suspendisse id interdum felis.
|
||||
Sed egestas eget tellus eu pharetra. Praesent pulvinar sed massa id placerat. Etiam sem libero, semper vitae consequat ut, volutpat id mi.
|
||||
Mauris volutpat pellentesque convallis. Curabitur rutrum venenatis orci nec ornare. Maecenas quis pellentesque neque.
|
||||
Aliquam consectetur dapibus nulla, id maximus odio ultrices ac. Sed luctus at felis sed faucibus. Cras leo augue, congue in velit ut, mattis rhoncus lectus.
|
||||
|
||||
Praesent viverra, mauris ut ullamcorper semper, leo urna auctor lectus, vitae vehicula mi leo quis lorem.
|
||||
Nullam condimentum, massa at tempor feugiat, metus enim lobortis velit, eget suscipit eros ipsum quis tellus. Aenean fermentum diam vel felis dictum semper.
|
||||
Duis nisl orci, tincidunt ut leo quis, luctus vehicula diam. Sed velit justo, congue id augue eu, euismod dapibus lacus. Proin sit amet imperdiet sapien.
|
||||
Mauris erat urna, fermentum et quam rhoncus, fringilla consequat ante. Vivamus consectetur molestie odio, ac rutrum erat finibus a.
|
||||
Suspendisse id maximus felis. Sed mauris odio, mattis eget mi eu, consequat tempus purus.`
|
||||
)
|
||||
|
||||
var (
|
||||
progress *widget.ProgressBar
|
||||
fprogress *widget.ProgressBar
|
||||
infProgress *widget.ProgressBarInfinite
|
||||
endProgress chan interface{}
|
||||
)
|
||||
|
||||
func makeAccordionTab(_ fyne.Window) fyne.CanvasObject {
|
||||
link, err := url.Parse("https://fyne.io/")
|
||||
if err != nil {
|
||||
fyne.LogError("Could not parse URL", err)
|
||||
}
|
||||
ac := widget.NewAccordion(
|
||||
widget.NewAccordionItem("A", widget.NewHyperlink("One", link)),
|
||||
widget.NewAccordionItem("B", widget.NewLabel("Two")),
|
||||
&widget.AccordionItem{
|
||||
Title: "C",
|
||||
Detail: widget.NewLabel("Three"),
|
||||
},
|
||||
)
|
||||
ac.Append(widget.NewAccordionItem("D", &widget.Entry{Text: "Four"}))
|
||||
return ac
|
||||
}
|
||||
|
||||
func makeButtonTab(_ fyne.Window) fyne.CanvasObject {
|
||||
disabled := widget.NewButton("Disabled", func() {})
|
||||
disabled.Disable()
|
||||
|
||||
shareItem := fyne.NewMenuItem("Share via", nil)
|
||||
shareItem.ChildMenu = fyne.NewMenu("",
|
||||
fyne.NewMenuItem("Twitter", func() { fmt.Println("context menu Share->Twitter") }),
|
||||
fyne.NewMenuItem("Reddit", func() { fmt.Println("context menu Share->Reddit") }),
|
||||
)
|
||||
menuLabel := newContextMenuButton("tap me for pop-up menu with submenus", fyne.NewMenu("",
|
||||
fyne.NewMenuItem("Copy", func() { fmt.Println("context menu copy") }),
|
||||
shareItem,
|
||||
))
|
||||
|
||||
return container.NewVBox(
|
||||
widget.NewButton("Button (text only)", func() { fmt.Println("tapped text button") }),
|
||||
widget.NewButtonWithIcon("Button (text & leading icon)", theme.ConfirmIcon(), func() { fmt.Println("tapped text & leading icon button") }),
|
||||
&widget.Button{
|
||||
Alignment: widget.ButtonAlignLeading,
|
||||
Text: "Button (leading-aligned, text only)",
|
||||
OnTapped: func() { fmt.Println("tapped leading-aligned, text only button") },
|
||||
},
|
||||
&widget.Button{
|
||||
Alignment: widget.ButtonAlignTrailing,
|
||||
IconPlacement: widget.ButtonIconTrailingText,
|
||||
Text: "Button (trailing-aligned, text & trailing icon)",
|
||||
Icon: theme.ConfirmIcon(),
|
||||
OnTapped: func() { fmt.Println("tapped trailing-aligned, text & trailing icon button") },
|
||||
},
|
||||
disabled,
|
||||
layout.NewSpacer(),
|
||||
layout.NewSpacer(),
|
||||
menuLabel,
|
||||
layout.NewSpacer(),
|
||||
)
|
||||
}
|
||||
|
||||
func makeCardTab(_ fyne.Window) fyne.CanvasObject {
|
||||
card1 := widget.NewCard("Book a table", "Which time suits?",
|
||||
widget.NewRadioGroup([]string{"6:30pm", "7:00pm", "7:45pm"}, func(string) {}))
|
||||
card2 := widget.NewCard("With media", "No content, with image", nil)
|
||||
card2.Image = canvas.NewImageFromResource(theme.FyneLogo())
|
||||
card3 := widget.NewCard("Title 3", "Another card", widget.NewLabel("Content"))
|
||||
return container.NewGridWithColumns(2, container.NewVBox(card1, card3),
|
||||
container.NewVBox(card2))
|
||||
}
|
||||
|
||||
func makeEntryTab(_ fyne.Window) fyne.CanvasObject {
|
||||
entry := widget.NewEntry()
|
||||
entry.SetPlaceHolder("Entry")
|
||||
entryDisabled := widget.NewEntry()
|
||||
entryDisabled.SetText("Entry (disabled)")
|
||||
entryDisabled.Disable()
|
||||
entryValidated := newNumEntry()
|
||||
entryValidated.SetPlaceHolder("Must contain a number")
|
||||
entryMultiLine := widget.NewMultiLineEntry()
|
||||
entryMultiLine.SetPlaceHolder("MultiLine Entry")
|
||||
|
||||
return container.NewVBox(
|
||||
entry,
|
||||
entryDisabled,
|
||||
entryValidated,
|
||||
entryMultiLine)
|
||||
}
|
||||
|
||||
func makeTextGrid() *widget.TextGrid {
|
||||
grid := widget.NewTextGridFromString("TextGrid\n\tContent\nZebra")
|
||||
grid.SetStyleRange(0, 4, 0, 7,
|
||||
&widget.CustomTextGridStyle{BGColor: &color.NRGBA{R: 64, G: 64, B: 192, A: 128}})
|
||||
grid.SetRowStyle(1, &widget.CustomTextGridStyle{BGColor: &color.NRGBA{R: 64, G: 192, B: 64, A: 128}})
|
||||
|
||||
white := &widget.CustomTextGridStyle{FGColor: color.White, BGColor: color.Black}
|
||||
black := &widget.CustomTextGridStyle{FGColor: color.Black, BGColor: color.White}
|
||||
grid.Rows[2].Cells[0].Style = white
|
||||
grid.Rows[2].Cells[1].Style = black
|
||||
grid.Rows[2].Cells[2].Style = white
|
||||
grid.Rows[2].Cells[3].Style = black
|
||||
grid.Rows[2].Cells[4].Style = white
|
||||
|
||||
grid.ShowLineNumbers = true
|
||||
grid.ShowWhitespace = true
|
||||
|
||||
return grid
|
||||
}
|
||||
|
||||
func makeTextTab(_ fyne.Window) fyne.CanvasObject {
|
||||
label := widget.NewLabel("Label")
|
||||
|
||||
link, err := url.Parse("https://fyne.io/")
|
||||
if err != nil {
|
||||
fyne.LogError("Could not parse URL", err)
|
||||
}
|
||||
hyperlink := widget.NewHyperlink("Hyperlink", link)
|
||||
|
||||
entryLoremIpsum := widget.NewMultiLineEntry()
|
||||
entryLoremIpsum.SetText(loremIpsum)
|
||||
|
||||
label.Alignment = fyne.TextAlignLeading
|
||||
hyperlink.Alignment = fyne.TextAlignLeading
|
||||
|
||||
label.Wrapping = fyne.TextWrapWord
|
||||
hyperlink.Wrapping = fyne.TextWrapWord
|
||||
entryLoremIpsum.Wrapping = fyne.TextWrapWord
|
||||
|
||||
rich := widget.NewRichTextFromMarkdown(`
|
||||
# RichText Heading
|
||||
|
||||
## A Sub Heading
|
||||
|
||||
---
|
||||
|
||||
* Item1 in _three_ segments
|
||||
* Item2
|
||||
* Item3
|
||||
|
||||
Normal **Bold** *Italic* [Link](https://fyne.io/) and some ` + "`Code`" + `.
|
||||
This styled row should also wrap as expected, but only *when required*.
|
||||
|
||||
> An interesting quote here, most likely sharing some very interesting wisdom.`)
|
||||
rich.Scroll = container.ScrollBoth
|
||||
|
||||
radioAlign := widget.NewRadioGroup([]string{"Text Alignment Leading", "Text Alignment Center", "Text Alignment Trailing"}, func(s string) {
|
||||
var align fyne.TextAlign
|
||||
switch s {
|
||||
case "Text Alignment Leading":
|
||||
align = fyne.TextAlignLeading
|
||||
case "Text Alignment Center":
|
||||
align = fyne.TextAlignCenter
|
||||
case "Text Alignment Trailing":
|
||||
align = fyne.TextAlignTrailing
|
||||
}
|
||||
|
||||
label.Alignment = align
|
||||
hyperlink.Alignment = align
|
||||
for i := range rich.Segments {
|
||||
if seg, ok := rich.Segments[i].(*widget.TextSegment); ok {
|
||||
seg.Style.Alignment = align
|
||||
}
|
||||
if seg, ok := rich.Segments[i].(*widget.HyperlinkSegment); ok {
|
||||
seg.Alignment = align
|
||||
}
|
||||
}
|
||||
|
||||
label.Refresh()
|
||||
hyperlink.Refresh()
|
||||
rich.Refresh()
|
||||
})
|
||||
radioAlign.SetSelected("Text Alignment Leading")
|
||||
|
||||
radioWrap := widget.NewRadioGroup([]string{"Text Wrapping Off", "Text Wrapping Truncate", "Text Wrapping Break", "Text Wrapping Word"}, func(s string) {
|
||||
var wrap fyne.TextWrap
|
||||
switch s {
|
||||
case "Text Wrapping Off":
|
||||
wrap = fyne.TextWrapOff
|
||||
case "Text Wrapping Truncate":
|
||||
wrap = fyne.TextTruncate
|
||||
case "Text Wrapping Break":
|
||||
wrap = fyne.TextWrapBreak
|
||||
case "Text Wrapping Word":
|
||||
wrap = fyne.TextWrapWord
|
||||
}
|
||||
|
||||
label.Wrapping = wrap
|
||||
hyperlink.Wrapping = wrap
|
||||
entryLoremIpsum.Wrapping = wrap
|
||||
rich.Wrapping = wrap
|
||||
|
||||
label.Refresh()
|
||||
hyperlink.Refresh()
|
||||
entryLoremIpsum.Refresh()
|
||||
rich.Refresh()
|
||||
})
|
||||
radioWrap.SetSelected("Text Wrapping Word")
|
||||
|
||||
fixed := container.NewVBox(
|
||||
container.NewHBox(
|
||||
radioAlign,
|
||||
layout.NewSpacer(),
|
||||
radioWrap,
|
||||
),
|
||||
label,
|
||||
hyperlink,
|
||||
)
|
||||
|
||||
grid := makeTextGrid()
|
||||
return container.NewBorder(fixed, grid, nil, nil,
|
||||
container.NewGridWithRows(2, rich, entryLoremIpsum))
|
||||
}
|
||||
|
||||
func makeInputTab(_ fyne.Window) fyne.CanvasObject {
|
||||
selectEntry := widget.NewSelectEntry([]string{"Option A", "Option B", "Option C"})
|
||||
selectEntry.PlaceHolder = "Type or select"
|
||||
disabledCheck := widget.NewCheck("Disabled check", func(bool) {})
|
||||
disabledCheck.Disable()
|
||||
checkGroup := widget.NewCheckGroup([]string{"CheckGroup Item 1", "CheckGroup Item 2AAAAAAAAAAAAAA", "CheckGroup Item 3"}, func(s []string) { fmt.Println("selected", s) })
|
||||
checkGroup.Horizontal = true
|
||||
radio := widget.NewRadioGroup([]string{"Radio Item 1", "Radio Item 2"}, func(s string) { fmt.Println("selected", s) })
|
||||
radio.Horizontal = true
|
||||
disabledRadio := widget.NewRadioGroup([]string{"Disabled radio"}, func(string) {})
|
||||
disabledRadio.Disable()
|
||||
|
||||
return container.NewVBox(
|
||||
widget.NewSelect([]string{"Option 1", "Option 2", "Option 3"}, func(s string) { fmt.Println("selected", s) }),
|
||||
selectEntry,
|
||||
widget.NewCheck("Check", func(on bool) { fmt.Println("checked", on) }),
|
||||
disabledCheck,
|
||||
checkGroup,
|
||||
radio,
|
||||
disabledRadio,
|
||||
widget.NewSlider(0, 100),
|
||||
)
|
||||
}
|
||||
|
||||
func makeProgressTab(_ fyne.Window) fyne.CanvasObject {
|
||||
stopProgress()
|
||||
|
||||
progress = widget.NewProgressBar()
|
||||
|
||||
fprogress = widget.NewProgressBar()
|
||||
fprogress.TextFormatter = func() string {
|
||||
return fmt.Sprintf("%.2f out of %.2f", fprogress.Value, fprogress.Max)
|
||||
}
|
||||
|
||||
infProgress = widget.NewProgressBarInfinite()
|
||||
endProgress = make(chan interface{}, 1)
|
||||
startProgress()
|
||||
|
||||
return container.NewVBox(
|
||||
widget.NewLabel("Percent"), progress,
|
||||
widget.NewLabel("Formatted"), fprogress,
|
||||
widget.NewLabel("Infinite"), infProgress)
|
||||
}
|
||||
|
||||
func makeFormTab(_ fyne.Window) fyne.CanvasObject {
|
||||
name := widget.NewEntry()
|
||||
name.SetPlaceHolder("John Smith")
|
||||
|
||||
email := widget.NewEntry()
|
||||
email.SetPlaceHolder("test@example.com")
|
||||
email.Validator = validation.NewRegexp(`\w{1,}@\w{1,}\.\w{1,4}`, "not a valid email")
|
||||
|
||||
password := widget.NewPasswordEntry()
|
||||
password.SetPlaceHolder("Password")
|
||||
|
||||
disabled := widget.NewRadioGroup([]string{"Option 1", "Option 2"}, func(string) {})
|
||||
disabled.Horizontal = true
|
||||
disabled.Disable()
|
||||
largeText := widget.NewMultiLineEntry()
|
||||
|
||||
form := &widget.Form{
|
||||
Items: []*widget.FormItem{
|
||||
{Text: "Name", Widget: name, HintText: "Your full name"},
|
||||
{Text: "Email", Widget: email, HintText: "A valid email address"},
|
||||
},
|
||||
OnCancel: func() {
|
||||
fmt.Println("Cancelled")
|
||||
},
|
||||
OnSubmit: func() {
|
||||
fmt.Println("Form submitted")
|
||||
fyne.CurrentApp().SendNotification(&fyne.Notification{
|
||||
Title: "Form for: " + name.Text,
|
||||
Content: largeText.Text,
|
||||
})
|
||||
},
|
||||
}
|
||||
form.Append("Password", password)
|
||||
form.Append("Disabled", disabled)
|
||||
form.Append("Message", largeText)
|
||||
return form
|
||||
}
|
||||
|
||||
func makeToolbarTab(_ fyne.Window) fyne.CanvasObject {
|
||||
t := widget.NewToolbar(widget.NewToolbarAction(theme.MailComposeIcon(), func() { fmt.Println("New") }),
|
||||
widget.NewToolbarSeparator(),
|
||||
widget.NewToolbarSpacer(),
|
||||
widget.NewToolbarAction(theme.ContentCutIcon(), func() { fmt.Println("Cut") }),
|
||||
widget.NewToolbarAction(theme.ContentCopyIcon(), func() { fmt.Println("Copy") }),
|
||||
widget.NewToolbarAction(theme.ContentPasteIcon(), func() { fmt.Println("Paste") }),
|
||||
)
|
||||
|
||||
return container.NewBorder(t, nil, nil, nil)
|
||||
}
|
||||
|
||||
func startProgress() {
|
||||
progress.SetValue(0)
|
||||
fprogress.SetValue(0)
|
||||
select { // ignore stale end message
|
||||
case <-endProgress:
|
||||
default:
|
||||
}
|
||||
|
||||
go func() {
|
||||
end := endProgress
|
||||
num := 0.0
|
||||
for num < 1.0 {
|
||||
time.Sleep(16 * time.Millisecond)
|
||||
select {
|
||||
case <-end:
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
progress.SetValue(num)
|
||||
fprogress.SetValue(num)
|
||||
num += 0.002
|
||||
}
|
||||
|
||||
progress.SetValue(1)
|
||||
fprogress.SetValue(1)
|
||||
|
||||
// TODO make sure this resets when we hide etc...
|
||||
stopProgress()
|
||||
}()
|
||||
infProgress.Start()
|
||||
}
|
||||
|
||||
func stopProgress() {
|
||||
if !infProgress.Running() {
|
||||
return
|
||||
}
|
||||
|
||||
infProgress.Stop()
|
||||
endProgress <- struct{}{}
|
||||
}
|
||||
|
||||
// widgetScreen shows a panel containing widget demos
|
||||
func widgetScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
content := container.NewVBox(
|
||||
widget.NewLabel("Labels"),
|
||||
widget.NewButtonWithIcon("Icons", theme.HomeIcon(), func() {}),
|
||||
widget.NewSlider(0, 1))
|
||||
return container.NewCenter(content)
|
||||
}
|
||||
|
||||
type contextMenuButton struct {
|
||||
widget.Button
|
||||
menu *fyne.Menu
|
||||
}
|
||||
|
||||
func (b *contextMenuButton) Tapped(e *fyne.PointEvent) {
|
||||
widget.ShowPopUpMenuAtPosition(b.menu, fyne.CurrentApp().Driver().CanvasForObject(b), e.AbsolutePosition)
|
||||
}
|
||||
|
||||
func newContextMenuButton(label string, menu *fyne.Menu) *contextMenuButton {
|
||||
b := &contextMenuButton{menu: menu}
|
||||
b.Text = label
|
||||
|
||||
b.ExtendBaseWidget(b)
|
||||
return b
|
||||
}
|
||||
|
||||
type numEntry struct {
|
||||
widget.Entry
|
||||
}
|
||||
|
||||
func (n *numEntry) Keyboard() mobile.KeyboardType {
|
||||
return mobile.NumberKeyboard
|
||||
}
|
||||
|
||||
func newNumEntry() *numEntry {
|
||||
e := &numEntry{}
|
||||
e.ExtendBaseWidget(e)
|
||||
e.Validator = validation.NewRegexp(`\d`, "Must contain a number")
|
||||
return e
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
package tutorials
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/driver/desktop"
|
||||
"fyne.io/fyne/v2/layout"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
func windowScreen(_ fyne.Window) fyne.CanvasObject {
|
||||
windowGroup := container.NewVBox(
|
||||
widget.NewButton("New window", func() {
|
||||
w := fyne.CurrentApp().NewWindow("Hello")
|
||||
w.SetContent(widget.NewLabel("Hello World!"))
|
||||
w.Show()
|
||||
}),
|
||||
widget.NewButton("Fixed size window", func() {
|
||||
w := fyne.CurrentApp().NewWindow("Fixed")
|
||||
w.SetContent(fyne.NewContainerWithLayout(layout.NewCenterLayout(), widget.NewLabel("Hello World!")))
|
||||
|
||||
w.Resize(fyne.NewSize(240, 180))
|
||||
w.SetFixedSize(true)
|
||||
w.Show()
|
||||
}),
|
||||
widget.NewButton("Toggle between fixed/not fixed window size", func() {
|
||||
w := fyne.CurrentApp().NewWindow("Toggle fixed size")
|
||||
w.SetContent(fyne.NewContainerWithLayout(layout.NewCenterLayout(), widget.NewCheck("Fixed size", func(toggle bool) {
|
||||
if toggle {
|
||||
w.Resize(fyne.NewSize(240, 180))
|
||||
}
|
||||
w.SetFixedSize(toggle)
|
||||
})))
|
||||
w.Show()
|
||||
}),
|
||||
widget.NewButton("Centered window", func() {
|
||||
w := fyne.CurrentApp().NewWindow("Central")
|
||||
w.SetContent(fyne.NewContainerWithLayout(layout.NewCenterLayout(), widget.NewLabel("Hello World!")))
|
||||
|
||||
w.CenterOnScreen()
|
||||
w.Show()
|
||||
}))
|
||||
|
||||
drv := fyne.CurrentApp().Driver()
|
||||
if drv, ok := drv.(desktop.Driver); ok {
|
||||
windowGroup.Objects = append(windowGroup.Objects,
|
||||
widget.NewButton("Splash Window (only use on start)", func() {
|
||||
w := drv.CreateSplashWindow()
|
||||
w.SetContent(widget.NewLabelWithStyle("Hello World!\n\nMake a splash!",
|
||||
fyne.TextAlignCenter, fyne.TextStyle{Bold: true}))
|
||||
w.Show()
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Second * 3)
|
||||
w.Close()
|
||||
}()
|
||||
}))
|
||||
}
|
||||
|
||||
otherGroup := widget.NewCard("Other", "",
|
||||
widget.NewButton("Notification", func() {
|
||||
fyne.CurrentApp().SendNotification(&fyne.Notification{
|
||||
Title: "Fyne Demo",
|
||||
Content: "Testing notifications...",
|
||||
})
|
||||
}))
|
||||
|
||||
return container.NewVBox(widget.NewCard("Windows", "", windowGroup), otherGroup)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/plugin/diange"
|
||||
"AynaLivePlayer/plugin/qiege"
|
||||
"AynaLivePlayer/plugin/textinfo"
|
||||
"AynaLivePlayer/plugin/webinfo"
|
||||
"AynaLivePlayer/plugin/wylogin"
|
||||
)
|
||||
|
||||
var plugins = []controller.Plugin{diange.NewDiange(), qiege.NewQiege(), textinfo.NewTextInfo(), webinfo.NewWebInfo(),
|
||||
wylogin.NewWYLogin()}
|
||||
|
||||
func main() {
|
||||
logger.Logger.Info("================Program Start================")
|
||||
logger.Logger.Infof("================Current Version: %s================", config.Version)
|
||||
controller.Initialize()
|
||||
controller.LoadPlugins(plugins...)
|
||||
gui.Initialize()
|
||||
gui.MainWindow.ShowAndRun()
|
||||
controller.ClosePlugins(plugins...)
|
||||
controller.Destroy()
|
||||
_ = config.SaveToConfigFile(config.ConfigPath)
|
||||
logger.Logger.Info("================Program End================")
|
||||
}
|
||||
88
app/main.go
Normal file
88
app/main.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui"
|
||||
"AynaLivePlayer/internal"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
loggerRepo "AynaLivePlayer/pkg/logger/repository"
|
||||
"flag"
|
||||
"os"
|
||||
"os/signal"
|
||||
"time"
|
||||
)
|
||||
|
||||
var dev = flag.Bool("dev", false, "dev")
|
||||
var headless = flag.Bool("headless", false, "headless")
|
||||
|
||||
type _LogConfig struct {
|
||||
config.BaseConfig
|
||||
Path string
|
||||
Level logger.LogLevel
|
||||
RedirectStderr bool
|
||||
MaxSize int64
|
||||
}
|
||||
|
||||
func (c *_LogConfig) Name() string {
|
||||
return "Log"
|
||||
}
|
||||
|
||||
var Log = &_LogConfig{
|
||||
Path: "./log.txt",
|
||||
Level: logger.LogLevelInfo,
|
||||
RedirectStderr: false, // this should be true if it is in production mode.
|
||||
MaxSize: 5,
|
||||
}
|
||||
|
||||
func setupGlobal() {
|
||||
//global.EventManager = event.NewManger(128, 16)
|
||||
global.EventBus = eventbus.New()
|
||||
global.Logger = loggerRepo.NewZapColoredLogger(Log.Path, !*dev)
|
||||
global.Logger.SetLogLevel(Log.Level)
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
config.LoadFromFile(config.ConfigPath)
|
||||
config.LoadConfig(Log)
|
||||
i18n.LoadLanguage(config.General.Language)
|
||||
setupGlobal()
|
||||
global.Logger.Info("================Program Start================")
|
||||
global.Logger.Infof("================Current Version: %s================", model.Version(config.Version))
|
||||
internal.Initialize()
|
||||
go func() {
|
||||
// temporary fix for gui not render correctly.
|
||||
// wait until gui rendered then start event dispatching
|
||||
time.Sleep(1 * time.Second)
|
||||
//global.EventManager.Start()
|
||||
_ = global.EventBus.Start()
|
||||
}()
|
||||
if *headless || config.Experimental.Headless {
|
||||
quit := make(chan os.Signal)
|
||||
signal.Notify(quit, os.Interrupt)
|
||||
<-quit
|
||||
} else {
|
||||
gui.Initialize()
|
||||
gui.MainWindow.ShowAndRun()
|
||||
}
|
||||
global.Logger.Info("closing internal server")
|
||||
internal.Stop()
|
||||
global.Logger.Infof("closing event manager")
|
||||
//global.EventManager.Stop()
|
||||
_ = global.EventBus.Stop()
|
||||
if *dev {
|
||||
global.Logger.Infof("saving translation")
|
||||
i18n.SaveTranslation()
|
||||
}
|
||||
err := config.SaveToConfigFile(config.ConfigPath)
|
||||
if err != nil {
|
||||
global.Logger.Errorf("save config failed: %v", err)
|
||||
} else {
|
||||
global.Logger.Infof("save config success")
|
||||
}
|
||||
global.Logger.Info("================Program End================")
|
||||
}
|
||||
32
assets/config/diange.json
Normal file
32
assets/config/diange.json
Normal file
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"bilibili-video": {
|
||||
"enable": true,
|
||||
"command": "点b歌",
|
||||
"priority": 3
|
||||
},
|
||||
"kugou": {
|
||||
"enable": true,
|
||||
"command": "点kg歌",
|
||||
"priority": 5
|
||||
},
|
||||
"kugou-instr": {
|
||||
"enable": true,
|
||||
"command": "点伴奏",
|
||||
"priority": 6
|
||||
},
|
||||
"kuwo": {
|
||||
"enable": true,
|
||||
"command": "点k歌",
|
||||
"priority": 2
|
||||
},
|
||||
"local": {
|
||||
"enable": true,
|
||||
"command": "点local",
|
||||
"priority": 4
|
||||
},
|
||||
"netease": {
|
||||
"enable": true,
|
||||
"command": "点w歌",
|
||||
"priority": 1
|
||||
}
|
||||
}
|
||||
12
assets/config/liverooms.json
Normal file
12
assets/config/liverooms.json
Normal file
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"live_room": {
|
||||
"provider": "biliweb",
|
||||
"room": "3819533"
|
||||
},
|
||||
"config": {
|
||||
"auto_connect": false
|
||||
},
|
||||
"title": "web 3819533"
|
||||
}
|
||||
]
|
||||
2230
assets/config/playlists.json
Normal file
2230
assets/config/playlists.json
Normal file
File diff suppressed because one or more lines are too long
0
assets/deps/linux/.gitkeep
Normal file
0
assets/deps/linux/.gitkeep
Normal file
0
assets/deps/windows/.gitkeep
Normal file
0
assets/deps/windows/.gitkeep
Normal file
BIN
assets/empty.png
BIN
assets/empty.png
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
BIN
assets/icon.jpg
BIN
assets/icon.jpg
Binary file not shown.
|
Before Width: | Height: | Size: 95 KiB |
BIN
assets/icon.png
Normal file
BIN
assets/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
BIN
assets/icon2.png
Normal file
BIN
assets/icon2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 350 KiB |
BIN
assets/msyh0.ttf
Normal file
BIN
assets/msyh0.ttf
Normal file
Binary file not shown.
BIN
assets/msyhbd.ttc
Normal file
BIN
assets/msyhbd.ttc
Normal file
Binary file not shown.
BIN
assets/msyhbd0.ttf
Normal file
BIN
assets/msyhbd0.ttf
Normal file
Binary file not shown.
0
assets/scripts/linux/.gitkeep
Normal file
0
assets/scripts/linux/.gitkeep
Normal file
68
assets/scripts/utils/ttc2ttf.py
Normal file
68
assets/scripts/utils/ttc2ttf.py
Normal file
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#First released as C++ program by Hiroyuki Tsutsumi as part of the free software suite “Beer”
|
||||
#I thought porting it to Python could be both a challenge and useful
|
||||
|
||||
from sys import argv, exit, getsizeof
|
||||
from struct import pack_into, unpack_from
|
||||
|
||||
def ceil4(n):
|
||||
"""returns the next integer which is a multiple of 4"""
|
||||
return (n + 3) & ~3
|
||||
|
||||
if len(argv)!=2:
|
||||
print("Usage: %s FontCollection.ttc" % argv)
|
||||
exit(2)
|
||||
|
||||
filename = argv[1]
|
||||
in_file = open(filename, "rb")
|
||||
buf = in_file.read()
|
||||
in_file.close()
|
||||
|
||||
if filename.lower().endswith(".ttc"):
|
||||
filename = filename[:-4]
|
||||
|
||||
|
||||
if buf[:4] != b"ttcf":
|
||||
out_filename = "%s.ttf" % filename
|
||||
out_file = open(out_filename, "wb")
|
||||
out_file.write(buf)
|
||||
#end, so we don’t have to close the files or call exit() here
|
||||
else:
|
||||
ttf_count = unpack_from("!L", buf, 0x08)[0]
|
||||
print("Anzahl enthaltener TTF-Dateien: %s" % ttf_count)
|
||||
ttf_offset_array = unpack_from("!"+ttf_count*"L", buf, 0x0C)
|
||||
for i in range(ttf_count):
|
||||
print("Extrahiere TTF #%s:" % (i+1))
|
||||
table_header_offset = ttf_offset_array[i]
|
||||
print("\tHeader beginnt bei Byte %s" % table_header_offset)
|
||||
table_count = unpack_from("!H", buf, table_header_offset+0x04)[0]
|
||||
header_length = 0x0C + table_count * 0x10
|
||||
print("\tHeaderlänge: %s Byte" % header_length)
|
||||
|
||||
table_length = 0
|
||||
for j in range(table_count):
|
||||
length = unpack_from("!L", buf, table_header_offset+0x0C+0x0C+j*0x10)[0]
|
||||
table_length += ceil4(length)
|
||||
|
||||
total_length = header_length + table_length
|
||||
new_buf = bytearray(total_length)
|
||||
header = unpack_from(header_length*"c", buf, table_header_offset)
|
||||
pack_into(header_length*"c", new_buf, 0, *header)
|
||||
current_offset = header_length
|
||||
|
||||
for j in range(table_count):
|
||||
offset = unpack_from("!L", buf, table_header_offset+0x0C+0x08+j*0x10)[0]
|
||||
length = unpack_from("!L", buf, table_header_offset+0x0C+0x0C+j*0x10)[0]
|
||||
pack_into("!L", new_buf, 0x0C+0x08+j*0x10, current_offset)
|
||||
current_table = unpack_from(length*"c", buf, offset)
|
||||
pack_into(length*"c", new_buf, current_offset, *current_table)
|
||||
|
||||
#table_checksum = sum(unpack_from("!"+("L"*length), new_buf, current_offset))
|
||||
#pack_into("!L", new_buf, 0x0C+0x04+j*0x10, table_checksum)
|
||||
|
||||
current_offset += ceil4(length)
|
||||
|
||||
out_file = open("%s%d.ttf"%(filename, i), "wb")
|
||||
out_file.write(new_buf)
|
||||
1
assets/scripts/windows/start-headless.bat
Normal file
1
assets/scripts/windows/start-headless.bat
Normal file
@@ -0,0 +1 @@
|
||||
start AynaLivePlayer.exe --headless
|
||||
1
assets/scripts/windows/stop-headless.bat
Normal file
1
assets/scripts/windows/stop-headless.bat
Normal file
@@ -0,0 +1 @@
|
||||
taskkill /F /IM AynaLivePlayer.exe
|
||||
1
assets/scripts/windows/强制结束-出现任何奇怪问题点我重置.bat
Normal file
1
assets/scripts/windows/强制结束-出现任何奇怪问题点我重置.bat
Normal file
@@ -0,0 +1 @@
|
||||
taskkill /IM "AynaLivePlayer.exe" /F
|
||||
1
assets/scripts/windows/重置winsock.bat
Normal file
1
assets/scripts/windows/重置winsock.bat
Normal file
@@ -0,0 +1 @@
|
||||
netsh winsock reset
|
||||
@@ -4,10 +4,26 @@
|
||||
"zh-CN"
|
||||
],
|
||||
"Messages": {
|
||||
"default web protocol. enter room id to connect.": {
|
||||
"en": "default web protocol. enter room id to connect.",
|
||||
"zh-CN": "网页弹幕协议,请输入房间号"
|
||||
},
|
||||
"gui.config.basic.audio_device": {
|
||||
"en": "Audio Device",
|
||||
"zh-CN": "音频输出设备"
|
||||
},
|
||||
"gui.config.basic.auto_check_update": {
|
||||
"en": "Check update at startup",
|
||||
"zh-CN": "自动检查更新"
|
||||
},
|
||||
"gui.config.basic.auto_check_update.prompt": {
|
||||
"en": "Yes",
|
||||
"zh-CN": "确认"
|
||||
},
|
||||
"gui.config.basic.check_update": {
|
||||
"en": "Check Update",
|
||||
"zh-CN": "检查更新"
|
||||
},
|
||||
"gui.config.basic.description": {
|
||||
"en": "Basic Configuration",
|
||||
"zh-CN": "基础设置"
|
||||
@@ -24,11 +40,11 @@
|
||||
"en": "User Playlist",
|
||||
"zh-CN": "用户歌单"
|
||||
},
|
||||
"gui.config.basic.skip_playlist": {
|
||||
"en": "Skip Media From System Playlist",
|
||||
"zh-CN": "跳过闲置歌单"
|
||||
"gui.config.basic.skip_when_error": {
|
||||
"en": "Skip this Media On Error",
|
||||
"zh-CN": "跳过获取失败的歌曲"
|
||||
},
|
||||
"gui.config.basic.skip_playlist.prompt": {
|
||||
"gui.config.basic.skip_when_error.prompt": {
|
||||
"en": "Skip",
|
||||
"zh-CN": "跳过"
|
||||
},
|
||||
@@ -36,6 +52,14 @@
|
||||
"en": "Basic",
|
||||
"zh-CN": "基础设置"
|
||||
},
|
||||
"gui.config.basic.use_system_playlist": {
|
||||
"en": "Play system playlist when no music",
|
||||
"zh-CN": "是否播放闲置歌单(实验性)"
|
||||
},
|
||||
"gui.config.basic.use_system_playlist.prompt": {
|
||||
"en": "Yes",
|
||||
"zh-CN": "是"
|
||||
},
|
||||
"gui.history.artist": {
|
||||
"en": "Artist",
|
||||
"zh-CN": "歌手"
|
||||
@@ -52,10 +76,18 @@
|
||||
"en": "User",
|
||||
"zh-CN": "用户"
|
||||
},
|
||||
"gui.lyric.title": {
|
||||
"en": "Lyric",
|
||||
"zh-CN": "歌词"
|
||||
},
|
||||
"gui.player.button.lrc": {
|
||||
"en": "lrc",
|
||||
"zh-CN": "歌词"
|
||||
},
|
||||
"gui.player.button.player": {
|
||||
"en": "player",
|
||||
"zh-CN": "播放器"
|
||||
},
|
||||
"gui.player.playlist.artist": {
|
||||
"en": "Artist",
|
||||
"zh-CN": "歌手"
|
||||
@@ -128,6 +160,34 @@
|
||||
"en": "Current: None",
|
||||
"zh-CN": "当前为: 无"
|
||||
},
|
||||
"gui.room.add.cancel": {
|
||||
"en": "Cancel",
|
||||
"zh-CN": "取消"
|
||||
},
|
||||
"gui.room.add.client_name": {
|
||||
"en": "Client Name",
|
||||
"zh-CN": "直播平台"
|
||||
},
|
||||
"gui.room.add.confirm": {
|
||||
"en": "Confirm",
|
||||
"zh-CN": "确定"
|
||||
},
|
||||
"gui.room.add.id_url": {
|
||||
"en": "Room ID",
|
||||
"zh-CN": "房间号"
|
||||
},
|
||||
"gui.room.add.name": {
|
||||
"en": "Display Name",
|
||||
"zh-CN": "显示名"
|
||||
},
|
||||
"gui.room.add.prompt": {
|
||||
"en": "enter room id",
|
||||
"zh-CN": "填入房间号"
|
||||
},
|
||||
"gui.room.add.title": {
|
||||
"en": "Add Room",
|
||||
"zh-CN": "添加房间"
|
||||
},
|
||||
"gui.room.btn.connect": {
|
||||
"en": "Connect",
|
||||
"zh-CN": "连接"
|
||||
@@ -136,6 +196,18 @@
|
||||
"en": "Disconnect",
|
||||
"zh-CN": "断开"
|
||||
},
|
||||
"gui.room.button.add": {
|
||||
"en": "Add",
|
||||
"zh-CN": "新增"
|
||||
},
|
||||
"gui.room.button.remove": {
|
||||
"en": "Remove",
|
||||
"zh-CN": "删除"
|
||||
},
|
||||
"gui.room.check.autoconnect": {
|
||||
"en": "Auto Connection",
|
||||
"zh-CN": "自动连接"
|
||||
},
|
||||
"gui.room.id": {
|
||||
"en": "Room ID: ",
|
||||
"zh-CN": "房间号: "
|
||||
@@ -208,22 +280,74 @@
|
||||
"en": "Search",
|
||||
"zh-CN": "搜索"
|
||||
},
|
||||
"gui.tray.btn.show": {
|
||||
"en": "Show",
|
||||
"zh-CN": "打开"
|
||||
},
|
||||
"gui.update.already_latest_version": {
|
||||
"en": "no update available",
|
||||
"zh-CN": "没有可用更新"
|
||||
},
|
||||
"gui.update.new_version": {
|
||||
"en": "New Version Available",
|
||||
"zh-CN": "有新版本可用"
|
||||
},
|
||||
"open bilibili live protocol. enter client key to connect.": {
|
||||
"en": "open bilibili live protocol. enter client key to connect.",
|
||||
"zh-CN": "新版b站协议,输入身份码"
|
||||
},
|
||||
"plugin.diange.admin": {
|
||||
"en": "Admin",
|
||||
"zh-CN": "管理员"
|
||||
},
|
||||
"plugin.diange.blacklist.btn.add": {
|
||||
"en": "Add",
|
||||
"zh-CN": "添加"
|
||||
},
|
||||
"plugin.diange.blacklist.description": {
|
||||
"en": "Blacklist Configuration",
|
||||
"zh-CN": "点歌黑名单设置"
|
||||
},
|
||||
"plugin.diange.blacklist.input.placeholder": {
|
||||
"en": "enter word",
|
||||
"zh-CN": "输入黑名单词"
|
||||
},
|
||||
"plugin.diange.blacklist.option.contains": {
|
||||
"en": "Contains",
|
||||
"zh-CN": "包含"
|
||||
},
|
||||
"plugin.diange.blacklist.option.exact": {
|
||||
"en": "Exact Match",
|
||||
"zh-CN": "相等"
|
||||
},
|
||||
"plugin.diange.blacklist.title": {
|
||||
"en": "Blacklist",
|
||||
"zh-CN": "点歌黑名单"
|
||||
},
|
||||
"plugin.diange.cooldown": {
|
||||
"en": "Cooldown",
|
||||
"zh-CN": "点歌冷却"
|
||||
},
|
||||
"plugin.diange.custom_cmd": {
|
||||
"en": "Custom Command (Default one still works)",
|
||||
"zh-CN": "自定义命令 (默认的依然可用)"
|
||||
"en": "Custom Command",
|
||||
"zh-CN": "自定义命令"
|
||||
},
|
||||
"plugin.diange.description": {
|
||||
"en": "Basic Diange Configuration",
|
||||
"zh-CN": "点歌基本设置"
|
||||
},
|
||||
"plugin.diange.medal.level": {
|
||||
"en": "Level",
|
||||
"zh-CN": "等级"
|
||||
},
|
||||
"plugin.diange.medal.name": {
|
||||
"en": "Name",
|
||||
"zh-CN": "牌子名"
|
||||
},
|
||||
"plugin.diange.medal.perm": {
|
||||
"en": "Medal Permission",
|
||||
"zh-CN": "牌子点歌权限"
|
||||
},
|
||||
"plugin.diange.permission": {
|
||||
"en": "Permission",
|
||||
"zh-CN": "点歌权限"
|
||||
@@ -236,6 +360,26 @@
|
||||
"en": "Max Queue",
|
||||
"zh-CN": "最大点歌数"
|
||||
},
|
||||
"plugin.diange.skip_playlist": {
|
||||
"en": "Skip Media From System Playlist",
|
||||
"zh-CN": "跳过闲置歌单"
|
||||
},
|
||||
"plugin.diange.skip_playlist.prompt": {
|
||||
"en": "Skip",
|
||||
"zh-CN": "跳过"
|
||||
},
|
||||
"plugin.diange.source.command": {
|
||||
"en": "command",
|
||||
"zh-CN": "命令"
|
||||
},
|
||||
"plugin.diange.source.enable": {
|
||||
"en": "enable",
|
||||
"zh-CN": "启用"
|
||||
},
|
||||
"plugin.diange.source.priority": {
|
||||
"en": "priority",
|
||||
"zh-CN": "优先级"
|
||||
},
|
||||
"plugin.diange.source_cmd": {
|
||||
"en": "Source Command",
|
||||
"zh-CN": "来源点歌命令"
|
||||
@@ -248,45 +392,41 @@
|
||||
"en": "User",
|
||||
"zh-CN": "普通用户"
|
||||
},
|
||||
"plugin.neteaselogin.current_user": {
|
||||
"en": "Current User:",
|
||||
"zh-CN": "当前用户:"
|
||||
"plugin.diange.user_max": {
|
||||
"en": "User Maximum queue",
|
||||
"zh-CN": "单个用户最大点歌数"
|
||||
},
|
||||
"plugin.neteaselogin.current_user.notlogin": {
|
||||
"en": "Not Login",
|
||||
"zh-CN": "未登录"
|
||||
"plugin.maxduration.description": {
|
||||
"en": "Set the maximum duration of a song",
|
||||
"zh-CN": "设置歌曲最长能播多久"
|
||||
},
|
||||
"plugin.neteaselogin.description": {
|
||||
"en": "Netease User Login",
|
||||
"zh-CN": "网易云登录"
|
||||
"plugin.maxduration.enable": {
|
||||
"en": "Enable",
|
||||
"zh-CN": "开启"
|
||||
},
|
||||
"plugin.neteaselogin.logout": {
|
||||
"en": "Logout",
|
||||
"zh-CN": "登出"
|
||||
"plugin.maxduration.maxduration": {
|
||||
"en": "Max Duration (seconds)",
|
||||
"zh-CN": "最大时长 (秒)"
|
||||
},
|
||||
"plugin.neteaselogin.qr.finish": {
|
||||
"en": "Finish Scan",
|
||||
"zh-CN": "完成扫描后按我"
|
||||
"plugin.maxduration.skiponplay": {
|
||||
"en": "Skip on play",
|
||||
"zh-CN": "播放时跳过"
|
||||
},
|
||||
"plugin.neteaselogin.qr.new": {
|
||||
"en": "Get a new qr code",
|
||||
"zh-CN": "获取新二维码"
|
||||
"plugin.maxduration.skiponreach": {
|
||||
"en": "Skip when reach max duration",
|
||||
"zh-CN": "播放到最大时长时跳过"
|
||||
},
|
||||
"plugin.neteaselogin.refresh": {
|
||||
"en": "Refresh",
|
||||
"zh-CN": "刷新状态"
|
||||
},
|
||||
"plugin.neteaselogin.title": {
|
||||
"en": "Netease Login",
|
||||
"zh-CN": "网易云登录"
|
||||
"plugin.maxduration.title": {
|
||||
"en": "Audio Duration Control",
|
||||
"zh-CN": "歌曲时长控制"
|
||||
},
|
||||
"plugin.qiege.admin": {
|
||||
"en": "Admin",
|
||||
"zh-CN": "管理员"
|
||||
},
|
||||
"plugin.qiege.custom_cmd": {
|
||||
"en": "Custom Command (Default one still works)",
|
||||
"zh-CN": "自定义命令 (默认的依然可用)"
|
||||
"en": "Custom Command",
|
||||
"zh-CN": "自定义命令"
|
||||
},
|
||||
"plugin.qiege.description": {
|
||||
"en": "Basic Qiege configuration",
|
||||
@@ -308,6 +448,42 @@
|
||||
"en": "User",
|
||||
"zh-CN": "切自己"
|
||||
},
|
||||
"plugin.sourcelogin.current_user": {
|
||||
"en": "Current Status:",
|
||||
"zh-CN": "当前状态:"
|
||||
},
|
||||
"plugin.sourcelogin.current_user.loggedin": {
|
||||
"en": "Logged In",
|
||||
"zh-CN": "已登录"
|
||||
},
|
||||
"plugin.sourcelogin.current_user.notlogin": {
|
||||
"en": "Not Login",
|
||||
"zh-CN": "未登录"
|
||||
},
|
||||
"plugin.sourcelogin.description": {
|
||||
"en": "Netease User Login",
|
||||
"zh-CN": "来源登录"
|
||||
},
|
||||
"plugin.sourcelogin.logout": {
|
||||
"en": "Logout",
|
||||
"zh-CN": "登出"
|
||||
},
|
||||
"plugin.sourcelogin.qr.finish": {
|
||||
"en": "Finish Scan",
|
||||
"zh-CN": "完成扫描后按我"
|
||||
},
|
||||
"plugin.sourcelogin.qr.new": {
|
||||
"en": "Get a new qr code",
|
||||
"zh-CN": "获取新二维码"
|
||||
},
|
||||
"plugin.sourcelogin.refresh": {
|
||||
"en": "Refresh",
|
||||
"zh-CN": "刷新状态"
|
||||
},
|
||||
"plugin.sourcelogin.title": {
|
||||
"en": "Netease Login",
|
||||
"zh-CN": "来源登录"
|
||||
},
|
||||
"plugin.textinfo.checkbox": {
|
||||
"en": "Enable",
|
||||
"zh-CN": "开启"
|
||||
@@ -324,49 +500,93 @@
|
||||
"en": "Text Output",
|
||||
"zh-CN": "文本输出"
|
||||
},
|
||||
"plugin.webinfo.description": {
|
||||
"en": "Web output configuration",
|
||||
"zh-CN": "web输出设置"
|
||||
"plugin.wshub.autostart": {
|
||||
"en": "Auto start",
|
||||
"zh-CN": "自动启用"
|
||||
},
|
||||
"plugin.webinfo.port": {
|
||||
"plugin.wshub.description": {
|
||||
"en": "Websocket Hub Configuration",
|
||||
"zh-CN": "Websocket服务器设置"
|
||||
},
|
||||
"plugin.wshub.local_host_only": {
|
||||
"en": "only allow local host connection",
|
||||
"zh-CN": "只允许本地连接"
|
||||
},
|
||||
"plugin.wshub.port": {
|
||||
"en": "Port",
|
||||
"zh-CN": "服务器端口"
|
||||
},
|
||||
"plugin.webinfo.server_control": {
|
||||
"plugin.wshub.server_control": {
|
||||
"en": "Control",
|
||||
"zh-CN": "操作"
|
||||
},
|
||||
"plugin.webinfo.server_control.restart": {
|
||||
"plugin.wshub.server_control.restart": {
|
||||
"en": "Restart",
|
||||
"zh-CN": "重启"
|
||||
},
|
||||
"plugin.webinfo.server_control.start": {
|
||||
"plugin.wshub.server_control.start": {
|
||||
"en": "Start",
|
||||
"zh-CN": "启动"
|
||||
},
|
||||
"plugin.webinfo.server_control.stop": {
|
||||
"plugin.wshub.server_control.stop": {
|
||||
"en": "Stop",
|
||||
"zh-CN": "停止"
|
||||
},
|
||||
"plugin.webinfo.server_status": {
|
||||
"plugin.wshub.server_link": {
|
||||
"en": "Websocket Server Link",
|
||||
"zh-CN": "Websocket服务器链接"
|
||||
},
|
||||
"plugin.wshub.server_status": {
|
||||
"en": "Server Status",
|
||||
"zh-CN": "服务器状态"
|
||||
},
|
||||
"plugin.webinfo.server_status.running": {
|
||||
"plugin.wshub.server_status.running": {
|
||||
"en": "Running",
|
||||
"zh-CN": "运行中"
|
||||
},
|
||||
"plugin.webinfo.server_status.stopped": {
|
||||
"plugin.wshub.server_status.stopped": {
|
||||
"en": "Stopped",
|
||||
"zh-CN": "已停止"
|
||||
},
|
||||
"plugin.webinfo.title": {
|
||||
"en": "Web Output",
|
||||
"zh-CN": "Web输出"
|
||||
"plugin.wshub.title": {
|
||||
"en": "Websocket Hub",
|
||||
"zh-CN": "Websocket服务器"
|
||||
},
|
||||
"plugin.webinfo.server_preview": {
|
||||
"en": "Server Preview",
|
||||
"zh-CN":"效果预览"
|
||||
"plugin.wshub.webinfo_text": {
|
||||
"en": "Obs browser output",
|
||||
"zh-CN": "OBS网页输出: "
|
||||
},
|
||||
"plugin.yinliang.title": {
|
||||
"en": "Volume Control",
|
||||
"zh-CN": "音量控制"
|
||||
},
|
||||
"plugin.yinliang.description": {
|
||||
"en": "Control volume via danmaku",
|
||||
"zh-CN": "通过弹幕控制音量"
|
||||
},
|
||||
"plugin.yinliang.admin_permission": {
|
||||
"en": "Admin only",
|
||||
"zh-CN": "仅房管可操作"
|
||||
},
|
||||
"plugin.yinliang.enabled": {
|
||||
"en": "Enabled volume control",
|
||||
"zh-CN": "启用弹幕音量控制"
|
||||
},
|
||||
"plugin.yinliang.volume_up_cmd": {
|
||||
"en": "Volume increase command",
|
||||
"zh-CN": "音量增加命令"
|
||||
},
|
||||
"plugin.yinliang.volume_down_cmd": {
|
||||
"en": "Volume decrease command",
|
||||
"zh-CN": "音量减少命令"
|
||||
},
|
||||
"plugin.yinliang.volume_step": {
|
||||
"en": "Adjustment step (%)",
|
||||
"zh-CN": "每次音量调整 (%)"
|
||||
},
|
||||
"plugin.yinliang.max_volume": {
|
||||
"en": "Maximum volume (%)",
|
||||
"zh-CN": "最大音量限制 (%)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
package config
|
||||
|
||||
type _GeneralConfig struct {
|
||||
Language string
|
||||
}
|
||||
|
||||
func (c *_GeneralConfig) Name() string {
|
||||
return "General"
|
||||
}
|
||||
|
||||
var General = &_GeneralConfig{
|
||||
Language: "en",
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package config
|
||||
|
||||
type _LiveRoomConfig struct {
|
||||
History []string
|
||||
}
|
||||
|
||||
func (c *_LiveRoomConfig) Name() string {
|
||||
return "LiveRoom"
|
||||
}
|
||||
|
||||
var LiveRoom = &_LiveRoomConfig{History: []string{"9076804", "3819533"}}
|
||||
@@ -1,19 +0,0 @@
|
||||
package config
|
||||
|
||||
import "github.com/sirupsen/logrus"
|
||||
|
||||
type _LogConfig struct {
|
||||
Path string
|
||||
Level logrus.Level
|
||||
RedirectStderr bool
|
||||
}
|
||||
|
||||
func (c *_LogConfig) Name() string {
|
||||
return "Log"
|
||||
}
|
||||
|
||||
var Log = &_LogConfig{
|
||||
Path: "./log.txt",
|
||||
Level: logrus.InfoLevel,
|
||||
RedirectStderr: false, // this should be true if it is in production mode.
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package config
|
||||
|
||||
type _PlayerConfig struct {
|
||||
Playlists []string
|
||||
PlaylistsProvider []string
|
||||
PlaylistIndex int
|
||||
PlaylistRandom bool
|
||||
AudioDevice string
|
||||
Volume float64
|
||||
SkipPlaylist bool
|
||||
}
|
||||
|
||||
func (c *_PlayerConfig) Name() string {
|
||||
return "Player"
|
||||
}
|
||||
|
||||
var Player = &_PlayerConfig{
|
||||
Playlists: []string{"2382819181", "4987059624", "list1"},
|
||||
PlaylistsProvider: []string{"netease", "netease", "local"},
|
||||
PlaylistIndex: 0,
|
||||
PlaylistRandom: true,
|
||||
AudioDevice: "auto",
|
||||
Volume: 100,
|
||||
SkipPlaylist: false,
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package config
|
||||
|
||||
type _ProviderConfig struct {
|
||||
Priority []string
|
||||
LocalDir string
|
||||
}
|
||||
|
||||
func (c *_ProviderConfig) Name() string {
|
||||
return "Provider"
|
||||
}
|
||||
|
||||
var Provider = &_ProviderConfig{
|
||||
Priority: []string{"netease", "kuwo", "bilibili", "local", "bilibili-video"},
|
||||
LocalDir: "./music",
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCreate(t *testing.T) {
|
||||
fmt.Println(SaveToConfigFile(ConfigPath))
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
fmt.Println(Log.Path)
|
||||
fmt.Println(Player.Playlists)
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var Commands []DanmuCommandExecutor
|
||||
|
||||
type DanmuCommandExecutor interface {
|
||||
Match(command string) bool
|
||||
Execute(command string, args []string, danmu *liveclient.DanmuMessage)
|
||||
}
|
||||
|
||||
func AddCommand(executors ...DanmuCommandExecutor) {
|
||||
Commands = append(Commands, executors...)
|
||||
}
|
||||
|
||||
func danmuCommandHandler(event *event.Event) {
|
||||
danmu := event.Data.(*liveclient.DanmuMessage)
|
||||
args := strings.Split(danmu.Message, " ")
|
||||
if len(args[0]) == 0 {
|
||||
return
|
||||
}
|
||||
for _, cmd := range Commands {
|
||||
if cmd.Match(args[0]) {
|
||||
cmd.Execute(args[0], args[1:], danmu)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
"AynaLivePlayer/util"
|
||||
"fmt"
|
||||
"github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const MODULE_CONTROLLER = "Controller"
|
||||
|
||||
func l() *logrus.Entry {
|
||||
return logger.Logger.WithField("Module", MODULE_CONTROLLER)
|
||||
}
|
||||
|
||||
func SetDanmuClient(roomId string) {
|
||||
ResetDanmuClient()
|
||||
l().Infof("setting live client for %s", roomId)
|
||||
room, err := strconv.Atoi(roomId)
|
||||
if err != nil {
|
||||
l().Warn("parse room id error", err)
|
||||
return
|
||||
}
|
||||
if !util.StringSliceContains(config.LiveRoom.History, roomId) {
|
||||
config.LiveRoom.History = append(config.LiveRoom.History, roomId)
|
||||
}
|
||||
LiveClient = liveclient.NewBilibili(room)
|
||||
LiveClient.Handler().Register(&event.EventHandler{
|
||||
EventId: liveclient.EventMessageReceive,
|
||||
Name: "controller.commandexecutor",
|
||||
Handler: danmuCommandHandler,
|
||||
})
|
||||
LiveClient.Handler().RegisterA(
|
||||
liveclient.EventMessageReceive,
|
||||
"controller.danmu.handler",
|
||||
danmuHandler)
|
||||
l().Infof("setting live client for %s success", roomId)
|
||||
}
|
||||
|
||||
func StartDanmuClient() {
|
||||
LiveClient.Connect()
|
||||
}
|
||||
|
||||
func ResetDanmuClient() {
|
||||
if LiveClient != nil {
|
||||
l().Infof("disconnect from current live client %s", LiveClient.ClientName())
|
||||
LiveClient.Disconnect()
|
||||
LiveClient.Handler().UnregisterAll()
|
||||
LiveClient = nil
|
||||
}
|
||||
}
|
||||
|
||||
func AddPlaylist(pname string, uri string) *player.Playlist {
|
||||
l().Infof("try add playlist %s with provider %s", uri, pname)
|
||||
id, err := provider.FormatPlaylistUrl(pname, uri)
|
||||
if err != nil || id == "" {
|
||||
l().Warnf("fail to format %s playlist id for %s", uri, pname)
|
||||
return nil
|
||||
}
|
||||
p := player.NewPlaylist(fmt.Sprintf("%s-%s", pname, id), player.PlaylistConfig{})
|
||||
p.Meta = provider.Meta{
|
||||
Name: pname,
|
||||
Id: id,
|
||||
}
|
||||
PlaylistManager = append(PlaylistManager, p)
|
||||
config.Player.Playlists = append(config.Player.Playlists, id)
|
||||
config.Player.PlaylistsProvider = append(config.Player.PlaylistsProvider, pname)
|
||||
return p
|
||||
}
|
||||
|
||||
func RemovePlaylist(index int) {
|
||||
l().Infof("Try to remove playlist.index=%d", index)
|
||||
if index < 0 || index >= len(PlaylistManager) {
|
||||
l().Warnf("playlist.index=%d not found", index)
|
||||
return
|
||||
}
|
||||
if index == config.Player.PlaylistIndex {
|
||||
l().Info("Delete current system playlist, reset system playlist to index = 0")
|
||||
SetSystemPlaylist(0)
|
||||
}
|
||||
if index < config.Player.PlaylistIndex {
|
||||
l().Debugf("Delete playlist before system playlist (index=%d), reduce system playlist index by 1", config.Player.PlaylistIndex)
|
||||
config.Player.PlaylistIndex = config.Player.PlaylistIndex - 1
|
||||
}
|
||||
PlaylistManager = append(PlaylistManager[:index], PlaylistManager[index+1:]...)
|
||||
config.Player.Playlists = append(config.Player.Playlists[:index], config.Player.Playlists[index+1:]...)
|
||||
config.Player.PlaylistsProvider = append(config.Player.PlaylistsProvider[:index], config.Player.PlaylistsProvider[index+1:]...)
|
||||
}
|
||||
|
||||
func SetSystemPlaylist(index int) {
|
||||
l().Infof("try set system playlist to playlist.id=%d", index)
|
||||
if index < 0 || index >= len(PlaylistManager) {
|
||||
l().Warn("playlist.index=%d not found", index)
|
||||
return
|
||||
}
|
||||
err := PreparePlaylist(PlaylistManager[index])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
medias := PlaylistManager[index].Playlist
|
||||
config.Player.PlaylistIndex = index
|
||||
ApplyUser(medias, player.PlaylistUser)
|
||||
SystemPlaylist.Replace(medias)
|
||||
}
|
||||
|
||||
func PreparePlaylistByIndex(index int) {
|
||||
l().Infof("try prepare playlist.id=%d", index)
|
||||
if index < 0 || index >= len(PlaylistManager) {
|
||||
l().Warn("playlist.id=%d not found", index)
|
||||
return
|
||||
}
|
||||
err := PreparePlaylist(PlaylistManager[index])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestController(t *testing.T) {
|
||||
fmt.Println(LiveClient == nil)
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/liveclient"
|
||||
)
|
||||
|
||||
var DanmuHandlers []DanmuHandler
|
||||
|
||||
type DanmuHandler interface {
|
||||
Execute(anmu *liveclient.DanmuMessage)
|
||||
}
|
||||
|
||||
func AddDanmuHandler(handlers ...DanmuHandler) {
|
||||
DanmuHandlers = append(DanmuHandlers, handlers...)
|
||||
}
|
||||
|
||||
func danmuHandler(event *event.Event) {
|
||||
danmu := event.Data.(*liveclient.DanmuMessage)
|
||||
for _, cmd := range DanmuHandlers {
|
||||
cmd.Execute(danmu)
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/liveclient"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var MainPlayer *player.Player
|
||||
var UserPlaylist *player.Playlist
|
||||
var History *player.Playlist
|
||||
var HistoryUser *player.User
|
||||
var SystemPlaylist *player.Playlist
|
||||
var LiveClient liveclient.LiveClient
|
||||
var PlaylistManager []*player.Playlist
|
||||
var CurrentLyric *player.Lyric
|
||||
var CurrentMedia *player.Media
|
||||
|
||||
func Initialize() {
|
||||
|
||||
MainPlayer = player.NewPlayer()
|
||||
SetAudioDevice(config.Player.AudioDevice)
|
||||
SetVolume(config.Player.Volume)
|
||||
UserPlaylist = player.NewPlaylist("user", player.PlaylistConfig{RandomNext: false})
|
||||
SystemPlaylist = player.NewPlaylist("system", player.PlaylistConfig{RandomNext: config.Player.PlaylistRandom})
|
||||
PlaylistManager = make([]*player.Playlist, 0)
|
||||
CurrentLyric = player.NewLyric("")
|
||||
loadPlaylists()
|
||||
|
||||
History = player.NewPlaylist("history", player.PlaylistConfig{RandomNext: false})
|
||||
HistoryUser = &player.User{Name: "History"}
|
||||
|
||||
MainPlayer.ObserveProperty("idle-active", handleMpvIdlePlayNext)
|
||||
UserPlaylist.Handler.RegisterA(player.EventPlaylistInsert, "controller.playnextwhenadd", handlePlaylistAdd)
|
||||
MainPlayer.ObserveProperty("time-pos", handleLyricUpdate)
|
||||
MainPlayer.Start()
|
||||
|
||||
}
|
||||
|
||||
func loadPlaylists() {
|
||||
l().Info("Loading playlists ", config.Player.Playlists, config.Player.PlaylistsProvider)
|
||||
if len(config.Player.Playlists) != len(config.Player.Playlists) {
|
||||
l().Warn("playlist id and provider does not have same length")
|
||||
return
|
||||
}
|
||||
for i := 0; i < len(config.Player.Playlists); i++ {
|
||||
pname := config.Player.PlaylistsProvider[i]
|
||||
id := config.Player.Playlists[i]
|
||||
p := player.NewPlaylist(fmt.Sprintf("%s-%s", pname, id), player.PlaylistConfig{})
|
||||
p.Meta = provider.Meta{
|
||||
Name: pname,
|
||||
Id: id,
|
||||
}
|
||||
PlaylistManager = append(PlaylistManager, p)
|
||||
}
|
||||
if config.Player.PlaylistIndex < 0 || config.Player.PlaylistIndex >= len(config.Player.Playlists) {
|
||||
l().Warn("playlist index did not find")
|
||||
return
|
||||
}
|
||||
go func() {
|
||||
c := config.Player.PlaylistIndex
|
||||
err := PreparePlaylist(PlaylistManager[c])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
SetSystemPlaylist(c)
|
||||
}()
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/event"
|
||||
"AynaLivePlayer/player"
|
||||
"github.com/aynakeya/go-mpv"
|
||||
)
|
||||
|
||||
func handleMpvIdlePlayNext(property *mpv.EventProperty) {
|
||||
isIdle := property.Data.(mpv.Node).Value.(bool)
|
||||
if isIdle {
|
||||
l().Info("mpv went idle, try play next")
|
||||
PlayNext()
|
||||
}
|
||||
}
|
||||
|
||||
func handlePlaylistAdd(event *event.Event) {
|
||||
if MainPlayer.IsIdle() {
|
||||
PlayNext()
|
||||
return
|
||||
}
|
||||
if config.Player.SkipPlaylist && CurrentMedia != nil && CurrentMedia.User == player.PlaylistUser {
|
||||
PlayNext()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func handleLyricUpdate(property *mpv.EventProperty) {
|
||||
if property.Data == nil {
|
||||
return
|
||||
}
|
||||
t := property.Data.(mpv.Node).Value.(float64)
|
||||
CurrentLyric.Update(t)
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
)
|
||||
|
||||
func PlayNext() {
|
||||
l().Info("try to play next possible media")
|
||||
if UserPlaylist.Size() == 0 && SystemPlaylist.Size() == 0 {
|
||||
return
|
||||
}
|
||||
var media *player.Media
|
||||
if UserPlaylist.Size() != 0 {
|
||||
media = UserPlaylist.Pop()
|
||||
} else if SystemPlaylist.Size() != 0 {
|
||||
media = SystemPlaylist.Next()
|
||||
}
|
||||
Play(media)
|
||||
}
|
||||
|
||||
func Play(media *player.Media) {
|
||||
l().Infof("prepare media %s", media.Title)
|
||||
err := PrepareMedia(media)
|
||||
if err != nil {
|
||||
l().Warn("prepare media failed. try play next")
|
||||
PlayNext()
|
||||
return
|
||||
}
|
||||
CurrentMedia = media
|
||||
AddToHistory(media)
|
||||
if err := MainPlayer.Play(media); err != nil {
|
||||
l().Warn("play failed", err)
|
||||
return
|
||||
}
|
||||
CurrentLyric.Reload(media.Lyric)
|
||||
// reset
|
||||
media.Url = ""
|
||||
}
|
||||
|
||||
func Add(keyword string, user interface{}) {
|
||||
media := MediaMatch(keyword)
|
||||
if media == nil {
|
||||
medias, err := Search(keyword)
|
||||
if err != nil {
|
||||
l().Warnf("search for %s, got error %s", keyword, err)
|
||||
return
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
l().Info("search for %s, got no result", keyword)
|
||||
return
|
||||
}
|
||||
media = medias[0]
|
||||
}
|
||||
media.User = user
|
||||
l().Infof("add media %s (%s)", media.Title, media.Artist)
|
||||
UserPlaylist.Insert(-1, media)
|
||||
}
|
||||
|
||||
func AddWithProvider(keyword string, pname string, user interface{}) {
|
||||
media := provider.MatchMedia(pname, keyword)
|
||||
if media == nil {
|
||||
medias, err := provider.Search(pname, keyword)
|
||||
if err != nil {
|
||||
l().Warnf("search for %s, got error %s", keyword, err)
|
||||
return
|
||||
}
|
||||
if len(medias) == 0 {
|
||||
l().Infof("search for %s, got no result", keyword)
|
||||
return
|
||||
}
|
||||
media = medias[0]
|
||||
}
|
||||
media.User = user
|
||||
l().Infof("add media %s (%s)", media.Title, media.Artist)
|
||||
UserPlaylist.Insert(-1, media)
|
||||
}
|
||||
|
||||
func Seek(position float64, absolute bool) {
|
||||
if err := MainPlayer.Seek(position, absolute); err != nil {
|
||||
l().Warnf("seek to position %f (%t) failed, %s", position, absolute, err)
|
||||
}
|
||||
}
|
||||
|
||||
func Toggle() (b bool) {
|
||||
var err error
|
||||
if MainPlayer.IsPaused() {
|
||||
err = MainPlayer.Unpause()
|
||||
b = false
|
||||
} else {
|
||||
err = MainPlayer.Pause()
|
||||
b = true
|
||||
}
|
||||
if err != nil {
|
||||
l().Warn("toggle failed", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func SetVolume(volume float64) {
|
||||
if MainPlayer.SetVolume(volume) != nil {
|
||||
l().Warnf("set mpv volume to %f failed", volume)
|
||||
return
|
||||
}
|
||||
config.Player.Volume = volume
|
||||
}
|
||||
|
||||
func Destroy() {
|
||||
MainPlayer.Stop()
|
||||
}
|
||||
|
||||
func GetAudioDevices() []player.AudioDevice {
|
||||
dl, err := MainPlayer.GetAudioDeviceList()
|
||||
if err != nil {
|
||||
return make([]player.AudioDevice, 0)
|
||||
}
|
||||
return dl
|
||||
}
|
||||
|
||||
func SetAudioDevice(device string) {
|
||||
l().Infof("set audio device to %s", device)
|
||||
if err := MainPlayer.SetAudioDevice(device); err != nil {
|
||||
l().Warnf("set mpv audio device to %s failed, %s", device, err)
|
||||
MainPlayer.SetAudioDevice("auto")
|
||||
config.Player.AudioDevice = "auto"
|
||||
return
|
||||
}
|
||||
config.Player.AudioDevice = device
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package controller
|
||||
|
||||
import "AynaLivePlayer/player"
|
||||
|
||||
func AddToHistory(media *player.Media) {
|
||||
l().Tracef("add media %s (%s) to history", media.Title, media.Artist)
|
||||
media = media.Copy()
|
||||
// reset url for future use
|
||||
media.Url = ""
|
||||
if History.Size() >= 1024 {
|
||||
History.Replace([]*player.Media{})
|
||||
}
|
||||
History.Push(media)
|
||||
return
|
||||
}
|
||||
|
||||
func ToHistoryMedia(media *player.Media) *player.Media {
|
||||
media = media.Copy()
|
||||
media.User = HistoryUser
|
||||
return media
|
||||
}
|
||||
|
||||
func ToSystemMedia(media *player.Media) *player.Media {
|
||||
media = media.Copy()
|
||||
media.User = player.SystemUser
|
||||
return media
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package controller
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Enable() error
|
||||
Disable() error
|
||||
}
|
||||
|
||||
func LoadPlugin(plugin Plugin) {
|
||||
l().Info("Loading plugin: " + plugin.Name())
|
||||
if err := plugin.Enable(); err != nil {
|
||||
l().Warnf("Failed to load plugin: %s, %s", plugin.Name(), err)
|
||||
}
|
||||
}
|
||||
|
||||
func LoadPlugins(plugins ...Plugin) {
|
||||
for _, plugin := range plugins {
|
||||
LoadPlugin(plugin)
|
||||
}
|
||||
}
|
||||
|
||||
func ClosePlugins(plugins ...Plugin) {
|
||||
for _, plugin := range plugins {
|
||||
err := plugin.Disable()
|
||||
if err != nil {
|
||||
l().Warnf("Failed to close plugin: %s, %s", plugin.Name(), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/player"
|
||||
"AynaLivePlayer/provider"
|
||||
)
|
||||
|
||||
func PrepareMedia(media *player.Media) error {
|
||||
var err error
|
||||
if media.Title == "" || !media.Cover.Exists() {
|
||||
l().Trace("fetching media info")
|
||||
if err = provider.UpdateMedia(media); err != nil {
|
||||
l().Warn("fail to prepare media when fetch info", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if media.Url == "" {
|
||||
l().Trace("fetching media url")
|
||||
if err = provider.UpdateMediaUrl(media); err != nil {
|
||||
l().Warn("fail to prepare media when url", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if media.Lyric == "" {
|
||||
l().Trace("fetching media lyric")
|
||||
if err = provider.UpdateMediaLyric(media); err != nil {
|
||||
l().Warn("fail to prepare media when lyric", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MediaMatch(keyword string) *player.Media {
|
||||
l().Infof("Match media for %s", keyword)
|
||||
for _, p := range config.Provider.Priority {
|
||||
if pr, ok := provider.Providers[p]; ok {
|
||||
m := pr.MatchMedia(keyword)
|
||||
if m == nil {
|
||||
continue
|
||||
}
|
||||
if err := provider.UpdateMedia(m); err == nil {
|
||||
return m
|
||||
}
|
||||
} else {
|
||||
l().Warnf("Provider %s not exist", p)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Search(keyword string) ([]*player.Media, error) {
|
||||
l().Infof("Search for %s", keyword)
|
||||
for _, p := range config.Provider.Priority {
|
||||
if pr, ok := provider.Providers[p]; ok {
|
||||
r, err := pr.Search(keyword)
|
||||
if err != nil {
|
||||
l().Warn("Provider %s return err", err)
|
||||
continue
|
||||
}
|
||||
return r, err
|
||||
} else {
|
||||
l().Warnf("Provider %s not exist", p)
|
||||
}
|
||||
}
|
||||
return nil, provider.ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func SearchWithProvider(keyword string, p string) ([]*player.Media, error) {
|
||||
l().Infof("Search for %s using %s", keyword, p)
|
||||
if pr, ok := provider.Providers[p]; ok {
|
||||
r, err := pr.Search(keyword)
|
||||
return r, err
|
||||
}
|
||||
l().Warnf("Provider %s not exist", p)
|
||||
return nil, provider.ErrorNoSuchProvider
|
||||
}
|
||||
|
||||
func ApplyUser(medias []*player.Media, user interface{}) {
|
||||
for _, m := range medias {
|
||||
m.User = user
|
||||
}
|
||||
}
|
||||
|
||||
func PreparePlaylist(playlist *player.Playlist) error {
|
||||
l().Debug("Prepare playlist ", playlist.Meta.(provider.Meta))
|
||||
medias, err := provider.GetPlaylist(playlist.Meta.(provider.Meta))
|
||||
if err != nil {
|
||||
l().Warn("prepare playlist failed ", err)
|
||||
return err
|
||||
}
|
||||
ApplyUser(medias, player.SystemUser)
|
||||
playlist.Replace(medias)
|
||||
return nil
|
||||
}
|
||||
56
core/events/event.go
Normal file
56
core/events/event.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package events
|
||||
|
||||
//const (
|
||||
// EventPlay string = "player.play"
|
||||
// EventPlayed string = "player.played"
|
||||
// EventPlaylistPreInsert string = "playlist.insert.pre"
|
||||
// EventPlaylistInsert string = "playlist.insert.after"
|
||||
// EventPlaylistUpdate string = "playlist.update"
|
||||
// EventLyricUpdate string = "lyric.update"
|
||||
// EventLyricReload string = "lyric.reload"
|
||||
//)
|
||||
|
||||
const ErrorUpdate = "update.error"
|
||||
|
||||
type ErrorUpdateEvent struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
//
|
||||
//func EventPlayerPropertyUpdate(property model.PlayerProperty) string {
|
||||
// return string("player.property.update." + string(property))
|
||||
//}
|
||||
//
|
||||
//type PlaylistInsertEvent struct {
|
||||
// Playlist *model.Playlist
|
||||
// Index int
|
||||
// Media *model.Media
|
||||
//}
|
||||
//
|
||||
//type PlaylistUpdateEvent struct {
|
||||
// Playlist *model.Playlist // Playlist is a copy of the playlist
|
||||
//}
|
||||
//
|
||||
//type PlayEvent struct {
|
||||
// Media *model.Media
|
||||
//}
|
||||
//
|
||||
//type LyricUpdateEvent struct {
|
||||
// Lyrics *model.Lyric
|
||||
// Time float64
|
||||
// Lyric *model.LyricContext
|
||||
//}
|
||||
//
|
||||
//type LyricReloadEvent struct {
|
||||
// Lyrics *model.Lyric
|
||||
//}
|
||||
//
|
||||
//type PlayerPropertyUpdateEvent struct {
|
||||
// Property model.PlayerProperty
|
||||
// Value model.PlayerPropertyValue
|
||||
//}
|
||||
//
|
||||
//type LiveRoomStatusUpdateEvent struct {
|
||||
// RoomTitle string
|
||||
// Status bool
|
||||
//}
|
||||
7
core/events/gui.go
Normal file
7
core/events/gui.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package events
|
||||
|
||||
const GUISetPlayerWindowOpenCmd = "cmd.gui.player_window.op"
|
||||
|
||||
type GUISetPlayerWindowOpenCmdEvent struct {
|
||||
SetOpen bool
|
||||
}
|
||||
73
core/events/liveroom.go
Normal file
73
core/events/liveroom.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
liveroomsdk "github.com/AynaLivePlayer/liveroom-sdk"
|
||||
)
|
||||
|
||||
//const (
|
||||
// LiveRoomStatusChange string = "liveclient.status.change"
|
||||
// LiveRoomMessageReceive string = "liveclient.message.receive"
|
||||
//)
|
||||
//
|
||||
//type StatusChangeEvent struct {
|
||||
// Connected bool
|
||||
// Client adapter.LiveClient
|
||||
//}
|
||||
|
||||
const LiveRoomAddCmd = "cmd.liveroom.add"
|
||||
|
||||
type LiveRoomAddCmdEvent struct {
|
||||
Title string
|
||||
Provider string
|
||||
RoomKey string
|
||||
}
|
||||
|
||||
const LiveRoomProviderUpdate = "update.liveroom.provider"
|
||||
|
||||
type LiveRoomProviderUpdateEvent struct {
|
||||
Providers []model.LiveRoomProviderInfo
|
||||
}
|
||||
|
||||
const LiveRoomRemoveCmd = "cmd.liveroom.remove"
|
||||
|
||||
type LiveRoomRemoveCmdEvent struct {
|
||||
Identifier string
|
||||
}
|
||||
|
||||
const LiveRoomRoomsUpdate = "update.liveroom.rooms"
|
||||
|
||||
type LiveRoomRoomsUpdateEvent struct {
|
||||
Rooms []model.LiveRoom
|
||||
}
|
||||
|
||||
const LiveRoomStatusUpdate = "update.liveroom.status"
|
||||
|
||||
type LiveRoomStatusUpdateEvent struct {
|
||||
Room model.LiveRoom
|
||||
}
|
||||
|
||||
const LiveRoomConfigChangeCmd = "cmd.liveroom.config.change"
|
||||
|
||||
type LiveRoomConfigChangeCmdEvent struct {
|
||||
Identifier string
|
||||
Config model.LiveRoomConfig
|
||||
}
|
||||
|
||||
const LiveRoomOperationCmd = "cmd.liveroom.operation"
|
||||
|
||||
type LiveRoomOperationCmdEvent struct {
|
||||
Identifier string
|
||||
SetConnect bool // connect or disconnect
|
||||
}
|
||||
|
||||
const LiveRoomOperationFinish = "update.liveroom.operation"
|
||||
|
||||
type LiveRoomOperationFinishEvent struct {
|
||||
}
|
||||
|
||||
const LiveRoomMessageReceive = "update.liveroom.message"
|
||||
|
||||
type LiveRoomMessageReceiveEvent struct {
|
||||
Message *liveroomsdk.Message
|
||||
}
|
||||
78
core/events/mapping.go
Normal file
78
core/events/mapping.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
var EventsMapping = map[string]any{
|
||||
LiveRoomAddCmd: LiveRoomAddCmdEvent{},
|
||||
LiveRoomProviderUpdate: LiveRoomProviderUpdateEvent{},
|
||||
LiveRoomRemoveCmd: LiveRoomRemoveCmdEvent{},
|
||||
LiveRoomRoomsUpdate: LiveRoomRoomsUpdateEvent{},
|
||||
LiveRoomStatusUpdate: LiveRoomStatusUpdateEvent{},
|
||||
LiveRoomConfigChangeCmd: LiveRoomConfigChangeCmdEvent{},
|
||||
LiveRoomOperationCmd: LiveRoomOperationCmdEvent{},
|
||||
PlayerVolumeChangeCmd: PlayerVolumeChangeCmdEvent{},
|
||||
PlayerPlayCmd: PlayerPlayCmdEvent{},
|
||||
PlayerPlayErrorUpdate: PlayerPlayErrorUpdateEvent{},
|
||||
PlayerSeekCmd: PlayerSeekCmdEvent{},
|
||||
PlayerToggleCmd: PlayerToggleCmdEvent{},
|
||||
PlayerSetPauseCmd: PlayerSetPauseCmdEvent{},
|
||||
PlayerPlayNextCmd: PlayerPlayNextCmdEvent{},
|
||||
PlayerLyricRequestCmd: PlayerLyricRequestCmdEvent{},
|
||||
PlayerLyricReload: PlayerLyricReloadEvent{},
|
||||
PlayerLyricPosUpdate: PlayerLyricPosUpdateEvent{},
|
||||
PlayerPlayingUpdate: PlayerPlayingUpdateEvent{},
|
||||
PlayerPropertyPauseUpdate: PlayerPropertyPauseUpdateEvent{},
|
||||
PlayerPropertyPercentPosUpdate: PlayerPropertyPercentPosUpdateEvent{},
|
||||
PlayerPropertyStateUpdate: PlayerPropertyStateUpdateEvent{},
|
||||
PlayerPropertyTimePosUpdate: PlayerPropertyTimePosUpdateEvent{},
|
||||
PlayerPropertyDurationUpdate: PlayerPropertyDurationUpdateEvent{},
|
||||
PlayerPropertyVolumeUpdate: PlayerPropertyVolumeUpdateEvent{},
|
||||
PlayerVideoPlayerSetWindowHandleCmd: PlayerVideoPlayerSetWindowHandleCmdEvent{},
|
||||
PlayerSetAudioDeviceCmd: PlayerSetAudioDeviceCmdEvent{},
|
||||
PlayerAudioDeviceUpdate: PlayerAudioDeviceUpdateEvent{},
|
||||
PlaylistManagerSetSystemCmd: PlaylistManagerSetSystemCmdEvent{},
|
||||
PlaylistManagerSystemUpdate: PlaylistManagerSystemUpdateEvent{},
|
||||
PlaylistManagerRefreshCurrentCmd: PlaylistManagerRefreshCurrentCmdEvent{},
|
||||
PlaylistManagerGetCurrentCmd: PlaylistManagerGetCurrentCmdEvent{},
|
||||
PlaylistManagerCurrentUpdate: PlaylistManagerCurrentUpdateEvent{},
|
||||
PlaylistManagerInfoUpdate: PlaylistManagerInfoUpdateEvent{},
|
||||
PlaylistManagerAddPlaylistCmd: PlaylistManagerAddPlaylistCmdEvent{},
|
||||
PlaylistManagerRemovePlaylistCmd: PlaylistManagerRemovePlaylistCmdEvent{},
|
||||
MediaProviderUpdate: MediaProviderUpdateEvent{},
|
||||
SearchCmd: SearchCmdEvent{},
|
||||
SearchResultUpdate: SearchResultUpdateEvent{},
|
||||
GUISetPlayerWindowOpenCmd: GUISetPlayerWindowOpenCmdEvent{},
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, v := range []model.PlaylistID{model.PlaylistIDSystem, model.PlaylistIDPlayer, model.PlaylistIDHistory} {
|
||||
EventsMapping[PlaylistDetailUpdate(v)] = PlaylistDetailUpdateEvent{}
|
||||
EventsMapping[PlaylistMoveCmd(v)] = PlaylistMoveCmdEvent{}
|
||||
EventsMapping[PlaylistSetIndexCmd(v)] = PlaylistSetIndexCmdEvent{}
|
||||
EventsMapping[PlaylistDeleteCmd(v)] = PlaylistDeleteCmdEvent{}
|
||||
EventsMapping[PlaylistInsertCmd(v)] = PlaylistInsertCmdEvent{}
|
||||
EventsMapping[PlaylistInsertUpdate(v)] = PlaylistInsertUpdateEvent{}
|
||||
EventsMapping[PlaylistNextCmd(v)] = PlaylistNextCmdEvent{}
|
||||
EventsMapping[PlaylistNextUpdate(v)] = PlaylistNextUpdateEvent{}
|
||||
EventsMapping[PlaylistModeChangeCmd(v)] = PlaylistModeChangeCmdEvent{}
|
||||
EventsMapping[PlaylistModeChangeUpdate(v)] = PlaylistModeChangeUpdateEvent{}
|
||||
}
|
||||
}
|
||||
|
||||
func UnmarshalEventData(eventId string, data []byte) (any, error) {
|
||||
val, ok := EventsMapping[eventId]
|
||||
if !ok {
|
||||
return nil, errors.New("event id not found")
|
||||
}
|
||||
newVal := reflect.New(reflect.TypeOf(val))
|
||||
err := json.Unmarshal(data, newVal.Interface())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newVal.Elem().Interface(), nil
|
||||
}
|
||||
22
core/events/mapping_test.go
Normal file
22
core/events/mapping_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUnmarshalEventData(t *testing.T) {
|
||||
eventData := LiveRoomAddCmdEvent{
|
||||
Title: "test",
|
||||
Provider: "asdfasd",
|
||||
RoomKey: "asdfasdf",
|
||||
}
|
||||
data, err := json.Marshal(eventData)
|
||||
require.NoError(t, err)
|
||||
val, err := UnmarshalEventData(LiveRoomAddCmd, data)
|
||||
require.NoError(t, err)
|
||||
resultData, ok := val.(LiveRoomAddCmdEvent)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, eventData, resultData)
|
||||
}
|
||||
49
core/events/player_control.go
Normal file
49
core/events/player_control.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
const PlayerVolumeChangeCmd = "cmd.player.op.change_volume"
|
||||
|
||||
type PlayerVolumeChangeCmdEvent struct {
|
||||
Volume float64 // Volume from 0-100
|
||||
}
|
||||
|
||||
const PlayerPlayCmd = "cmd.player.op.play"
|
||||
|
||||
type PlayerPlayCmdEvent struct {
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
const PlayerPlayErrorUpdate = "update.player.play.error"
|
||||
|
||||
type PlayerPlayErrorUpdateEvent struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
const PlayerSeekCmd = "cmd.player.op.seek"
|
||||
|
||||
type PlayerSeekCmdEvent struct {
|
||||
Position float64
|
||||
// Absolute is the seek mode.
|
||||
// if absolute = true : position is the time in second
|
||||
// if absolute = false: position is in percentage eg 0.1 0.2
|
||||
Absolute bool
|
||||
}
|
||||
|
||||
const PlayerToggleCmd = "cmd.player.op.toggle"
|
||||
|
||||
type PlayerToggleCmdEvent struct {
|
||||
}
|
||||
|
||||
const PlayerSetPauseCmd = "cmd.player.op.pause"
|
||||
|
||||
type PlayerSetPauseCmdEvent struct {
|
||||
Pause bool
|
||||
}
|
||||
|
||||
const PlayerPlayNextCmd = "cmd.player.op.next"
|
||||
|
||||
type PlayerPlayNextCmdEvent struct {
|
||||
}
|
||||
23
core/events/player_lyric.go
Normal file
23
core/events/player_lyric.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package events
|
||||
|
||||
import "github.com/AynaLivePlayer/miaosic"
|
||||
|
||||
const PlayerLyricRequestCmd = "cmd.player.lyric.request"
|
||||
|
||||
type PlayerLyricRequestCmdEvent struct {
|
||||
}
|
||||
|
||||
const PlayerLyricReload = "update.player.lyric.reload"
|
||||
|
||||
type PlayerLyricReloadEvent struct {
|
||||
Lyrics miaosic.Lyrics
|
||||
}
|
||||
|
||||
const PlayerLyricPosUpdate = "update.player.lyric.pos"
|
||||
|
||||
type PlayerLyricPosUpdateEvent struct {
|
||||
Time float64
|
||||
CurrentIndex int // -1 means no lyric
|
||||
CurrentLine miaosic.LyricLine
|
||||
Total int // total lyric count
|
||||
}
|
||||
46
core/events/player_property.go
Normal file
46
core/events/player_property.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package events
|
||||
|
||||
import "AynaLivePlayer/core/model"
|
||||
|
||||
const PlayerPlayingUpdate = "update.player.playing"
|
||||
|
||||
type PlayerPlayingUpdateEvent struct {
|
||||
Media model.Media
|
||||
Removed bool // if no media is playing, removed is true
|
||||
}
|
||||
|
||||
const PlayerPropertyPauseUpdate = "update.player.property.pause"
|
||||
|
||||
type PlayerPropertyPauseUpdateEvent struct {
|
||||
Paused bool
|
||||
}
|
||||
|
||||
const PlayerPropertyPercentPosUpdate = "update.player.property.percent_pos"
|
||||
|
||||
type PlayerPropertyPercentPosUpdateEvent struct {
|
||||
PercentPos float64
|
||||
}
|
||||
|
||||
const PlayerPropertyStateUpdate = "update.player.property.state"
|
||||
|
||||
type PlayerPropertyStateUpdateEvent struct {
|
||||
State model.PlayerState
|
||||
}
|
||||
|
||||
const PlayerPropertyTimePosUpdate = "update.player.property.time_pos"
|
||||
|
||||
type PlayerPropertyTimePosUpdateEvent struct {
|
||||
TimePos float64 // Time in seconds
|
||||
}
|
||||
|
||||
const PlayerPropertyDurationUpdate = "update.player.property.duration"
|
||||
|
||||
type PlayerPropertyDurationUpdateEvent struct {
|
||||
Duration float64 // Duration in seconds
|
||||
}
|
||||
|
||||
const PlayerPropertyVolumeUpdate = "update.player.property.volume"
|
||||
|
||||
type PlayerPropertyVolumeUpdateEvent struct {
|
||||
Volume float64 // Volume from 0-100
|
||||
}
|
||||
22
core/events/player_videoplayer.go
Normal file
22
core/events/player_videoplayer.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package events
|
||||
|
||||
import "AynaLivePlayer/core/model"
|
||||
|
||||
const PlayerVideoPlayerSetWindowHandleCmd = "cmd.player.videoplayer.set_window_handle"
|
||||
|
||||
type PlayerVideoPlayerSetWindowHandleCmdEvent struct {
|
||||
Handle uintptr
|
||||
}
|
||||
|
||||
const PlayerSetAudioDeviceCmd = "cmd.player.set_audio_device"
|
||||
|
||||
type PlayerSetAudioDeviceCmdEvent struct {
|
||||
Device string
|
||||
}
|
||||
|
||||
const PlayerAudioDeviceUpdate = "update.player.audio_device"
|
||||
|
||||
type PlayerAudioDeviceUpdateEvent struct {
|
||||
Current string
|
||||
Devices []model.AudioDevice
|
||||
}
|
||||
88
core/events/playlist.go
Normal file
88
core/events/playlist.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
func PlaylistDetailUpdate(id model.PlaylistID) string {
|
||||
return string("update.playlist.detail." + id)
|
||||
}
|
||||
|
||||
type PlaylistDetailUpdateEvent struct {
|
||||
Medias []model.Media
|
||||
}
|
||||
|
||||
func PlaylistMoveCmd(id model.PlaylistID) string {
|
||||
return string("cmd.playlist.move." + id)
|
||||
}
|
||||
|
||||
type PlaylistMoveCmdEvent struct {
|
||||
From int
|
||||
To int
|
||||
}
|
||||
|
||||
func PlaylistSetIndexCmd(id model.PlaylistID) string {
|
||||
return string("cmd.playlist.setindex." + id)
|
||||
}
|
||||
|
||||
type PlaylistSetIndexCmdEvent struct {
|
||||
Index int
|
||||
}
|
||||
|
||||
func PlaylistDeleteCmd(id model.PlaylistID) string {
|
||||
return string("cmd.playlist.delete." + id)
|
||||
}
|
||||
|
||||
type PlaylistDeleteCmdEvent struct {
|
||||
Index int
|
||||
}
|
||||
|
||||
func PlaylistInsertCmd(id model.PlaylistID) string {
|
||||
return string("cmd.playlist.insert." + id)
|
||||
}
|
||||
|
||||
type PlaylistInsertCmdEvent struct {
|
||||
Position int // position to insert, -1 means last one
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
func PlaylistInsertUpdate(id model.PlaylistID) string {
|
||||
return string("update.playlist.insert." + id)
|
||||
}
|
||||
|
||||
type PlaylistInsertUpdateEvent struct {
|
||||
Position int // position to insert, -1 means last one
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
func PlaylistNextCmd(id model.PlaylistID) string {
|
||||
return string("cmd.playlist.next." + id)
|
||||
}
|
||||
|
||||
type PlaylistNextCmdEvent struct {
|
||||
Remove bool // remove the media after next
|
||||
}
|
||||
|
||||
func PlaylistNextUpdate(id model.PlaylistID) string {
|
||||
return string("update.playlist.next." + id)
|
||||
}
|
||||
|
||||
type PlaylistNextUpdateEvent struct {
|
||||
Media model.Media
|
||||
}
|
||||
|
||||
func PlaylistModeChangeCmd(id model.PlaylistID) string {
|
||||
return string("cmd.playlist.mode." + id)
|
||||
}
|
||||
|
||||
type PlaylistModeChangeCmdEvent struct {
|
||||
Mode model.PlaylistMode
|
||||
}
|
||||
|
||||
func PlaylistModeChangeUpdate(id model.PlaylistID) string {
|
||||
return string("update.playlist.mode." + id)
|
||||
}
|
||||
|
||||
type PlaylistModeChangeUpdateEvent struct {
|
||||
Mode model.PlaylistMode
|
||||
}
|
||||
54
core/events/playlists.go
Normal file
54
core/events/playlists.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
const PlaylistManagerSetSystemCmd = "cmd.playlist.manager.set.system"
|
||||
|
||||
type PlaylistManagerSetSystemCmdEvent struct {
|
||||
PlaylistID string
|
||||
}
|
||||
|
||||
const PlaylistManagerSystemUpdate = "update.playlist.manager.system"
|
||||
|
||||
type PlaylistManagerSystemUpdateEvent struct {
|
||||
Info model.PlaylistInfo
|
||||
}
|
||||
|
||||
const PlaylistManagerRefreshCurrentCmd = "cmd.playlist.manager.refresh.current"
|
||||
|
||||
type PlaylistManagerRefreshCurrentCmdEvent struct {
|
||||
PlaylistID string
|
||||
}
|
||||
|
||||
const PlaylistManagerGetCurrentCmd = "cmd.playlist.manager.get.current"
|
||||
|
||||
type PlaylistManagerGetCurrentCmdEvent struct {
|
||||
PlaylistID string
|
||||
}
|
||||
|
||||
const PlaylistManagerCurrentUpdate = "update.playlist.manager.current"
|
||||
|
||||
type PlaylistManagerCurrentUpdateEvent struct {
|
||||
Medias []model.Media
|
||||
}
|
||||
|
||||
const PlaylistManagerInfoUpdate = "update.playlist.manager.info"
|
||||
|
||||
type PlaylistManagerInfoUpdateEvent struct {
|
||||
Playlists []model.PlaylistInfo
|
||||
}
|
||||
|
||||
const PlaylistManagerAddPlaylistCmd = "cmd.playlist.manager.add"
|
||||
|
||||
type PlaylistManagerAddPlaylistCmdEvent struct {
|
||||
Provider string
|
||||
URL string
|
||||
}
|
||||
|
||||
const PlaylistManagerRemovePlaylistCmd = "cmd.playlist.manager.remove"
|
||||
|
||||
type PlaylistManagerRemovePlaylistCmdEvent struct {
|
||||
PlaylistID string
|
||||
}
|
||||
7
core/events/provider.go
Normal file
7
core/events/provider.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package events
|
||||
|
||||
const MediaProviderUpdate = "update.media.provider.update"
|
||||
|
||||
type MediaProviderUpdateEvent struct {
|
||||
Providers []string
|
||||
}
|
||||
18
core/events/search.go
Normal file
18
core/events/search.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
)
|
||||
|
||||
const SearchCmd = "cmd.search"
|
||||
|
||||
type SearchCmdEvent struct {
|
||||
Keyword string
|
||||
Provider string
|
||||
}
|
||||
|
||||
const SearchResultUpdate = "update.search_result"
|
||||
|
||||
type SearchResultUpdateEvent struct {
|
||||
Medias []model.Media
|
||||
}
|
||||
15
core/events/updater.go
Normal file
15
core/events/updater.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package events
|
||||
|
||||
import "AynaLivePlayer/core/model"
|
||||
|
||||
const CheckUpdateCmd = "cmd.update.check"
|
||||
|
||||
type CheckUpdateCmdEvent struct {
|
||||
}
|
||||
|
||||
const CheckUpdateResultUpdate = "update.update.check"
|
||||
|
||||
type CheckUpdateResultUpdateEvent struct {
|
||||
HasUpdate bool
|
||||
Info model.VersionInfo
|
||||
}
|
||||
35
core/model/application.go
Normal file
35
core/model/application.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package model
|
||||
|
||||
import "fmt"
|
||||
|
||||
type VersionInfo struct {
|
||||
Version Version
|
||||
Info string
|
||||
}
|
||||
|
||||
type Version uint32
|
||||
|
||||
func (v Version) String() string {
|
||||
return fmt.Sprintf("%d.%d.%d", (v>>16)&0xff, (v>>8)&0xff, v&0xff)
|
||||
}
|
||||
|
||||
func (v Version) Major() uint8 {
|
||||
return uint8((v >> 16) & 0xff)
|
||||
}
|
||||
|
||||
func (v Version) Minor() uint8 {
|
||||
return uint8((v >> 8) & 0xff)
|
||||
}
|
||||
|
||||
func (v Version) Patch() uint8 {
|
||||
return uint8(v & 0xff)
|
||||
}
|
||||
|
||||
func VersionFromString(s string) Version {
|
||||
var major, minor, patch uint8
|
||||
_, err := fmt.Sscanf(s, "%d.%d.%d", &major, &minor, &patch)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return Version(major)<<16 | Version(minor)<<8 | Version(patch)
|
||||
}
|
||||
17
core/model/application_test.go
Normal file
17
core/model/application_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
v := Version(0x00010203)
|
||||
assert.Equal(t, "1.2.3", v.String())
|
||||
v2 := VersionFromString("1.2.3")
|
||||
assert.Equal(t, v, v2)
|
||||
v3 := VersionFromString("1.2.4")
|
||||
assert.True(t, v3 > v2)
|
||||
assert.False(t, v3 < v2)
|
||||
assert.Equal(t, Version(0), VersionFromString(""))
|
||||
}
|
||||
26
core/model/liveroom.go
Normal file
26
core/model/liveroom.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package model
|
||||
|
||||
import "github.com/AynaLivePlayer/liveroom-sdk"
|
||||
|
||||
type LiveRoomConfig struct {
|
||||
AutoConnect bool `json:"auto_connect"`
|
||||
}
|
||||
|
||||
type LiveRoom struct {
|
||||
LiveRoom liveroom.LiveRoom `json:"live_room"`
|
||||
Config LiveRoomConfig `json:"config"`
|
||||
Title string `json:"title"`
|
||||
Status bool `json:"status"`
|
||||
}
|
||||
|
||||
func (r *LiveRoom) DisplayName() string {
|
||||
if r.Title != "" {
|
||||
return r.Title
|
||||
}
|
||||
return r.LiveRoom.Identifier()
|
||||
}
|
||||
|
||||
type LiveRoomProviderInfo struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
37
core/model/media.go
Normal file
37
core/model/media.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"github.com/AynaLivePlayer/liveroom-sdk"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
var PlaylistUser = User{Name: "Playlists"}
|
||||
var SystemUser = User{Name: "System"}
|
||||
|
||||
type Media struct {
|
||||
Info miaosic.MediaInfo
|
||||
User interface{}
|
||||
}
|
||||
|
||||
func (m *Media) IsLiveRoomUser() bool {
|
||||
_, ok := m.User.(liveroom.User)
|
||||
return ok
|
||||
}
|
||||
|
||||
func (m *Media) ToUser() User {
|
||||
if u, ok := m.User.(User); ok {
|
||||
return u
|
||||
}
|
||||
return User{Name: m.DanmuUser().Username}
|
||||
}
|
||||
|
||||
func (m *Media) DanmuUser() liveroom.User {
|
||||
if u, ok := m.User.(liveroom.User); ok {
|
||||
return u
|
||||
}
|
||||
return liveroom.User{}
|
||||
}
|
||||
30
core/model/player.go
Normal file
30
core/model/player.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package model
|
||||
|
||||
type AudioDevice struct {
|
||||
Name string
|
||||
Description string
|
||||
}
|
||||
|
||||
type PlayerState int
|
||||
|
||||
const (
|
||||
PlayerStatePlaying PlayerState = iota
|
||||
PlayerStateLoading
|
||||
PlayerStateIdle
|
||||
)
|
||||
|
||||
func (s PlayerState) NextState(next PlayerState) PlayerState {
|
||||
if s == PlayerStatePlaying {
|
||||
return next
|
||||
}
|
||||
if s == PlayerStateIdle {
|
||||
return next
|
||||
}
|
||||
if s == PlayerStateLoading {
|
||||
if next != PlayerStatePlaying {
|
||||
return PlayerStateLoading
|
||||
}
|
||||
return next
|
||||
}
|
||||
return next
|
||||
}
|
||||
31
core/model/playlist.go
Normal file
31
core/model/playlist.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package model
|
||||
|
||||
import "github.com/AynaLivePlayer/miaosic"
|
||||
|
||||
type PlaylistMode int
|
||||
|
||||
const (
|
||||
PlaylistModeNormal PlaylistMode = iota
|
||||
PlaylistModeRandom
|
||||
PlaylistModeRepeat
|
||||
)
|
||||
|
||||
type PlaylistID string
|
||||
|
||||
const (
|
||||
PlaylistIDPlayer PlaylistID = "player"
|
||||
PlaylistIDSystem PlaylistID = "system"
|
||||
PlaylistIDHistory PlaylistID = "history"
|
||||
)
|
||||
|
||||
type PlaylistInfo struct {
|
||||
Meta miaosic.MetaData
|
||||
Title string
|
||||
}
|
||||
|
||||
func (p PlaylistInfo) DisplayName() string {
|
||||
if p.Title != "" {
|
||||
return p.Title
|
||||
}
|
||||
return p.Meta.ID()
|
||||
}
|
||||
7
core/model/plugin.go
Normal file
7
core/model/plugin.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package model
|
||||
|
||||
type Plugin interface {
|
||||
Name() string
|
||||
Enable() error
|
||||
Disable() error
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package event
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/logger"
|
||||
"github.com/sirupsen/logrus"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type EventId string
|
||||
|
||||
const MODULE_HANDLER = "EventHandler"
|
||||
|
||||
var eventLogger = logger.Logger.WithFields(logrus.Fields{
|
||||
"Module": MODULE_HANDLER,
|
||||
})
|
||||
|
||||
type Event struct {
|
||||
Id EventId
|
||||
Cancelled bool
|
||||
Data interface{}
|
||||
}
|
||||
|
||||
type EventHandlerFunc func(event *Event)
|
||||
|
||||
type EventHandler struct {
|
||||
EventId EventId
|
||||
Name string
|
||||
Handler EventHandlerFunc
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
handlers map[string]*EventHandler
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
func NewHandler() *Handler {
|
||||
return &Handler{
|
||||
handlers: make(map[string]*EventHandler),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Register(handler *EventHandler) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
eventLogger.Tracef("register new handler id=%s,name=%s", handler.EventId, handler.Name)
|
||||
h.handlers[handler.Name] = handler
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterA(id EventId, name string, handler EventHandlerFunc) {
|
||||
h.Register(&EventHandler{
|
||||
EventId: id,
|
||||
Name: name,
|
||||
Handler: handler,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) UnregisterAll() {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
eventLogger.Trace("clear all handler")
|
||||
h.handlers = make(map[string]*EventHandler)
|
||||
}
|
||||
|
||||
func (h *Handler) Unregister(name string) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
eventLogger.Tracef("unregister handler name=%s", name)
|
||||
delete(h.handlers, name)
|
||||
}
|
||||
|
||||
func (h *Handler) Call(event *Event) {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
for _, eh := range h.handlers {
|
||||
if eh.EventId == event.Id {
|
||||
eventLogger.Tracef("handler name=%s called by event_id = %s", event.Id, eh.Name)
|
||||
// todo: @3
|
||||
go eh.Handler(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) CallA(id EventId, data interface{}) {
|
||||
h.Call(&Event{
|
||||
Id: id,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
10
global/global.go
Normal file
10
global/global.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package global
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/logger"
|
||||
)
|
||||
|
||||
var Logger logger.ILogger = nil
|
||||
|
||||
var EventBus eventbus.Bus = nil
|
||||
108
go.mod
108
go.mod
@@ -1,26 +1,94 @@
|
||||
module AynaLivePlayer
|
||||
|
||||
go 1.16
|
||||
go 1.23.0
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.1.4
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.26
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1
|
||||
github.com/aynakeya/blivedm v0.1.3
|
||||
github.com/aynakeya/go-mpv v0.0.4
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jinzhu/copier v0.3.5
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/spf13/cast v1.3.1
|
||||
github.com/tidwall/gjson v1.14.1
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||
gopkg.in/ini.v1 v1.66.4
|
||||
)
|
||||
toolchain go1.24.4
|
||||
|
||||
replace (
|
||||
github.com/aynakeya/blivedm => D:\Repository\blivedm
|
||||
github.com/aynakeya/go-mpv => D:\Repository\go-mpv
|
||||
github.com/AynaLivePlayer/liveroom-sdk v0.1.0 => ./pkg/liveroom-sdk // submodule
|
||||
github.com/AynaLivePlayer/miaosic v0.2.3 => ./pkg/miaosic // submodule
|
||||
|
||||
github.com/saltosystems/winrt-go => github.com/go-musicfox/winrt-go v0.1.4 // winrt with media foundation
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/fyne/v2 v2.6.3
|
||||
github.com/AynaLivePlayer/liveroom-sdk v0.1.0
|
||||
github.com/AynaLivePlayer/miaosic v0.2.3
|
||||
github.com/adrg/libvlc-go/v3 v3.1.6
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1
|
||||
github.com/aynakeya/go-mpv v0.0.8
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728
|
||||
github.com/go-ole/go-ole v1.3.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/mattn/go-colorable v0.1.14
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646
|
||||
github.com/saltosystems/winrt-go v0.0.0-20241223121953-98e32661f6ff
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/tidwall/gjson v1.18.0
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
|
||||
golang.org/x/sys v0.34.0
|
||||
gopkg.in/ini.v1 v1.67.0
|
||||
)
|
||||
|
||||
require (
|
||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 // indirect
|
||||
github.com/AynaLivePlayer/blivedm-go v0.0.0-20250629154348-690af765bfbc // indirect
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/PuerkitoBio/goquery v1.10.3 // indirect
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.30 // indirect
|
||||
github.com/abadojack/whatlanggo v1.0.1 // indirect
|
||||
github.com/andybalholm/brotli v1.1.0 // indirect
|
||||
github.com/andybalholm/cascadia v1.3.3 // indirect
|
||||
github.com/aynakeya/deepcolor v1.0.3 // indirect
|
||||
github.com/aynakeya/open-bilibili-live v0.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 // indirect
|
||||
github.com/fredbi/uri v1.1.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fyne-io/gl-js v0.2.0 // indirect
|
||||
github.com/fyne-io/glfw-js v0.3.0 // indirect
|
||||
github.com/fyne-io/image v0.1.1 // indirect
|
||||
github.com/fyne-io/oksvg v0.1.0 // indirect
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 // indirect
|
||||
github.com/go-text/render v0.2.0 // indirect
|
||||
github.com/go-text/typesetting v0.2.1 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 // indirect
|
||||
github.com/hack-pad/safejs v0.1.0 // indirect
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade // indirect
|
||||
github.com/jinzhu/copier v0.4.0 // indirect
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 // indirect
|
||||
github.com/makiuchi-d/gozxing v0.1.1 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rymdport/portal v0.4.1 // indirect
|
||||
github.com/sahilm/fuzzy v0.1.1 // indirect
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d // indirect
|
||||
github.com/spf13/cast v1.9.2 // indirect
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c // indirect
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
github.com/yuin/goldmark v1.7.8 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/image v0.24.0 // indirect
|
||||
golang.org/x/net v0.42.0 // indirect
|
||||
golang.org/x/text v0.27.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
//replace (
|
||||
// github.com/aynakeya/blivedm => D:\Repository\blivedm
|
||||
// github.com/aynakeya/go-mpv => D:\Repository\go-mpv
|
||||
//)
|
||||
|
||||
315
go.sum
315
go.sum
@@ -1,132 +1,259 @@
|
||||
fyne.io/fyne/v2 v2.1.4 h1:bt1+28++kAzRzPB0GM2EuSV4cnl8rXNX4cjfd8G06Rc=
|
||||
fyne.io/fyne/v2 v2.1.4/go.mod h1:p+E/Dh+wPW8JwR2DVcsZ9iXgR9ZKde80+Y+40Is54AQ=
|
||||
fyne.io/fyne/v2 v2.6.3 h1:cvtM2KHeRuH+WhtHiA63z5wJVBkQ9+Ay0UMl9PxFHyA=
|
||||
fyne.io/fyne/v2 v2.6.3/go.mod h1:NGSurpRElVoI1G3h+ab2df3O5KLGh1CGbsMMcX0bPIs=
|
||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58 h1:eA5/u2XRd8OUkoMqEv3IBlFYSruNlXD8bRHDiqm0VNI=
|
||||
fyne.io/systray v1.11.1-0.20250603113521-ca66a66d8b58/go.mod h1:RVwqP9nYMo7h5zViCBHri2FgjXF7H2cub7MAq4NSoLs=
|
||||
github.com/AynaLivePlayer/blivedm-go v0.0.0-20250629154348-690af765bfbc h1:t1fMdqUjB2lR9uuGQ9yWJ7LJ3h1hXhI+LhbTpElPueI=
|
||||
github.com/AynaLivePlayer/blivedm-go v0.0.0-20250629154348-690af765bfbc/go.mod h1:u+JfexgX5pYrylIuC5zP3N/Ylp47K/xvl+ntpZtosuE=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9/go.mod h1:7uhhqiBaR4CpN0k9rMjOtjpcfGd6DG2m04zQxKnWQ0I=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.26 h1:Nybor5okI8C0jzAiRvGfpLHdDrPqUbjx5kXWIZDX6pw=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.26/go.mod h1:kLU/CkLxKnEJFCge0URvQ0lHt6ImoG1/2aVeNbgV2RQ=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/PuerkitoBio/goquery v1.10.3 h1:pFYcNSqHxBD06Fpj/KsbStFRsgRATgnf3LeXiUkhzPo=
|
||||
github.com/PuerkitoBio/goquery v1.10.3/go.mod h1:tMUX0zDMHXYlAQk6p35XxQMqMweEKB7iK7iLNd4RH4Y=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.30 h1:MqRItDFtX1J0JTlFtwN2RwjsYMA7/g/+cTjcOJXy19s=
|
||||
github.com/XiaoMengXinX/Music163Api-Go v0.1.30/go.mod h1:kLU/CkLxKnEJFCge0URvQ0lHt6ImoG1/2aVeNbgV2RQ=
|
||||
github.com/abadojack/whatlanggo v1.0.1 h1:19N6YogDnf71CTHm3Mp2qhYfkRdyvbgwWdd2EPxJRG4=
|
||||
github.com/abadojack/whatlanggo v1.0.1/go.mod h1:66WiQbSbJBIlOZMsvbKe5m6pzQovxCH9B/K8tQB2uoc=
|
||||
github.com/adrg/libvlc-go/v3 v3.1.6 h1:Cm22w6xNMDdzYCW8koHgAvjonYm4xbPP5TrlVTtMdl4=
|
||||
github.com/adrg/libvlc-go/v3 v3.1.6/go.mod h1:xJK0YD8cyMDejnrTFQinStE6RYCV1nlfS8KmqTpszSc=
|
||||
github.com/ajstarks/deck v0.0.0-20200831202436-30c9fc6549a9/go.mod h1:JynElWSGnm/4RlzPXRlREEwqTHAN3T56Bv2ITsFT3gY=
|
||||
github.com/ajstarks/deck/generate v0.0.0-20210309230005-c3f852c02e19/go.mod h1:T13YZdzov6OU0A1+RfKZiZN9ca6VeKdBdyDV+BY97Tk=
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b h1:slYM766cy2nI3BwyRiyQj/Ud48djTMtMebDqepE95rw=
|
||||
github.com/ajstarks/svgo v0.0.0-20211024235047-1546f124cd8b/go.mod h1:1KcenG0jGWcpt8ov532z81sp/kMMUG485J2InIOyADM=
|
||||
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
|
||||
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
|
||||
github.com/andybalholm/cascadia v1.3.3 h1:AG2YHrzJIm4BZ19iwJ/DAua6Btl3IwJX+VI4kktS1LM=
|
||||
github.com/andybalholm/cascadia v1.3.3/go.mod h1:xNd9bqTn98Ln4DwST8/nG+H0yuB8Hmgu1YHNnWw0GeA=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1 h1:NFJIr+pzwv5QLHTPyKz9UMEoHck02Q9L0FP13b/xSbQ=
|
||||
github.com/antonfisher/nested-logrus-formatter v1.3.1/go.mod h1:6WTfyWFkBc9+zyBaKIqRrg/KwMqBbodBjgbHjDz7zjA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/aynakeya/deepcolor v1.0.3 h1:FKDVxGIiD4R2XSOhS6HwCWPPsmXgG3KKqja1nNNU7HE=
|
||||
github.com/aynakeya/deepcolor v1.0.3/go.mod h1:9wdFsi0G4uAQlu58B2/eHBlGoQ8VkmSyPsK+bDZ+6dQ=
|
||||
github.com/aynakeya/go-mpv v0.0.8 h1:Gtc7N0EuPqB5JEOdah9cvamSCZIE/vqTcV5MuUAPaCg=
|
||||
github.com/aynakeya/go-mpv v0.0.8/go.mod h1:do6ImaEyt9dlQ7JRS/8ke+P9q4kGW8+Bf6j3faBQOfE=
|
||||
github.com/aynakeya/open-bilibili-live v0.0.7 h1:em/IpFeExaUmOO+jSlhzjjppiuDflLuobzdMo96dtOo=
|
||||
github.com/aynakeya/open-bilibili-live v0.0.7/go.mod h1:8aYl0767J4wgBkJ6kgOpOuj6FcFFL8GN3HRHGGjYpSk=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dhowden/itl v0.0.0-20170329215456-9fbe21093131/go.mod h1:eVWQJVQ67aMvYhpkDwaH2Goy2vo6v8JCMfGXfQ9sPtw=
|
||||
github.com/dhowden/plist v0.0.0-20141002110153-5db6e0d9931a/go.mod h1:sLjdR6uwx3L6/Py8F+QgAfeiuY87xuYGwCDqRFrvCzw=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086 h1:ORubSQoKnncsBnR4zD9CuYFJCPOCuSNEpWEZrDdBXkc=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086/go.mod h1:Z3Lomva4pyMWYezjMAU5QWRh0p1VvO4199OHlFnyKkM=
|
||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3 h1:FDqhDm7pcsLhhWl1QtD8vlzI4mm59llRvNzrFg6/LAA=
|
||||
github.com/fredbi/uri v0.0.0-20181227131451-3dcfdacbaaf3/go.mod h1:CzM2G82Q9BDUvMTGHnXf/6OExw/Dz2ivDj48nVg7Lg8=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f h1:s0O46d8fPwk9kU4k1jj76wBquMVETx7uveQD9MCIQoU=
|
||||
github.com/go-gl/gl v0.0.0-20210813123233-e4099ee2221f/go.mod h1:wjpnOv6ONl2SuJSxqCPVaPZibGFdSci9HFocT9qtVYM=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be h1:Z28GdQBfKOL8tNHjvaDn3wHDO7AzTRkmAXvHvnopp98=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20211024062804-40e447a793be/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-resty/resty/v2 v2.6.0/go.mod h1:PwvJS6hvaPkjtjNg9ph+VrSD92bi5Zq73w/BIH7cC3Q=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||
github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg=
|
||||
github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lucor/goinfo v0.0.0-20210802170112-c078a2b0f08b/go.mod h1:PRq09yoB+Q2OJReAmwzKivcYyremnibWGbK7WfftHzc=
|
||||
github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE=
|
||||
github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA=
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8 h1:OtSeLS5y0Uy01jaKK4mA/WVIYtpzVm63vLVAPzJXigg=
|
||||
github.com/dhowden/tag v0.0.0-20240417053706-3d75831295e8/go.mod h1:apkPC/CR3s48O2D7Y++n1XWEpgPNNCjXYga3PPbJe2E=
|
||||
github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g=
|
||||
github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fredbi/uri v1.1.0 h1:OqLpTXtyRg9ABReqvDGdJPqZUxs8cyBDOMXBbskCaB8=
|
||||
github.com/fredbi/uri v1.1.0/go.mod h1:aYTUoAXBOq7BLfVJ8GnKmfcuURosB1xyHDIfWeC/iW4=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fyne-io/gl-js v0.2.0 h1:+EXMLVEa18EfkXBVKhifYB6OGs3HwKO3lUElA0LlAjs=
|
||||
github.com/fyne-io/gl-js v0.2.0/go.mod h1:ZcepK8vmOYLu96JoxbCKJy2ybr+g1pTnaBDdl7c3ajI=
|
||||
github.com/fyne-io/glfw-js v0.3.0 h1:d8k2+Y7l+zy2pc7wlGRyPfTgZoqDf3AI4G+2zOWhWUk=
|
||||
github.com/fyne-io/glfw-js v0.3.0/go.mod h1:Ri6te7rdZtBgBpxLW19uBpp3Dl6K9K/bRaYdJ22G8Jk=
|
||||
github.com/fyne-io/image v0.1.1 h1:WH0z4H7qfvNUw5l4p3bC1q70sa5+YWVt6HCj7y4VNyA=
|
||||
github.com/fyne-io/image v0.1.1/go.mod h1:xrfYBh6yspc+KjkgdZU/ifUC9sPA5Iv7WYUBzQKK7JM=
|
||||
github.com/fyne-io/oksvg v0.1.0 h1:7EUKk3HV3Y2E+qypp3nWqMXD7mum0hCw2KEGhI1fnBw=
|
||||
github.com/fyne-io/oksvg v0.1.0/go.mod h1:dJ9oEkPiWhnTFNCmRgEze+YNprJF7YRbpjgpWS4kzoI=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71 h1:5BVwOaUSBTlVZowGO6VZGw2H/zl9nrd3eCZfYV+NfQA=
|
||||
github.com/go-gl/gl v0.0.0-20231021071112-07e5d0ea2e71/go.mod h1:9YTyiznxEY1fVinfM7RvRcjRHbw2xLBJ3AAGIT0I4Nw=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728 h1:RkGhqHxEVAvPM0/R+8g7XRwQnHatO0KAuVcwHo8q9W8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20250301202403-da16c1255728/go.mod h1:SyRD8YfuKk+ZXlDqYiqe1qMSqjNgtHzBTG810KUagMc=
|
||||
github.com/go-musicfox/winrt-go v0.1.4 h1:xg+7VKsIozGK8S4X4zNQ/3HNhg5yHWYaTE+Zs4jySaU=
|
||||
github.com/go-musicfox/winrt-go v0.1.4/go.mod h1:CIltaIm7qaANUIvzr0Vmz71lmQMAIbGJ7cvgzX7FMfA=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-text/render v0.2.0 h1:LBYoTmp5jYiJ4NPqDc2pz17MLmA3wHw1dZSVGcOdeAc=
|
||||
github.com/go-text/render v0.2.0/go.mod h1:CkiqfukRGKJA5vZZISkjSYrcdtgKQWRa2HIzvwNN5SU=
|
||||
github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg82V8=
|
||||
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd h1:1FjCyPC+syAzJ5/2S8fqdZK1R22vvA0J7JZKcuOIQ7Y=
|
||||
github.com/google/pprof v0.0.0-20211214055906-6f57359322fd/go.mod h1:KgnwoLYCZ8IQu3XUZ8Nc/bM9CCZFOyjUNOSygVozoDg=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/hack-pad/go-indexeddb v0.3.2 h1:DTqeJJYc1usa45Q5r52t01KhvlSN02+Oq+tQbSBI91A=
|
||||
github.com/hack-pad/go-indexeddb v0.3.2/go.mod h1:QvfTevpDVlkfomY498LhstjwbPW6QC4VC/lxYb0Kom0=
|
||||
github.com/hack-pad/safejs v0.1.0 h1:qPS6vjreAqh2amUqj4WNG1zIw7qlRQJ9K10eDKMCnE8=
|
||||
github.com/hack-pad/safejs v0.1.0/go.mod h1:HdS+bKF1NrE72VoXZeWzxFOVQVUSqZJAG0xNCnb+Tio=
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade h1:FmusiCI1wHw+XQbvL9M+1r/C3SPqKrmBaIOYwVfQoDE=
|
||||
github.com/jeandeaual/go-locale v0.0.0-20250612000132-0ef82f21eade/go.mod h1:ZDXo8KHryOWSIqnsb/CiDq7hQUYryCgdVnxbj8tDG7o=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25 h1:YLvr1eE6cdCqjOe972w/cYF+FjW34v27+9Vo5106B4M=
|
||||
github.com/jsummers/gobmp v0.0.0-20230614200233-a9de23ed2e25/go.mod h1:kLgvv7o6UM+0QSf0QjAse3wReFDsb9qbZJdfexWlrQw=
|
||||
github.com/k0kubun/pp/v3 v3.5.0 h1:iYNlYA5HJAJvkD4ibuf9c8y6SHM0QFhaBuCqm1zHp0w=
|
||||
github.com/k0kubun/pp/v3 v3.5.0/go.mod h1:5lzno5ZZeEeTV/Ky6vs3g6d1U3WarDrH8k240vMtGro=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I=
|
||||
github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU=
|
||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||
github.com/nicksnyder/go-i18n/v2 v2.5.1/go.mod h1:DrhgsSDZxoAfvVrBVLXoxZn/pN5TXqaDbq7ju94viiQ=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
|
||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA=
|
||||
github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/rymdport/portal v0.4.1 h1:2dnZhjf5uEaeDjeF/yBIeeRo6pNI2QAKm7kq1w/kbnA=
|
||||
github.com/rymdport/portal v0.4.1/go.mod h1:kFF4jslnJ8pD5uCi17brj/ODlfIidOxlgUDTO5ncnC4=
|
||||
github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA=
|
||||
github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d h1:hrujxIzL1woJ7AwssoOcM/tq5JjjG2yYOc8odClEiXA=
|
||||
github.com/saintfish/chardet v0.0.0-20230101081208-5e3ef4b5456d/go.mod h1:uugorj2VCxiV1x+LzaIdVa9b4S4qGAcH6cbhh4qVxOU=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
|
||||
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564 h1:HunZiaEKNGVdhTRQOVpMmj5MQnGnv+e8uZNu3xFLgyM=
|
||||
github.com/srwiley/oksvg v0.0.0-20200311192757-870daf9aa564/go.mod h1:afMbS0qvv1m5tfENCwnOdZGOF8RGR/FsZ7bvBxQGZG4=
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9 h1:m59mIOBO4kfcNCEzJNy71UkeF4XIx2EVmL9KLwDQdmM=
|
||||
github.com/srwiley/rasterx v0.0.0-20200120212402-85cb7272f5e9/go.mod h1:mvWM0+15UqyrFKqdRjY6LuAVJR0HOVhJlEgZ5JWtSWU=
|
||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
|
||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c h1:km8GpoQut05eY3GiYWEedbTT0qnSxrCjsVbb7yKY1KE=
|
||||
github.com/srwiley/oksvg v0.0.0-20221011165216-be6e8873101c/go.mod h1:cNQ3dwVJtS5Hmnjxy6AgTPd0Inb3pW05ftPSX7NZO7Q=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef h1:Ch6Q+AZUxDBCVqdkI8FSpFyZDtCVBc2VmejdNrm5rRQ=
|
||||
github.com/srwiley/rasterx v0.0.0-20220730225603-2ab79fcdd4ef/go.mod h1:nXTWP6+gD5+LUJ8krVhhoeHjvHTutPxMYl5SvkcnJNE=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.0.3/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
|
||||
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
||||
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
|
||||
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459 h1:x9pIfbdIjnw+Ylb2vE27Gtqb7BDmfR+nLcJwvbJh98U=
|
||||
github.com/virtuald/go-paniclog v0.0.0-20190812204905-43a7fa316459/go.mod h1:nFvuG3SWu3VWqobG3cX8nt57wXU0OOFapeCs/8axIuM=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.3.8 h1:Nw158Q8QN+CPgTmVRByhVwapp8Mm1e2blinhmx4wx5E=
|
||||
github.com/yuin/goldmark v1.3.8/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
|
||||
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8 h1:6WW6V3x1P/jokJBpRQYUJnMHRP6isStQwCozxnU7XQw=
|
||||
golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
|
||||
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
|
||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb h1:pirldcYWx7rx7kE5r+9WsOXPXK0+WH5+uZ7uPmJ44uM=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c h1:F1jZWGFhYfh0Ci55sIpILtKKK8p3i2/krTr0H1rg74I=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||
golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U=
|
||||
golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da h1:noIWHXmPHxILtqtCOPIhSt0ABwskkZKjD3bXGnZGpNY=
|
||||
golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
|
||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
|
||||
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
honnef.co/go/tools v0.1.3/go.mod h1:NgwopIslSNH47DimFoV78dnkksY2EFtX0ajyb3K/las=
|
||||
|
||||
48
gui/component/button.go
Normal file
48
gui/component/button.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type AsyncButton struct {
|
||||
widget.Button
|
||||
}
|
||||
|
||||
func NewAsyncButton(label string, tapped func()) *AsyncButton {
|
||||
b := &AsyncButton{
|
||||
Button: widget.Button{
|
||||
Text: label,
|
||||
},
|
||||
}
|
||||
b.ExtendBaseWidget(b)
|
||||
b.SetOnTapped(tapped)
|
||||
return b
|
||||
}
|
||||
|
||||
func NewAsyncButtonWithIcon(label string, icon fyne.Resource, tapped func()) *AsyncButton {
|
||||
b := &AsyncButton{
|
||||
Button: widget.Button{
|
||||
Text: label,
|
||||
Icon: icon,
|
||||
},
|
||||
}
|
||||
b.ExtendBaseWidget(b)
|
||||
b.SetOnTapped(tapped)
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *AsyncButton) SetOnTapped(f func()) {
|
||||
b.Button.OnTapped = func() {
|
||||
b.Disable()
|
||||
go func() {
|
||||
f()
|
||||
//time.Sleep(3 * time.Second)
|
||||
// todo use fyne.Do after upgrade to v2.6.x
|
||||
gutil.RunInFyneThread(b.Enable)
|
||||
//b.Enable()
|
||||
//fyne.Do(b.Enable)
|
||||
}()
|
||||
}
|
||||
}
|
||||
11
gui/component/check.go
Normal file
11
gui/component/check.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package component
|
||||
|
||||
import "fyne.io/fyne/v2/widget"
|
||||
|
||||
func NewCheckOneWayBinding(name string, val *bool, checked bool) *widget.Check {
|
||||
check := widget.NewCheck(name, func(b bool) {
|
||||
*val = b
|
||||
})
|
||||
check.SetChecked(checked)
|
||||
return check
|
||||
}
|
||||
34
gui/component/entry.go
Normal file
34
gui/component/entry.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/gui/xfyne"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type Entry struct {
|
||||
widget.Entry
|
||||
OnKeyUp func(key *fyne.KeyEvent)
|
||||
OnKeyDown func(key *fyne.KeyEvent)
|
||||
}
|
||||
|
||||
func NewEntry() *Entry {
|
||||
e := &Entry{}
|
||||
e.ExtendBaseWidget(e)
|
||||
xfyne.EntryDisableUndoRedo(&e.Entry)
|
||||
return e
|
||||
}
|
||||
|
||||
func (m *Entry) KeyUp(key *fyne.KeyEvent) {
|
||||
m.Entry.KeyUp(key)
|
||||
if m.OnKeyUp != nil {
|
||||
m.OnKeyUp(key)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Entry) KeyDown(key *fyne.KeyEvent) {
|
||||
m.Entry.KeyDown(key)
|
||||
if m.OnKeyDown != nil {
|
||||
m.OnKeyDown(key)
|
||||
}
|
||||
}
|
||||
38
gui/component/slider.go
Normal file
38
gui/component/slider.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type SliderPlus struct {
|
||||
widget.Slider
|
||||
OnDragEnd func(value float64)
|
||||
Dragging bool // during dragging
|
||||
}
|
||||
|
||||
func NewSliderPlus(min, max float64) *SliderPlus {
|
||||
slider := &SliderPlus{
|
||||
Slider: widget.Slider{
|
||||
Value: 0,
|
||||
Min: min,
|
||||
Max: max,
|
||||
Step: 1,
|
||||
Orientation: widget.Horizontal,
|
||||
},
|
||||
}
|
||||
slider.ExtendBaseWidget(slider)
|
||||
return slider
|
||||
}
|
||||
|
||||
func (s *SliderPlus) DragEnd() {
|
||||
if s.OnDragEnd != nil {
|
||||
s.OnDragEnd(s.Value)
|
||||
}
|
||||
s.Dragging = false
|
||||
}
|
||||
|
||||
func (s *SliderPlus) Dragged(e *fyne.DragEvent) {
|
||||
s.Dragging = true
|
||||
s.Slider.Dragged(e)
|
||||
}
|
||||
166
gui/component/split.go
Normal file
166
gui/component/split.go
Normal file
@@ -0,0 +1,166 @@
|
||||
package component
|
||||
|
||||
import (
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"fyne.io/fyne/v2/theme"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type FixedSplit struct {
|
||||
widget.BaseWidget
|
||||
Offset float64
|
||||
Horizontal bool
|
||||
SeparatorThickness float32
|
||||
Leading fyne.CanvasObject
|
||||
Trailing fyne.CanvasObject
|
||||
}
|
||||
|
||||
func NewFixedHSplitContainer(leading, trailing fyne.CanvasObject, offset float64) *FixedSplit {
|
||||
return NewFixedSplitContainer(leading, trailing, true, offset)
|
||||
|
||||
}
|
||||
|
||||
func NewFixedVSplitContainer(top, bottom fyne.CanvasObject, offset float64) *FixedSplit {
|
||||
return NewFixedSplitContainer(top, bottom, false, offset)
|
||||
}
|
||||
|
||||
func NewFixedSplitContainer(leading, trailing fyne.CanvasObject, horizontal bool, offset float64) *FixedSplit {
|
||||
s := &FixedSplit{
|
||||
Offset: offset, // Sensible default, can be overridden with SetOffset
|
||||
SeparatorThickness: theme.SeparatorThicknessSize(),
|
||||
Horizontal: horizontal,
|
||||
Leading: leading,
|
||||
Trailing: trailing,
|
||||
}
|
||||
s.BaseWidget.ExtendBaseWidget(s)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *FixedSplit) CreateRenderer() fyne.WidgetRenderer {
|
||||
s.BaseWidget.ExtendBaseWidget(s)
|
||||
d := widget.NewSeparator()
|
||||
return &fixedSplitContainerRenderer{
|
||||
split: s,
|
||||
divider: d,
|
||||
objects: []fyne.CanvasObject{s.Leading, d, s.Trailing},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FixedSplit) SetOffset(offset float64) {
|
||||
if s.Offset == offset {
|
||||
return
|
||||
}
|
||||
s.Offset = offset
|
||||
s.Refresh()
|
||||
}
|
||||
|
||||
func (s *FixedSplit) SetSepThickness(thickness float32) {
|
||||
if s.SeparatorThickness == thickness {
|
||||
return
|
||||
}
|
||||
s.SeparatorThickness = thickness
|
||||
s.Refresh()
|
||||
}
|
||||
|
||||
type fixedSplitContainerRenderer struct {
|
||||
split *FixedSplit
|
||||
divider *widget.Separator
|
||||
objects []fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) Destroy() {
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) Layout(size fyne.Size) {
|
||||
var dividerPos, leadingPos, trailingPos fyne.Position
|
||||
var dividerSize, leadingSize, trailingSize fyne.Size
|
||||
|
||||
if r.split.Horizontal {
|
||||
lw, tw := r.computeSplitLengths(size.Width, r.split.Leading.MinSize().Width, r.split.Trailing.MinSize().Width)
|
||||
leadingPos.X = 0
|
||||
leadingSize.Width = lw
|
||||
leadingSize.Height = size.Height
|
||||
dividerPos.X = lw
|
||||
dividerSize.Width = r.split.SeparatorThickness
|
||||
dividerSize.Height = size.Height
|
||||
trailingPos.X = lw + dividerSize.Width
|
||||
trailingSize.Width = tw
|
||||
trailingSize.Height = size.Height
|
||||
} else {
|
||||
lh, th := r.computeSplitLengths(size.Height, r.split.Leading.MinSize().Height, r.split.Trailing.MinSize().Height)
|
||||
leadingPos.Y = 0
|
||||
leadingSize.Width = size.Width
|
||||
leadingSize.Height = lh
|
||||
dividerPos.Y = lh
|
||||
dividerSize.Width = size.Width
|
||||
dividerSize.Height = r.split.SeparatorThickness
|
||||
trailingPos.Y = lh + dividerSize.Height
|
||||
trailingSize.Width = size.Width
|
||||
trailingSize.Height = th
|
||||
}
|
||||
|
||||
r.divider.Move(dividerPos)
|
||||
r.divider.Resize(dividerSize)
|
||||
r.split.Leading.Move(leadingPos)
|
||||
r.split.Leading.Resize(leadingSize)
|
||||
r.split.Trailing.Move(trailingPos)
|
||||
r.split.Trailing.Resize(trailingSize)
|
||||
canvas.Refresh(r.divider)
|
||||
canvas.Refresh(r.split.Leading)
|
||||
canvas.Refresh(r.split.Trailing)
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) 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 *fixedSplitContainerRenderer) Objects() []fyne.CanvasObject {
|
||||
return r.objects
|
||||
}
|
||||
|
||||
func (r *fixedSplitContainerRenderer) 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 *fixedSplitContainerRenderer) computeSplitLengths(total, lMin, tMin float32) (float32, float32) {
|
||||
available := float64(total - r.split.SeparatorThickness)
|
||||
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)
|
||||
}
|
||||
@@ -1,12 +1,16 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/controller"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/component"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/data/binding"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
@@ -26,36 +30,108 @@ func (b *bascicConfig) CreatePanel() fyne.CanvasObject {
|
||||
if b.panel != nil {
|
||||
return b.panel
|
||||
}
|
||||
|
||||
playerRandomCheck := widget.NewCheck(i18n.T("gui.config.basic.random_playlist.user"),
|
||||
func(b bool) {
|
||||
mode := model.PlaylistModeNormal
|
||||
if b {
|
||||
mode = model.PlaylistModeRandom
|
||||
}
|
||||
logger.Infof("Set player playlist mode to %d", mode)
|
||||
_ = global.EventBus.Publish(events.PlaylistModeChangeCmd(model.PlaylistIDPlayer),
|
||||
events.PlaylistModeChangeCmdEvent{
|
||||
Mode: mode,
|
||||
})
|
||||
})
|
||||
global.EventBus.Subscribe("", events.PlaylistModeChangeUpdate(model.PlaylistIDPlayer),
|
||||
"gui.config.basic.random_playlist.player",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
data := event.Data.(events.PlaylistModeChangeUpdateEvent)
|
||||
playerRandomCheck.SetChecked(data.Mode == model.PlaylistModeRandom)
|
||||
}))
|
||||
|
||||
systemRandomCheck := widget.NewCheck(i18n.T("gui.config.basic.random_playlist.system"),
|
||||
func(b bool) {
|
||||
mode := model.PlaylistModeNormal
|
||||
if b {
|
||||
mode = model.PlaylistModeRandom
|
||||
}
|
||||
_ = global.EventBus.Publish(events.PlaylistModeChangeCmd(model.PlaylistIDSystem),
|
||||
events.PlaylistModeChangeCmdEvent{
|
||||
Mode: mode,
|
||||
})
|
||||
})
|
||||
|
||||
global.EventBus.Subscribe("", events.PlaylistModeChangeUpdate(model.PlaylistIDSystem),
|
||||
"gui.config.basic.random_playlist.system",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
data := event.Data.(events.PlaylistModeChangeUpdateEvent)
|
||||
systemRandomCheck.SetChecked(data.Mode == model.PlaylistModeRandom)
|
||||
}))
|
||||
|
||||
randomPlaylist := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.random_playlist")),
|
||||
widget.NewCheckWithData(
|
||||
i18n.T("gui.config.basic.random_playlist.user"),
|
||||
binding.BindBool(&controller.UserPlaylist.Config.RandomNext)),
|
||||
widget.NewCheckWithData(
|
||||
i18n.T("gui.config.basic.random_playlist.system"),
|
||||
binding.BindBool(&controller.SystemPlaylist.Config.RandomNext)),
|
||||
playerRandomCheck,
|
||||
systemRandomCheck,
|
||||
)
|
||||
devices := controller.GetAudioDevices()
|
||||
deviceDesc := make([]string, len(devices))
|
||||
deviceDesc2Name := make(map[string]string)
|
||||
for i, device := range devices {
|
||||
deviceDesc[i] = device.Description
|
||||
deviceDesc2Name[device.Description] = device.Name
|
||||
}
|
||||
deviceSel := widget.NewSelect(deviceDesc, func(s string) {
|
||||
controller.SetAudioDevice(deviceDesc2Name[s])
|
||||
deviceSel := widget.NewSelect(make([]string, 0), func(s string) {
|
||||
name, ok := deviceDesc2Name[s]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
_ = global.EventBus.Publish(events.PlayerSetAudioDeviceCmd, events.PlayerSetAudioDeviceCmdEvent{
|
||||
Device: name,
|
||||
})
|
||||
})
|
||||
deviceSel.Selected = config.Player.AudioDevice
|
||||
global.EventBus.Subscribe("",
|
||||
events.PlayerAudioDeviceUpdate,
|
||||
"gui.config.basic.audio_device.update",
|
||||
gutil.ThreadSafeHandler(func(event *eventbus.Event) {
|
||||
data := event.Data.(events.PlayerAudioDeviceUpdateEvent)
|
||||
devices := make([]string, len(data.Devices))
|
||||
deviceDesc2Name = make(map[string]string)
|
||||
currentDevice := ""
|
||||
for i, device := range data.Devices {
|
||||
devices[i] = device.Description
|
||||
deviceDesc2Name[device.Description] = device.Name
|
||||
if device.Name == data.Current {
|
||||
currentDevice = device.Description
|
||||
}
|
||||
}
|
||||
logger.Infof("update audio device. set current to %s (%s)", data.Current, deviceDesc2Name[data.Current])
|
||||
deviceSel.Options = devices
|
||||
deviceSel.Selected = currentDevice
|
||||
deviceSel.Refresh()
|
||||
}))
|
||||
|
||||
outputDevice := container.NewBorder(nil, nil,
|
||||
widget.NewLabel(i18n.T("gui.config.basic.audio_device")), nil,
|
||||
deviceSel)
|
||||
skipPlaylist := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.skip_playlist")),
|
||||
widget.NewCheckWithData(
|
||||
i18n.T("gui.config.basic.skip_playlist.prompt"),
|
||||
binding.BindBool(&config.Player.SkipPlaylist),
|
||||
),
|
||||
skipWhenErr := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.skip_when_error")),
|
||||
component.NewCheckOneWayBinding(
|
||||
i18n.T("gui.config.basic.skip_when_error.prompt"),
|
||||
&config.General.PlayNextOnFail,
|
||||
config.General.PlayNextOnFail),
|
||||
)
|
||||
b.panel = container.NewVBox(randomPlaylist, outputDevice, skipPlaylist)
|
||||
checkUpdateBox := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.auto_check_update")),
|
||||
component.NewCheckOneWayBinding(
|
||||
i18n.T("gui.config.basic.auto_check_update.prompt"),
|
||||
&config.General.AutoCheckUpdate,
|
||||
config.General.AutoCheckUpdate),
|
||||
)
|
||||
checkUpdateBtn := widget.NewButton(i18n.T("gui.config.basic.check_update"), func() {
|
||||
_ = global.EventBus.Publish(events.CheckUpdateCmd, events.CheckUpdateCmdEvent{})
|
||||
})
|
||||
useSysPlaylistBtn := container.NewHBox(
|
||||
widget.NewLabel(i18n.T("gui.config.basic.use_system_playlist")),
|
||||
component.NewCheckOneWayBinding(
|
||||
i18n.T("gui.config.basic.use_system_playlist.prompt"),
|
||||
&config.General.UseSystemPlaylist,
|
||||
config.General.UseSystemPlaylist),
|
||||
)
|
||||
b.panel = container.NewVBox(randomPlaylist, useSysPlaylistBtn, skipWhenErr, outputDevice, checkUpdateBox, checkUpdateBtn)
|
||||
return b.panel
|
||||
}
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/gui/component"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
)
|
||||
|
||||
type TestConfig struct {
|
||||
var ConfigList = []ConfigLayout{
|
||||
&bascicConfig{},
|
||||
}
|
||||
|
||||
func (t *TestConfig) Title() string {
|
||||
return "Test Title"
|
||||
type ConfigLayout interface {
|
||||
Title() string
|
||||
Description() string
|
||||
CreatePanel() fyne.CanvasObject
|
||||
}
|
||||
|
||||
func (T *TestConfig) Description() string {
|
||||
return "Test Description"
|
||||
}
|
||||
|
||||
func (t *TestConfig) CreatePanel() fyne.CanvasObject {
|
||||
return widget.NewLabel("asdf")
|
||||
func AddConfigLayout(cfgs ...ConfigLayout) {
|
||||
ConfigList = append(ConfigList, cfgs...)
|
||||
}
|
||||
|
||||
func createConfigLayout() fyne.CanvasObject {
|
||||
@@ -32,7 +32,7 @@ func createConfigLayout() fyne.CanvasObject {
|
||||
return len(ConfigList)
|
||||
},
|
||||
func() fyne.CanvasObject {
|
||||
return widget.NewLabel("AAAAAAAAAAAAAAAA")
|
||||
return widget.NewLabel("")
|
||||
},
|
||||
func(id widget.ListItemID, object fyne.CanvasObject) {
|
||||
object.(*widget.Label).SetText(ConfigList[id].Title())
|
||||
@@ -44,16 +44,11 @@ func createConfigLayout() fyne.CanvasObject {
|
||||
seg.Style.Alignment = fyne.TextAlignCenter
|
||||
}
|
||||
}
|
||||
a := container.NewVScroll(ConfigList[id].CreatePanel())
|
||||
content.Objects = []fyne.CanvasObject{
|
||||
container.NewBorder(container.NewVBox(desc, widget.NewSeparator()), nil, nil, nil,
|
||||
a),
|
||||
container.NewVScroll(container.NewBorder(container.NewVBox(desc, widget.NewSeparator()), nil, nil, nil, ConfigList[id].CreatePanel())),
|
||||
}
|
||||
|
||||
content.Refresh()
|
||||
}
|
||||
return container.NewBorder(
|
||||
nil, nil,
|
||||
container.NewHBox(entryList, widget.NewSeparator()), nil,
|
||||
content)
|
||||
|
||||
return component.NewFixedSplitContainer(entryList, content, true, 0.23)
|
||||
}
|
||||
|
||||
99
gui/gui.go
99
gui/gui.go
@@ -1,69 +1,104 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/config"
|
||||
"AynaLivePlayer/i18n"
|
||||
"AynaLivePlayer/logger"
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/gui/gutil"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"AynaLivePlayer/pkg/i18n"
|
||||
"AynaLivePlayer/resource"
|
||||
"fmt"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/app"
|
||||
"fyne.io/fyne/v2/container"
|
||||
"github.com/sirupsen/logrus"
|
||||
"fyne.io/fyne/v2/dialog"
|
||||
"fyne.io/fyne/v2/widget"
|
||||
"os"
|
||||
|
||||
_logger "AynaLivePlayer/pkg/logger"
|
||||
)
|
||||
|
||||
const MODULE_GUI = "GUI"
|
||||
|
||||
type ConfigLayout interface {
|
||||
Title() string
|
||||
Description() string
|
||||
CreatePanel() fyne.CanvasObject
|
||||
}
|
||||
|
||||
var App fyne.App
|
||||
var MainWindow fyne.Window
|
||||
var ConfigList = []ConfigLayout{&bascicConfig{}}
|
||||
var playerWindow fyne.Window
|
||||
var playerWindowHandle uintptr
|
||||
|
||||
func l() *logrus.Entry {
|
||||
return logger.Logger.WithField("Module", MODULE_GUI)
|
||||
var logger _logger.ILogger = nil
|
||||
|
||||
func black_magic() {
|
||||
widget.RichTextStyleStrong.TextStyle.Bold = false
|
||||
}
|
||||
|
||||
func Initialize() {
|
||||
l().Info("Initializing GUI")
|
||||
os.Setenv("FYNE_FONT", config.GetAssetPath("msyh.ttc"))
|
||||
App = app.New()
|
||||
MainWindow = App.NewWindow(fmt.Sprintf("%s Ver.%s", config.ProgramName, config.Version))
|
||||
logger = global.Logger.WithPrefix("GUI")
|
||||
black_magic()
|
||||
logger.Info("Initializing GUI")
|
||||
if config.General.CustomFonts != "" {
|
||||
_ = os.Setenv("FYNE_FONT", config.GetAssetPath(config.General.CustomFonts))
|
||||
}
|
||||
App = app.NewWithID(config.ProgramName)
|
||||
//App.Settings().SetTheme(&myTheme{})
|
||||
MainWindow = App.NewWindow(getAppTitle())
|
||||
|
||||
tabs := container.NewAppTabs(
|
||||
container.NewTabItem(i18n.T("gui.tab.player"),
|
||||
newPaddedBoarder(nil, createPlayControllerV2(), nil, nil, createPlaylist()),
|
||||
container.NewBorder(nil, createPlayControllerV2(), nil, nil, createPlaylist()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.search"),
|
||||
newPaddedBoarder(createSearchBar(), nil, nil, nil, createSearchList()),
|
||||
container.NewBorder(createSearchBar(), nil, nil, nil, createSearchList()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.room"),
|
||||
newPaddedBoarder(createRoomController(), nil, nil, nil, createRoomLogger()),
|
||||
container.NewBorder(nil, nil, createRoomSelector(), nil, createRoomController()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.playlist"),
|
||||
newPaddedBoarder(nil, nil, createPlaylists(), nil, createPlaylistMedias()),
|
||||
container.NewBorder(nil, nil, createPlaylists(), nil, createPlaylistMedias()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.history"),
|
||||
newPaddedBoarder(nil, nil, nil, nil, createHistoryList()),
|
||||
container.NewBorder(nil, nil, nil, nil, createHistoryList()),
|
||||
),
|
||||
container.NewTabItem(i18n.T("gui.tab.config"),
|
||||
newPaddedBoarder(nil, nil, nil, nil, createConfigLayout()),
|
||||
createConfigLayout(),
|
||||
),
|
||||
)
|
||||
|
||||
tabs.SetTabLocation(container.TabLocationTop)
|
||||
MainWindow.SetIcon(fyne.NewStaticResource("icon", resource.ProgramIcon))
|
||||
MainWindow.SetIcon(resource.ImageIcon)
|
||||
MainWindow.SetContent(tabs)
|
||||
//MainWindow.Resize(fyne.NewSize(1280, 720))
|
||||
MainWindow.Resize(fyne.NewSize(960, 480))
|
||||
//MainWindow.SetFixedSize(true)
|
||||
}
|
||||
MainWindow.Resize(fyne.NewSize(config.General.Width, config.General.Height))
|
||||
|
||||
func AddConfigLayout(cfgs ...ConfigLayout) {
|
||||
ConfigList = append(ConfigList, cfgs...)
|
||||
// todo: fix, window were created even if not show. this block gui from closing
|
||||
// i can't create sub window before the main window shows.
|
||||
// setupPlayerWindow()
|
||||
|
||||
// register error
|
||||
global.EventBus.Subscribe("",
|
||||
events.ErrorUpdate, "gui.show_error", gutil.ThreadSafeHandler(func(e *eventbus.Event) {
|
||||
err := e.Data.(events.ErrorUpdateEvent).Error
|
||||
logger.Warnf("gui received error event: %v, %v", err, err == nil)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
dialog.ShowError(err, MainWindow)
|
||||
}))
|
||||
|
||||
checkUpdate()
|
||||
MainWindow.SetFixedSize(config.General.FixedSize)
|
||||
if config.General.ShowSystemTray {
|
||||
setupSysTray()
|
||||
} else {
|
||||
MainWindow.SetCloseIntercept(
|
||||
func() {
|
||||
// todo: save twice i don;t care
|
||||
_ = config.SaveToConfigFile(config.ConfigPath)
|
||||
MainWindow.Close()
|
||||
})
|
||||
}
|
||||
MainWindow.SetOnClosed(func() {
|
||||
logger.Infof("GUI closing")
|
||||
if playerWindow != nil {
|
||||
logger.Infof("player window closing")
|
||||
playerWindow.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
13
gui/gui_dev.go
Normal file
13
gui/gui_dev.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build !nosource
|
||||
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func getAppTitle() string {
|
||||
return fmt.Sprintf("%s Ver %s - 测试版 仅供开发人员测试使用 请勿用于其他用途", config.ProgramName, model.Version(config.Version))
|
||||
}
|
||||
13
gui/gui_stable.go
Normal file
13
gui/gui_stable.go
Normal file
@@ -0,0 +1,13 @@
|
||||
//go:build nosource
|
||||
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/model"
|
||||
"AynaLivePlayer/pkg/config"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func getAppTitle() string {
|
||||
return fmt.Sprintf("%s Ver %s - 正式版", config.ProgramName, model.Version(config.Version))
|
||||
}
|
||||
21
gui/gutil/fyne.go
Normal file
21
gui/gutil/fyne.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
"fyne.io/fyne/v2"
|
||||
)
|
||||
|
||||
// since 2.6.1, calls to fyne API from other go routine must be wrapped in fyne.Do
|
||||
func ThreadSafeHandler(fn func(e *eventbus.Event)) func(e *eventbus.Event) {
|
||||
//return fn
|
||||
return func(e *eventbus.Event) {
|
||||
fyne.Do(func() {
|
||||
fn(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func RunInFyneThread(fn func()) {
|
||||
//fn()
|
||||
fyne.Do(fn)
|
||||
}
|
||||
55
gui/gutil/resize.go
Normal file
55
gui/gutil/resize.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package gutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fyne.io/fyne/v2"
|
||||
"fyne.io/fyne/v2/canvas"
|
||||
"github.com/AynaLivePlayer/miaosic"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/nfnt/resize"
|
||||
"image"
|
||||
"image/png"
|
||||
)
|
||||
|
||||
func ResizeImage(resource fyne.Resource, width int, height int) fyne.Resource {
|
||||
data := resource.Content()
|
||||
img, _, err := image.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return resource
|
||||
}
|
||||
img = resize.Thumbnail(uint(width), uint(height), img, resize.Lanczos3)
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
err = png.Encode(buf, img)
|
||||
if err != nil {
|
||||
return resource
|
||||
}
|
||||
return fyne.NewStaticResource(resource.Name(), buf.Bytes())
|
||||
}
|
||||
|
||||
func NewImageFromPlayerPicture(picture miaosic.Picture) (*canvas.Image, error) {
|
||||
var img *canvas.Image
|
||||
if picture.Data != nil {
|
||||
img = canvas.NewImageFromReader(bytes.NewReader(picture.Data), "cover")
|
||||
// return an error when img is nil
|
||||
if img == nil {
|
||||
return nil, errors.New("fail to read image")
|
||||
}
|
||||
} else {
|
||||
get, err := resty.New().R().Get(picture.Url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
img = canvas.NewImageFromReader(bytes.NewReader(get.Body()), "cover")
|
||||
// NewImageFromURI will return an image with empty resource and file
|
||||
if img == nil {
|
||||
return nil, errors.New("fail to download image")
|
||||
}
|
||||
}
|
||||
if img.Resource == nil {
|
||||
return nil, errors.New("fail to read image")
|
||||
}
|
||||
// compress image, so it won't be too large
|
||||
img.Resource = ResizeImage(img.Resource, 128, 128)
|
||||
return img, nil
|
||||
}
|
||||
18
gui/handler.go
Normal file
18
gui/handler.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"AynaLivePlayer/core/events"
|
||||
"AynaLivePlayer/global"
|
||||
"AynaLivePlayer/pkg/eventbus"
|
||||
)
|
||||
|
||||
func registerHandlers() {
|
||||
global.EventBus.Subscribe("", events.GUISetPlayerWindowOpenCmd, "gui.player.videoplayer.handleopen", func(event *eventbus.Event) {
|
||||
data := event.Data.(events.GUISetPlayerWindowOpenCmdEvent)
|
||||
if data.SetOpen {
|
||||
playerWindow.Close()
|
||||
} else {
|
||||
showPlayerWindow()
|
||||
}
|
||||
})
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user