From 420a295b9f76f62cae52a567d3d6320856f90ec3 Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Sat, 16 Nov 2024 00:45:18 +0100 Subject: [PATCH 1/9] initial volume slider --- internal/logic/sync-5.1.go | 8 ++++++-- internal/logic/sync-7.1.4.go | 8 ++++++-- internal/logic/sync-stereo.go | 8 ++++++-- internal/logic/sync.go | 12 ++++++------ internal/ui/windows.go | 11 ++++++++++- 5 files changed, 34 insertions(+), 13 deletions(-) diff --git a/internal/logic/sync-5.1.go b/internal/logic/sync-5.1.go index d678421..450379f 100644 --- a/internal/logic/sync-5.1.go +++ b/internal/logic/sync-5.1.go @@ -1,9 +1,13 @@ package logic -func get5_1Arguments() []string { +import ( + "fmt" +) + +func get5_1Arguments(volume float64) []string { return []string{ // best audio format so far on ios - "-filter_complex", "[1:a]apad[a2];[0:a][a2]amerge=inputs=2[out]", + "-filter_complex", "[1:a]apad[a2];[0:a]volume=" + fmt.Sprintf("%f", volume / 100) + "[a1];[a1][a2]amerge=inputs=2[out]", "-c:a", "aac", "-b:a", "654k", // mp4 output // "-filter_complex", "[1:a]apad[a2];[0:a][a2]amerge=inputs=2,pan=5.1|c0=c0+c6|c1=c1+c7|c2=c2|c3=c3|c4=c4|c5=c5[out]", diff --git a/internal/logic/sync-7.1.4.go b/internal/logic/sync-7.1.4.go index 2906f3c..073c2a8 100644 --- a/internal/logic/sync-7.1.4.go +++ b/internal/logic/sync-7.1.4.go @@ -1,8 +1,12 @@ package logic -func get7_1_4Arguments() []string { +import ( + "fmt" +) + +func get7_1_4Arguments(volume float64) []string { return []string{ "-filter_complex", - "[1:a]apad[a2];[0:a][a2]amerge=inputs=2,pan=7.1.4|FL=c0+c12|FR=c1+c13|FC=c2|LFE=c3|BL=c6|BR=c7|SL=c4|SR=c5|TFL=c8|TFR=c9|TBL=c10|TBR=c11[out]", + "[1:a]apad[a2];[0:a]volume=" + fmt.Sprintf("%f", volume / 100) + "[a1];[a1][a2]amerge=inputs=2,pan=7.1.4|FL=c0+c12|FR=c1+c13|FC=c2|LFE=c3|BL=c6|BR=c7|SL=c4|SR=c5|TFL=c8|TFR=c9|TBL=c10|TBR=c11[out]", } } diff --git a/internal/logic/sync-stereo.go b/internal/logic/sync-stereo.go index 1a4e588..ad5c0fb 100644 --- a/internal/logic/sync-stereo.go +++ b/internal/logic/sync-stereo.go @@ -1,7 +1,11 @@ package logic -func getStereoArguments() []string { +import ( + "fmt" +) + +func getStereoArguments(volume float64) []string { return []string{ - "-filter_complex", "[1:a]apad[a2];[0:a][a2]amerge=inputs=2,pan=stereo|c0 Date: Sat, 14 Dec 2024 15:12:53 +0100 Subject: [PATCH 2/9] feat: add volume slider, try switch to native file dialogs --- internal/logic/sync-5.1.go | 10 +-- internal/logic/sync-7.1.2.go | 6 +- internal/logic/sync-7.1.4.go | 6 +- internal/logic/sync-stereo.go | 6 +- internal/logic/sync.go | 17 ++-- internal/ui/windows.go | 141 +++++++++++++++++++++++++--------- 6 files changed, 128 insertions(+), 58 deletions(-) diff --git a/internal/logic/sync-5.1.go b/internal/logic/sync-5.1.go index 450379f..c91f636 100644 --- a/internal/logic/sync-5.1.go +++ b/internal/logic/sync-5.1.go @@ -1,16 +1,10 @@ package logic -import ( - "fmt" -) +import "fmt" func get5_1Arguments(volume float64) []string { return []string{ - // best audio format so far on ios - "-filter_complex", "[1:a]apad[a2];[0:a]volume=" + fmt.Sprintf("%f", volume / 100) + "[a1];[a1][a2]amerge=inputs=2[out]", + "-filter_complex", fmt.Sprintf("[0:a]volume=%f,apad[a2];[1:a][a2]amerge=inputs=2[out]", volume), "-c:a", "aac", "-b:a", "654k", - // mp4 output - // "-filter_complex", "[1:a]apad[a2];[0:a][a2]amerge=inputs=2,pan=5.1|c0=c0+c6|c1=c1+c7|c2=c2|c3=c3|c4=c4|c5=c5[out]", - // "-c:a", "eac3", "-ac", "6", } } diff --git a/internal/logic/sync-7.1.2.go b/internal/logic/sync-7.1.2.go index 5b852d7..55ab759 100644 --- a/internal/logic/sync-7.1.2.go +++ b/internal/logic/sync-7.1.2.go @@ -1,8 +1,10 @@ package logic -func get7_1_2Arguments() []string { +import "fmt" + +func get7_1_2Arguments(volume float64) []string { return []string{ "-filter_complex", - "[1:a]apad[a2];[0:a][a2]amerge=inputs=2,pan=7.1.2|FL=c0+c10|FR=c1+c11|FC=c2|LFE=c3|BL=c6|BR=c7|SL=c4|SR=c5|TFL=c8|TFR=c9[out]", + fmt.Sprintf("[0:a]volume=%f,apad[a2];[1:a][a2]amerge=inputs=2,pan=7.1.2|FL=c0+c10|FR=c1+c11|FC=c2|LFE=c3|BL=c6|BR=c7|SL=c4|SR=c5|TFL=c8|TFR=c9[out]", volume), } } diff --git a/internal/logic/sync-7.1.4.go b/internal/logic/sync-7.1.4.go index 073c2a8..8259109 100644 --- a/internal/logic/sync-7.1.4.go +++ b/internal/logic/sync-7.1.4.go @@ -1,12 +1,10 @@ package logic -import ( - "fmt" -) +import "fmt" func get7_1_4Arguments(volume float64) []string { return []string{ "-filter_complex", - "[1:a]apad[a2];[0:a]volume=" + fmt.Sprintf("%f", volume / 100) + "[a1];[a1][a2]amerge=inputs=2,pan=7.1.4|FL=c0+c12|FR=c1+c13|FC=c2|LFE=c3|BL=c6|BR=c7|SL=c4|SR=c5|TFL=c8|TFR=c9|TBL=c10|TBR=c11[out]", + fmt.Sprintf("[0:a]volume=%f,apad[a2];[1:a][a2]amerge=inputs=2,pan=7.1.4|FL=c0+c12|FR=c1+c13|FC=c2|LFE=c3|BL=c6|BR=c7|SL=c4|SR=c5|TFL=c8|TFR=c9|TBL=c10|TBR=c11[out]", volume), } } diff --git a/internal/logic/sync-stereo.go b/internal/logic/sync-stereo.go index ad5c0fb..ceef83e 100644 --- a/internal/logic/sync-stereo.go +++ b/internal/logic/sync-stereo.go @@ -1,11 +1,9 @@ package logic -import ( - "fmt" -) +import "fmt" func getStereoArguments(volume float64) []string { return []string{ - "-filter_complex", "[1:a]apad[a2];[0:a]volume=" + fmt.Sprintf("%f", volume / 100) + "[a1];[a1][a2]amerge=inputs=2,pan=stereo|c0 Date: Sat, 14 Dec 2024 15:43:29 +0100 Subject: [PATCH 3/9] fix: change the dialog on windows --- .chatgpt_config.yaml | 2 ++ README.md | 3 --- internal/ui/windows.go | 12 ++++++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.chatgpt_config.yaml b/.chatgpt_config.yaml index a4d370c..01e37a9 100644 --- a/.chatgpt_config.yaml +++ b/.chatgpt_config.yaml @@ -2,3 +2,5 @@ project_name: "soundscape-sync" default_prompt_blocks: - "basic-prompt" - "go-development" +initial_files: + - "README.md" diff --git a/README.md b/README.md index aa34a4a..211e123 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,6 @@ It is important that both folders have the same amount of Files and that the fil - [x] Make it possible to Sync with spatial Audio Soundscapes - [ ] Make it usable with Mac - [ ] Generate a package for Ubuntu -- [ ] Better handling of file mapping -- [x] add cover art for mp3 -- [x] add cover art for m4b - [x] load metadata from audiobook ## Support Me diff --git a/internal/ui/windows.go b/internal/ui/windows.go index b143576..89b0cb4 100644 --- a/internal/ui/windows.go +++ b/internal/ui/windows.go @@ -49,14 +49,18 @@ func tryLinuxNativeFolderDialog() string { // tryNativeFolderDialog attempts to open a native OS folder selection dialog. // If successful, it returns the selected path. If not available or fails, it returns an empty string. +// On Windows, use an OpenFileDialog trick to simulate a normal file browser dialog rather than the older folder dialog. func tryNativeFolderDialog() string { switch runtime.GOOS { case "windows": - // Use PowerShell to prompt for a folder + // Use PowerShell to prompt with an OpenFileDialog that can simulate folder selection cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null; "+ - "$f = New-Object System.Windows.Forms.FolderBrowserDialog; "+ - "if($f.ShowDialog() -eq 'OK'){ $f.SelectedPath }") + "$ofd = New-Object System.Windows.Forms.OpenFileDialog; "+ + "$ofd.InitialDirectory = [Environment]::GetFolderPath('MyDocuments'); "+ + "$ofd.ValidateNames = $true; $ofd.CheckFileExists = $false; $ofd.CheckPathExists = $true; "+ + "$ofd.FileName = 'Folder Selection.'; "+ + "if ($ofd.ShowDialog() -eq 'OK') { Split-Path $ofd.FileName }") out, err := cmd.Output() if err == nil { folderPath := strings.TrimSpace(string(out)) @@ -211,4 +215,4 @@ func updateStartButton(label1, label2, folderOutputLabel *widget.Label, button * } else { button.Disable() } -} +} \ No newline at end of file From 14936ceea0d8ec349e2e85e6b9dbdf58bad62fd2 Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Sat, 14 Dec 2024 16:02:15 +0100 Subject: [PATCH 4/9] fix: hide the cmd window for folder selection on windows --- internal/ui/sysproc_default.go | 10 ++++++++++ internal/ui/sysproc_windows.go | 10 ++++++++++ internal/ui/windows.go | 8 ++++++-- 3 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 internal/ui/sysproc_default.go create mode 100644 internal/ui/sysproc_windows.go diff --git a/internal/ui/sysproc_default.go b/internal/ui/sysproc_default.go new file mode 100644 index 0000000..ddc9b49 --- /dev/null +++ b/internal/ui/sysproc_default.go @@ -0,0 +1,10 @@ +//go:build !windows +// +build !windows + +package ui + +import "syscall" + +func getSysProcAttr() *syscall.SysProcAttr { + return &syscall.SysProcAttr{} +} \ No newline at end of file diff --git a/internal/ui/sysproc_windows.go b/internal/ui/sysproc_windows.go new file mode 100644 index 0000000..2633af0 --- /dev/null +++ b/internal/ui/sysproc_windows.go @@ -0,0 +1,10 @@ +//go:build windows +// +build windows + +package ui + +import "syscall" + +func getSysProcAttr() *syscall.SysProcAttr { + return &syscall.SysProcAttr{HideWindow: true} +} \ No newline at end of file diff --git a/internal/ui/windows.go b/internal/ui/windows.go index 89b0cb4..3fd2ff6 100644 --- a/internal/ui/windows.go +++ b/internal/ui/windows.go @@ -49,11 +49,10 @@ func tryLinuxNativeFolderDialog() string { // tryNativeFolderDialog attempts to open a native OS folder selection dialog. // If successful, it returns the selected path. If not available or fails, it returns an empty string. -// On Windows, use an OpenFileDialog trick to simulate a normal file browser dialog rather than the older folder dialog. +// On Windows, use an OpenFileDialog trick to simulate a folder selection with no visible cmd window. func tryNativeFolderDialog() string { switch runtime.GOOS { case "windows": - // Use PowerShell to prompt with an OpenFileDialog that can simulate folder selection cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null; "+ "$ofd = New-Object System.Windows.Forms.OpenFileDialog; "+ @@ -61,6 +60,11 @@ func tryNativeFolderDialog() string { "$ofd.ValidateNames = $true; $ofd.CheckFileExists = $false; $ofd.CheckPathExists = $true; "+ "$ofd.FileName = 'Folder Selection.'; "+ "if ($ofd.ShowDialog() -eq 'OK') { Split-Path $ofd.FileName }") + + if runtime.GOOS == "windows" { + cmd.SysProcAttr = getSysProcAttr() + } + out, err := cmd.Output() if err == nil { folderPath := strings.TrimSpace(string(out)) From 099cd0cc8323cdb27a1f46f596cc27592bff0654 Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Sat, 14 Dec 2024 16:10:43 +0100 Subject: [PATCH 5/9] try to hide cmd window on windows --- internal/ui/sysproc_windows.go | 4 +++- internal/ui/windows.go | 31 ++++++------------------------- 2 files changed, 9 insertions(+), 26 deletions(-) diff --git a/internal/ui/sysproc_windows.go b/internal/ui/sysproc_windows.go index 2633af0..a3cc24f 100644 --- a/internal/ui/sysproc_windows.go +++ b/internal/ui/sysproc_windows.go @@ -6,5 +6,7 @@ package ui import "syscall" func getSysProcAttr() *syscall.SysProcAttr { - return &syscall.SysProcAttr{HideWindow: true} + return &syscall.SysProcAttr{ + CreationFlags: syscall.CREATE_NO_WINDOW, + } } \ No newline at end of file diff --git a/internal/ui/windows.go b/internal/ui/windows.go index 3fd2ff6..a7ef7fc 100644 --- a/internal/ui/windows.go +++ b/internal/ui/windows.go @@ -23,7 +23,6 @@ var bmcPng []byte // It first tries zenity (common on GNOME), then kdialog (common on KDE). // If neither works, it returns an empty string. func tryLinuxNativeFolderDialog() string { - // Try zenity first cmd := exec.Command("zenity", "--file-selection", "--directory") out, err := cmd.Output() if err == nil { @@ -33,7 +32,6 @@ func tryLinuxNativeFolderDialog() string { } } - // If zenity fails, try kdialog cmd = exec.Command("kdialog", "--getexistingdirectory", "$HOME") out, err = cmd.Output() if err == nil { @@ -43,17 +41,14 @@ func tryLinuxNativeFolderDialog() string { } } - // If both fail, return empty string return "" } // tryNativeFolderDialog attempts to open a native OS folder selection dialog. -// If successful, it returns the selected path. If not available or fails, it returns an empty string. -// On Windows, use an OpenFileDialog trick to simulate a folder selection with no visible cmd window. func tryNativeFolderDialog() string { switch runtime.GOOS { case "windows": - cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-Command", + cmd := exec.Command("powershell", "-NoProfile", "-NonInteractive", "-WindowStyle", "Hidden", "-Command", "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms') | Out-Null; "+ "$ofd = New-Object System.Windows.Forms.OpenFileDialog; "+ "$ofd.InitialDirectory = [Environment]::GetFolderPath('MyDocuments'); "+ @@ -75,7 +70,6 @@ func tryNativeFolderDialog() string { return "" case "darwin": - // On macOS, use AppleScript to choose a folder cmd := exec.Command("osascript", "-e", `tell application "System Events" to activate`, "-e", `POSIX path of (choose folder)`) out, err := cmd.Output() if err == nil { @@ -96,14 +90,12 @@ func tryNativeFolderDialog() string { // showFolderSelection attempts to show a native file dialog first. If it fails, fallback to Fyne's dialog. func showFolderSelection(win fyne.Window, callback func(string)) { - // Try native first nativePath := tryNativeFolderDialog() if nativePath != "" { callback(nativePath) return } - // Fallback to Fyne dialog if native is not available dialog.ShowFolderOpen(func(uri fyne.ListableURI, err error) { if err != nil { dialog.ShowError(err, win) @@ -118,25 +110,21 @@ func showFolderSelection(win fyne.Window, callback func(string)) { func CreateMainContent(app fyne.App, window fyne.Window) fyne.CanvasObject { var folder1, folder2, folderOutput string - // Create folder selection buttons + folder1Button := widget.NewButton("Select the Folder with the Soundscape", nil) folder2Button := widget.NewButton("Select the Folder with the Audiobook", nil) folderOutputButton := widget.NewButton("Select the output Folder", nil) - // Create labels to display selected folders folder1Label := widget.NewLabel("No folder selected") folder2Label := widget.NewLabel("No folder selected") folderOutputLabel := widget.NewLabel("No folder selected") - // Create start button startButton := widget.NewButton("Start Sync", nil) - startButton.Disable() // Disable initially + startButton.Disable() - // Create progress bar progressBar := widget.NewProgressBar() - progressBar.Hide() // Hide initially + progressBar.Hide() - // Set up folder selection actions with fallback logic folder1Button.OnTapped = func() { showFolderSelection(window, func(path string) { folder1 = path @@ -161,14 +149,11 @@ func CreateMainContent(app fyne.App, window fyne.Window) fyne.CanvasObject { }) } - // Volume slider volumeSliderValueLabel := widget.NewLabel("Adjust Soundscape volume") - // Create the slider volumeSlider := widget.NewSlider(0, 100) volumeSlider.Step = 1 volumeSlider.Value = 100 - // Set up start button action startButton.OnTapped = func() { startButton.Disable() progressBar.Show() @@ -184,22 +169,18 @@ func CreateMainContent(app fyne.App, window fyne.Window) fyne.CanvasObject { }() } - // Create an image for the Buy Me a Coffee button bmcResource := fyne.NewStaticResource("bmc.png", bmcPng) - // Create the Buy Me a Coffee button with an image bmcButton := widget.NewButtonWithIcon("Buy me a coffee", bmcResource, func() { u, _ := url.Parse("https://www.buymeacoffee.com/razormind") _ = app.OpenURL(u) }) - // Append new text newText := "I am an individual developer who has created an app for Soundscape synchronization." - newText = newText + "\nI hope this app helps you as much as it has helped me." - newText = newText + "\nIf you find it useful, please consider buying me a coffee. Thank you!" + newText += "\nI hope this app helps you as much as it has helped me." + newText += "\nIf you find it useful, please consider buying me a coffee. Thank you!" multiLineEntry := widget.NewMultiLineEntry() multiLineEntry.SetText(newText) - // Create and return the main content return container.NewVBox( container.NewHBox(folder1Button, folder1Label), container.NewHBox(folder2Button, folder2Label), From 2190d397bb52ef761e71449b2d5c28aa417627fa Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Sat, 14 Dec 2024 16:18:14 +0100 Subject: [PATCH 6/9] another try to hide cmd window on windows --- .gitignore | 2 +- FyneApp.toml | 10 +++++----- internal/ui/sysproc_windows.go | 8 ++++++-- internal/ui/windows.go | 3 +-- 4 files changed, 13 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index f8c8ca3..2e5c3d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -./fyne-cross +fyne-cross ./soundscape-sync diff --git a/FyneApp.toml b/FyneApp.toml index 2d8cc27..e138606 100644 --- a/FyneApp.toml +++ b/FyneApp.toml @@ -1,6 +1,6 @@ [Details] -Icon = "Icon.png" -Name = "SoundscapeSync" -ID = "com.cloonar.soundscape-sync" -Version = "0.0.1" -Build = 1 + Icon = "Icon.png" + Name = "SoundscapeSync" + ID = "com.cloonar.soundscape-sync" + Version = "0.0.1" + Build = 2 diff --git a/internal/ui/sysproc_windows.go b/internal/ui/sysproc_windows.go index a3cc24f..0d02487 100644 --- a/internal/ui/sysproc_windows.go +++ b/internal/ui/sysproc_windows.go @@ -3,10 +3,14 @@ package ui -import "syscall" +import ( + "syscall" +) + +const CREATE_NO_WINDOW = 0x08000000 func getSysProcAttr() *syscall.SysProcAttr { return &syscall.SysProcAttr{ - CreationFlags: syscall.CREATE_NO_WINDOW, + CreationFlags: CREATE_NO_WINDOW, } } \ No newline at end of file diff --git a/internal/ui/windows.go b/internal/ui/windows.go index a7ef7fc..a63c8a9 100644 --- a/internal/ui/windows.go +++ b/internal/ui/windows.go @@ -20,8 +20,6 @@ import ( var bmcPng []byte // tryLinuxNativeFolderDialog attempts to open a native OS folder selection dialog on Linux. -// It first tries zenity (common on GNOME), then kdialog (common on KDE). -// If neither works, it returns an empty string. func tryLinuxNativeFolderDialog() string { cmd := exec.Command("zenity", "--file-selection", "--directory") out, err := cmd.Output() @@ -56,6 +54,7 @@ func tryNativeFolderDialog() string { "$ofd.FileName = 'Folder Selection.'; "+ "if ($ofd.ShowDialog() -eq 'OK') { Split-Path $ofd.FileName }") + // Set SysProcAttr for Windows to hide window if runtime.GOOS == "windows" { cmd.SysProcAttr = getSysProcAttr() } From 8f3a11953131bbf88def7baa4cc689497b210675 Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Sun, 15 Dec 2024 22:33:47 +0100 Subject: [PATCH 7/9] feat: update shell.nix to work with nixos 24.11 --- shell.nix | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/shell.nix b/shell.nix index 02920f6..0659d86 100644 --- a/shell.nix +++ b/shell.nix @@ -1,16 +1,4 @@ { pkgs ? import {} }: -# with import { -# crossSystem = { -# config = "x86_64-w64-mingw32"; -# }; -# }; -# let -# # pkgs = (import { crossSystem = {config = "x86_64-w64-mingw32";}; }); -# pkgs = import { -# localSystem = "x86_64-linux"; # buildPlatform -# crossSystem = "x86_64-w64-mingw32"; # Note the `config` part! -# }; -# in pkgs.mkShell { buildInputs = with pkgs; [ go @@ -24,7 +12,7 @@ pkgs.mkShell { delve pkg-config clang - pkg-config + gcc glib cairo pango @@ -39,6 +27,7 @@ pkgs.mkShell { xorg.libXi xorg.libXrandr xorg.libXinerama + xorg.libXxf86vm libxkbcommon libGL mesa @@ -53,6 +42,8 @@ pkgs.mkShell { pkgs.xorg.libXcursor pkgs.xorg.libXi pkgs.xorg.libXrandr + pkgs.xorg.libXinerama + pkgs.xorg.libXxf86vm pkgs.libxkbcommon ]}:$LD_LIBRARY_PATH export GOPATH=$HOME/go From 2ad8964e11837c4b27b0a0c1cfe34079ec0522ad Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Sun, 15 Dec 2024 22:33:57 +0100 Subject: [PATCH 8/9] feat: add label to volume slider --- internal/ui/windows.go | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/internal/ui/windows.go b/internal/ui/windows.go index a63c8a9..ded8bb2 100644 --- a/internal/ui/windows.go +++ b/internal/ui/windows.go @@ -7,6 +7,7 @@ import ( "path/filepath" "runtime" "strings" + "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" @@ -148,16 +149,31 @@ func CreateMainContent(app fyne.App, window fyne.Window) fyne.CanvasObject { }) } - volumeSliderValueLabel := widget.NewLabel("Adjust Soundscape volume") - volumeSlider := widget.NewSlider(0, 100) + volumeSliderValueLabel := widget.NewLabel("Soundscape Volume: 100%") + // Adjust slider to range from 50 to 100 to represent 50% to 100% + volumeSlider := widget.NewSlider(50, 100) volumeSlider.Step = 1 volumeSlider.Value = 100 + volumeSlider.OnChanged = func(v float64) { + volumeSliderValueLabel.SetText(fmt.Sprintf("Soundscape Volume: %.0f%%", v)) + } + + // Use a grid wrap to give the slider a minimum width + sliderContainer := container.NewGridWrap(fyne.NewSize(300, volumeSlider.MinSize().Height), volumeSlider) + + // Add a label at the end of the slider to indicate default + volumeContainer := container.NewHBox( + sliderContainer, + widget.NewLabel("(Default: 100%)"), + ) startButton.OnTapped = func() { startButton.Disable() progressBar.Show() go func() { - err := logic.CombineFiles(folder1, folder2, folderOutput, progressBar, volumeSlider.Value) + // convert slider value (50-100) to 0.5-1.0 for logic + volume := volumeSlider.Value / 100.0 + err := logic.CombineFiles(folder1, folder2, folderOutput, progressBar, volume) if err != nil { dialog.ShowError(err, window) } else { @@ -185,7 +201,7 @@ func CreateMainContent(app fyne.App, window fyne.Window) fyne.CanvasObject { container.NewHBox(folder2Button, folder2Label), container.NewHBox(folderOutputButton, folderOutputLabel), volumeSliderValueLabel, - volumeSlider, + volumeContainer, startButton, multiLineEntry, bmcButton, @@ -199,4 +215,4 @@ func updateStartButton(label1, label2, folderOutputLabel *widget.Label, button * } else { button.Disable() } -} \ No newline at end of file +} From 41420f86c25e6813e010a4fa3fb4032b37f3423d Mon Sep 17 00:00:00 2001 From: Dominik Polakovics Date: Sun, 15 Dec 2024 23:01:22 +0100 Subject: [PATCH 9/9] feat: change ui for better usability --- internal/ui/windows.go | 116 +++++++++++++++++++++++++---------------- 1 file changed, 72 insertions(+), 44 deletions(-) diff --git a/internal/ui/windows.go b/internal/ui/windows.go index ded8bb2..8e8d7d9 100644 --- a/internal/ui/windows.go +++ b/internal/ui/windows.go @@ -2,16 +2,17 @@ package ui import ( _ "embed" + "fmt" "net/url" "os/exec" "path/filepath" "runtime" "strings" - "fmt" "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/dialog" + "fyne.io/fyne/v2/layout" "fyne.io/fyne/v2/widget" "github.com/dpolakovics/soundscape-sync/internal/logic" @@ -20,7 +21,6 @@ import ( //go:embed bmc.png var bmcPng []byte -// tryLinuxNativeFolderDialog attempts to open a native OS folder selection dialog on Linux. func tryLinuxNativeFolderDialog() string { cmd := exec.Command("zenity", "--file-selection", "--directory") out, err := cmd.Output() @@ -43,7 +43,6 @@ func tryLinuxNativeFolderDialog() string { return "" } -// tryNativeFolderDialog attempts to open a native OS folder selection dialog. func tryNativeFolderDialog() string { switch runtime.GOOS { case "windows": @@ -55,7 +54,6 @@ func tryNativeFolderDialog() string { "$ofd.FileName = 'Folder Selection.'; "+ "if ($ofd.ShowDialog() -eq 'OK') { Split-Path $ofd.FileName }") - // Set SysProcAttr for Windows to hide window if runtime.GOOS == "windows" { cmd.SysProcAttr = getSysProcAttr() } @@ -88,7 +86,6 @@ func tryNativeFolderDialog() string { } } -// showFolderSelection attempts to show a native file dialog first. If it fails, fallback to Fyne's dialog. func showFolderSelection(win fyne.Window, callback func(string)) { nativePath := tryNativeFolderDialog() if nativePath != "" { @@ -111,9 +108,7 @@ func showFolderSelection(win fyne.Window, callback func(string)) { func CreateMainContent(app fyne.App, window fyne.Window) fyne.CanvasObject { var folder1, folder2, folderOutput string - folder1Button := widget.NewButton("Select the Folder with the Soundscape", nil) - folder2Button := widget.NewButton("Select the Folder with the Audiobook", nil) - folderOutputButton := widget.NewButton("Select the output Folder", nil) + heading := widget.NewLabelWithStyle("Soundscape Sync", fyne.TextAlignCenter, fyne.TextStyle{Bold: true}) folder1Label := widget.NewLabel("No folder selected") folder2Label := widget.NewLabel("No folder selected") @@ -122,58 +117,72 @@ func CreateMainContent(app fyne.App, window fyne.Window) fyne.CanvasObject { startButton := widget.NewButton("Start Sync", nil) startButton.Disable() - progressBar := widget.NewProgressBar() - progressBar.Hide() - - folder1Button.OnTapped = func() { + folder1Button := widget.NewButton("Select the Folder with the Soundscape", func() { showFolderSelection(window, func(path string) { folder1 = path folder1Label.SetText(filepath.Base(path)) updateStartButton(folder1Label, folder2Label, folderOutputLabel, startButton) }) - } + }) - folder2Button.OnTapped = func() { + folder2Button := widget.NewButton("Select the Folder with the Audiobook", func() { showFolderSelection(window, func(path string) { folder2 = path folder2Label.SetText(filepath.Base(path)) updateStartButton(folder1Label, folder2Label, folderOutputLabel, startButton) }) - } + }) - folderOutputButton.OnTapped = func() { + folderOutputButton := widget.NewButton("Select the output Folder", func() { showFolderSelection(window, func(path string) { folderOutput = path folderOutputLabel.SetText(filepath.Base(path)) updateStartButton(folder1Label, folder2Label, folderOutputLabel, startButton) }) - } + }) + + foldersCard := widget.NewCard( + "Folder Selection", + "Select input and output folders", + container.NewVBox( + container.NewHBox(folder1Button, folder1Label), + container.NewHBox(folder2Button, folder2Label), + container.NewHBox(folderOutputButton, folderOutputLabel), + ), + ) - volumeSliderValueLabel := widget.NewLabel("Soundscape Volume: 100%") - // Adjust slider to range from 50 to 100 to represent 50% to 100% + volumeSliderValueLabel := widget.NewLabel("Volume: 100%") volumeSlider := widget.NewSlider(50, 100) volumeSlider.Step = 1 volumeSlider.Value = 100 volumeSlider.OnChanged = func(v float64) { - volumeSliderValueLabel.SetText(fmt.Sprintf("Soundscape Volume: %.0f%%", v)) + volumeSliderValueLabel.SetText(fmt.Sprintf("Volume: %.0f%%", v)) } - // Use a grid wrap to give the slider a minimum width - sliderContainer := container.NewGridWrap(fyne.NewSize(300, volumeSlider.MinSize().Height), volumeSlider) + sliderContainer := container.NewGridWrap(fyne.NewSize(400, volumeSlider.MinSize().Height), volumeSlider) + + volumeControls := container.NewVBox( + volumeSliderValueLabel, + container.NewHBox( + sliderContainer, + widget.NewLabel("(Default: 100%)"), + ), + ) - // Add a label at the end of the slider to indicate default - volumeContainer := container.NewHBox( - sliderContainer, - widget.NewLabel("(Default: 100%)"), + volumeCard := widget.NewCard( + "Volume Settings", + "Adjust the soundscape volume", + volumeControls, ) + progressBar := widget.NewProgressBar() + progressBar.Hide() + startButton.OnTapped = func() { startButton.Disable() progressBar.Show() go func() { - // convert slider value (50-100) to 0.5-1.0 for logic - volume := volumeSlider.Value / 100.0 - err := logic.CombineFiles(folder1, folder2, folderOutput, progressBar, volume) + err := logic.CombineFiles(folder1, folder2, folderOutput, progressBar, volumeSlider.Value) if err != nil { dialog.ShowError(err, window) } else { @@ -184,29 +193,48 @@ func CreateMainContent(app fyne.App, window fyne.Window) fyne.CanvasObject { }() } + actionCard := widget.NewCard( + "", + "", + container.NewVBox( + startButton, + progressBar, + ), + ) + bmcResource := fyne.NewStaticResource("bmc.png", bmcPng) bmcButton := widget.NewButtonWithIcon("Buy me a coffee", bmcResource, func() { u, _ := url.Parse("https://www.buymeacoffee.com/razormind") _ = app.OpenURL(u) }) - newText := "I am an individual developer who has created an app for Soundscape synchronization." - newText += "\nI hope this app helps you as much as it has helped me." - newText += "\nIf you find it useful, please consider buying me a coffee. Thank you!" - multiLineEntry := widget.NewMultiLineEntry() - multiLineEntry.SetText(newText) + newText := "I am an individual developer who has created an app for Soundscape synchronization.\n" + + "I hope this app helps you as much as it has helped me.\n" + + "If you find it useful, please consider buying me a coffee. Thank you!" + + // Use a label for static text to ensure consistent, visible color + aboutLabel := widget.NewLabel(newText) + aboutLabel.Wrapping = fyne.TextWrapWord + + supportCard := widget.NewCard( + "About & Support", + "", + container.NewVBox( + aboutLabel, + bmcButton, + ), + ) - return container.NewVBox( - container.NewHBox(folder1Button, folder1Label), - container.NewHBox(folder2Button, folder2Label), - container.NewHBox(folderOutputButton, folderOutputLabel), - volumeSliderValueLabel, - volumeContainer, - startButton, - multiLineEntry, - bmcButton, - progressBar, + content := container.NewVBox( + heading, + foldersCard, + volumeCard, + actionCard, + supportCard, ) + + outer := container.New(layout.NewPaddedLayout(), content) + return outer } func updateStartButton(label1, label2, folderOutputLabel *widget.Label, button *widget.Button) {