From 2f14a09cd83fb7545291a8e26cbf936eb6cc1ed0 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Fri, 21 Apr 2023 18:19:45 -0700 Subject: [PATCH] Fix #63: add experimental support for setting custom app font --- backend/config.go | 4 ++ main.go | 5 ++- ui/controller/controller.go | 2 +- ui/dialogs/settingsdialog.go | 75 +++++++++++++++++++++++++++++++++++- ui/theme/theme.go | 64 +++++++++++++++++++++++++++--- 5 files changed, 142 insertions(+), 8 deletions(-) diff --git a/backend/config.go b/backend/config.go index b925ef4..fe89a64 100644 --- a/backend/config.go +++ b/backend/config.go @@ -27,6 +27,10 @@ type AppConfig struct { LastCheckedVersion string EnableSystemTray bool CloseToSystemTray bool + + // Experimental - may be removed in future + FontNormalTTF string + FontBoldTTF string } type AlbumPageConfig struct { diff --git a/main.go b/main.go index 5d22edf..6dcb4b0 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,10 @@ func main() { } fyneApp := app.New() - fyneApp.Settings().SetTheme(&theme.MyTheme{}) + fyneApp.Settings().SetTheme(&theme.MyTheme{ + NormalFont: myApp.Config.Application.FontNormalTTF, + BoldFont: myApp.Config.Application.FontBoldTTF, + }) w := float32(myApp.Config.Application.WindowWidth) if w <= 1 { w = 1000 diff --git a/ui/controller/controller.go b/ui/controller/controller.go index 9d1dd3d..4caf8fa 100644 --- a/ui/controller/controller.go +++ b/ui/controller/controller.go @@ -319,7 +319,7 @@ func (c *Controller) ShowSettingsDialog() { devs = []player.AudioDevice{{Name: "auto", Description: "Autoselect device"}} } - dlg := dialogs.NewSettingsDialog(c.App.Config, devs) + dlg := dialogs.NewSettingsDialog(c.App.Config, devs, c.MainWindow) dlg.OnReplayGainSettingsChanged = func() { c.App.PlaybackManager.SetReplayGainOptions(c.App.Config.ReplayGain) } diff --git a/ui/dialogs/settingsdialog.go b/ui/dialogs/settingsdialog.go index a266557..f322bc7 100644 --- a/ui/dialogs/settingsdialog.go +++ b/ui/dialogs/settingsdialog.go @@ -1,8 +1,11 @@ package dialogs import ( + "errors" "math" + "os" "strconv" + "strings" "supersonic/backend" "supersonic/player" "supersonic/ui/layouts" @@ -13,7 +16,9 @@ import ( "fyne.io/fyne/v2" "fyne.io/fyne/v2/container" "fyne.io/fyne/v2/data/binding" + "fyne.io/fyne/v2/dialog" "fyne.io/fyne/v2/layout" + "fyne.io/fyne/v2/storage" "fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/widget" ) @@ -36,13 +41,14 @@ type SettingsDialog struct { } // TODO: having this depend on the player package for the AudioDevice type is kinda gross. Refactor. -func NewSettingsDialog(config *backend.Config, audioDeviceList []player.AudioDevice) *SettingsDialog { +func NewSettingsDialog(config *backend.Config, audioDeviceList []player.AudioDevice, window fyne.Window) *SettingsDialog { s := &SettingsDialog{config: config, audioDevices: audioDeviceList} s.ExtendBaseWidget(s) tabs := container.NewAppTabs( s.createGeneralTab(), s.createPlaybackTab(), + s.createExperimentalTab(window), ) s.promptText = widget.NewRichTextWithText("") s.content = container.NewVBox(tabs, widget.NewSeparator(), @@ -258,6 +264,73 @@ func (s *SettingsDialog) createPlaybackTab() *container.TabItem { )) } +func (s *SettingsDialog) createExperimentalTab(window fyne.Window) *container.TabItem { + warningLabel := widget.NewLabel("WARNING: these settings are experimental and may " + + "make the application buggy or increase system resource use. " + + "They may be removed in future versions.") + warningLabel.Wrapping = fyne.TextWrapWord + + normalFontEntry := widget.NewEntry() + normalFontEntry.SetPlaceHolder("path to .ttf or empty to use default") + normalFontEntry.Text = s.config.Application.FontNormalTTF + normalFontEntry.Validator = s.ttfPathValidator + normalFontEntry.OnChanged = func(path string) { + if normalFontEntry.Validate() == nil { + s.setRestartRequired() + s.config.Application.FontNormalTTF = path + } + } + normalFontBrowse := widget.NewButtonWithIcon("", theme.FolderOpenIcon(), func() { + s.doChooseTTFFile(window, normalFontEntry) + }) + + boldFontEntry := widget.NewEntry() + boldFontEntry.SetPlaceHolder("path to .ttf or empty to use default") + boldFontEntry.Text = s.config.Application.FontBoldTTF + boldFontEntry.Validator = s.ttfPathValidator + boldFontEntry.OnChanged = func(path string) { + if boldFontEntry.Validate() == nil { + s.setRestartRequired() + s.config.Application.FontBoldTTF = path + } + } + boldFontBrowse := widget.NewButtonWithIcon("", theme.FolderOpenIcon(), func() { + s.doChooseTTFFile(window, boldFontEntry) + }) + + return container.NewTabItem("Experimental", container.NewVBox( + warningLabel, + s.newSectionSeparator(), + widget.NewRichText(&widget.TextSegment{Text: "Application Font", Style: boldStyle}), + container.New(layout.NewFormLayout(), + widget.NewLabel("Normal font"), container.NewBorder(nil, nil, nil, normalFontBrowse, normalFontEntry), + widget.NewLabel("Bold font"), container.NewBorder(nil, nil, nil, boldFontBrowse, boldFontEntry), + ), + )) +} + +func (s *SettingsDialog) doChooseTTFFile(window fyne.Window, entry *widget.Entry) { + callback := func(urirc fyne.URIReadCloser, err error) { + if err == nil && urirc != nil { + entry.SetText(urirc.URI().Path()) + } + } + dlg := dialog.NewFileOpen(callback, window) + dlg.SetFilter(&storage.ExtensionFileFilter{Extensions: []string{".ttf"}}) + dlg.Show() +} + +func (s *SettingsDialog) ttfPathValidator(path string) error { + if path == "" { + return nil + } + if !strings.HasSuffix(path, ".ttf") { + return errors.New("only .ttf fonts supported") + } + _, err := os.Stat(path) + return err +} + func (s *SettingsDialog) setRestartRequired() { ts := s.promptText.Segments[0].(*widget.TextSegment) if ts.Text != "" { diff --git a/ui/theme/theme.go b/ui/theme/theme.go index c59e02b..e420137 100644 --- a/ui/theme/theme.go +++ b/ui/theme/theme.go @@ -1,7 +1,11 @@ package theme import ( + "errors" "image/color" + "io/ioutil" + "log" + "strings" "fyne.io/fyne/v2" "fyne.io/fyne/v2/theme" @@ -9,11 +13,19 @@ import ( const ColorNamePageBackground fyne.ThemeColorName = "PageBackground" -type MyTheme struct{} +var ( + normalFont fyne.Resource + boldFont fyne.Resource +) + +type MyTheme struct { + NormalFont string + BoldFont string +} var _ fyne.Theme = (*MyTheme)(nil) -func (m MyTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color { +func (m *MyTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) color.Color { switch name { case ColorNamePageBackground: return color.RGBA{R: 15, G: 15, B: 15, A: 255} @@ -29,14 +41,56 @@ func (m MyTheme) Color(name fyne.ThemeColorName, variant fyne.ThemeVariant) colo return theme.DarkTheme().Color(name, variant) } -func (m MyTheme) Icon(name fyne.ThemeIconName) fyne.Resource { +func (m *MyTheme) Icon(name fyne.ThemeIconName) fyne.Resource { return theme.DefaultTheme().Icon(name) } -func (m MyTheme) Font(style fyne.TextStyle) fyne.Resource { +func (m *MyTheme) Font(style fyne.TextStyle) fyne.Resource { + switch style { + case fyne.TextStyle{}: + if m.NormalFont != "" && normalFont == nil { + if content, err := readTTFFile(m.NormalFont); err != nil { + m.NormalFont = "" + m.BoldFont = "" + } else { + normalFont = fyne.NewStaticResource("normalFont", content) + } + } + if normalFont != nil { + return normalFont + } + case fyne.TextStyle{Bold: true}: + if m.BoldFont != "" && boldFont == nil { + if content, err := ioutil.ReadFile(m.BoldFont); err != nil { + m.BoldFont = "" + } else { + normalFont = fyne.NewStaticResource("boldFont", content) + } + } + if boldFont != nil { + return boldFont + } + if normalFont != nil { + return normalFont + } + } + return theme.DefaultTheme().Font(style) } -func (m MyTheme) Size(name fyne.ThemeSizeName) float32 { +func (m *MyTheme) Size(name fyne.ThemeSizeName) float32 { return theme.DefaultTheme().Size(name) } + +func readTTFFile(filepath string) ([]byte, error) { + if !strings.HasSuffix(filepath, ".ttf") { + err := errors.New("only .ttf fonts are supported") + log.Printf("error loading custom font %q: %s", filepath, err.Error()) + return nil, err + } + content, err := ioutil.ReadFile(filepath) + if err != nil { + log.Printf("error loading custom font %q: %s", filepath, err.Error()) + } + return content, err +}