Fix #63: add experimental support for setting custom app font
This commit is contained in:
@@ -27,6 +27,10 @@ type AppConfig struct {
|
|||||||
LastCheckedVersion string
|
LastCheckedVersion string
|
||||||
EnableSystemTray bool
|
EnableSystemTray bool
|
||||||
CloseToSystemTray bool
|
CloseToSystemTray bool
|
||||||
|
|
||||||
|
// Experimental - may be removed in future
|
||||||
|
FontNormalTTF string
|
||||||
|
FontBoldTTF string
|
||||||
}
|
}
|
||||||
|
|
||||||
type AlbumPageConfig struct {
|
type AlbumPageConfig struct {
|
||||||
|
|||||||
@@ -27,7 +27,10 @@ func main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fyneApp := app.New()
|
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)
|
w := float32(myApp.Config.Application.WindowWidth)
|
||||||
if w <= 1 {
|
if w <= 1 {
|
||||||
w = 1000
|
w = 1000
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ func (c *Controller) ShowSettingsDialog() {
|
|||||||
devs = []player.AudioDevice{{Name: "auto", Description: "Autoselect device"}}
|
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() {
|
dlg.OnReplayGainSettingsChanged = func() {
|
||||||
c.App.PlaybackManager.SetReplayGainOptions(c.App.Config.ReplayGain)
|
c.App.PlaybackManager.SetReplayGainOptions(c.App.Config.ReplayGain)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
package dialogs
|
package dialogs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"supersonic/backend"
|
"supersonic/backend"
|
||||||
"supersonic/player"
|
"supersonic/player"
|
||||||
"supersonic/ui/layouts"
|
"supersonic/ui/layouts"
|
||||||
@@ -13,7 +16,9 @@ import (
|
|||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/container"
|
"fyne.io/fyne/v2/container"
|
||||||
"fyne.io/fyne/v2/data/binding"
|
"fyne.io/fyne/v2/data/binding"
|
||||||
|
"fyne.io/fyne/v2/dialog"
|
||||||
"fyne.io/fyne/v2/layout"
|
"fyne.io/fyne/v2/layout"
|
||||||
|
"fyne.io/fyne/v2/storage"
|
||||||
"fyne.io/fyne/v2/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
"fyne.io/fyne/v2/widget"
|
"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.
|
// 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 := &SettingsDialog{config: config, audioDevices: audioDeviceList}
|
||||||
s.ExtendBaseWidget(s)
|
s.ExtendBaseWidget(s)
|
||||||
|
|
||||||
tabs := container.NewAppTabs(
|
tabs := container.NewAppTabs(
|
||||||
s.createGeneralTab(),
|
s.createGeneralTab(),
|
||||||
s.createPlaybackTab(),
|
s.createPlaybackTab(),
|
||||||
|
s.createExperimentalTab(window),
|
||||||
)
|
)
|
||||||
s.promptText = widget.NewRichTextWithText("")
|
s.promptText = widget.NewRichTextWithText("")
|
||||||
s.content = container.NewVBox(tabs, widget.NewSeparator(),
|
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() {
|
func (s *SettingsDialog) setRestartRequired() {
|
||||||
ts := s.promptText.Segments[0].(*widget.TextSegment)
|
ts := s.promptText.Segments[0].(*widget.TextSegment)
|
||||||
if ts.Text != "" {
|
if ts.Text != "" {
|
||||||
|
|||||||
+59
-5
@@ -1,7 +1,11 @@
|
|||||||
package theme
|
package theme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"image/color"
|
"image/color"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"fyne.io/fyne/v2"
|
"fyne.io/fyne/v2"
|
||||||
"fyne.io/fyne/v2/theme"
|
"fyne.io/fyne/v2/theme"
|
||||||
@@ -9,11 +13,19 @@ import (
|
|||||||
|
|
||||||
const ColorNamePageBackground fyne.ThemeColorName = "PageBackground"
|
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)
|
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 {
|
switch name {
|
||||||
case ColorNamePageBackground:
|
case ColorNamePageBackground:
|
||||||
return color.RGBA{R: 15, G: 15, B: 15, A: 255}
|
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)
|
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)
|
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)
|
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)
|
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
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user