Add theme management functionality to the application

Implemented a theme management system allowing users to select and persist themes (default, light, dark) in the settings menu.
This commit is contained in:
Leonid Nikitin 2025-05-23 20:18:05 +05:00
parent 712ec2f182
commit 82167f042f
Signed by: kor-elf
GPG Key ID: DAB5355A11C22541
8 changed files with 270 additions and 10 deletions

View File

@ -5,6 +5,7 @@ import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/localizer"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/menu"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/theme"
"github.com/nicksnyder/go-i18n/v2/i18n"
)
@ -20,6 +21,7 @@ type MenuHandler struct {
menuViewSetting menu.ViewSettingContract
localizerView localizer.ViewContract
localizerRepository localizer.RepositoryContract
themeService theme.ThemeContract
}
func NewMenuHandler(
@ -29,6 +31,7 @@ func NewMenuHandler(
menuViewSetting menu.ViewSettingContract,
localizerView localizer.ViewContract,
localizerRepository localizer.RepositoryContract,
themeService theme.ThemeContract,
) *MenuHandler {
return &MenuHandler{
app: app,
@ -37,6 +40,7 @@ func NewMenuHandler(
menuViewSetting: menuViewSetting,
localizerView: localizerView,
localizerRepository: localizerRepository,
themeService: themeService,
}
}
@ -154,6 +158,12 @@ func (h MenuHandler) settingsSelection() {
if err != nil {
return err
}
err = h.themeService.SetCurrentTheme(setting.ThemeInfo)
if err != nil {
return err
}
h.convertorHandler.MainConvertor()
return nil
}

View File

@ -426,6 +426,10 @@ other = "Licenses from other products used in the program"
hash = "sha1-ed3f0e507a5b4ed0649d7c768fe0d47413d839ba"
other = "Language"
[menuSettingsTheme]
hash = "sha1-553c45f1b84a92b08dc1f088c13f924cde95765e"
other = "Theme"
[or]
hash = "sha1-30bb0333ca1583110e4ced513b5d2455b86f529b"
other = "or"
@ -522,6 +526,18 @@ other = "Settings"
hash = "sha1-f5b8ed88e9609963035d2235be0a79bbec619976"
other = "Checking FFmpeg for serviceability..."
[themesNameDark]
hash = "sha1-bd16b234708a2515a9f2d0ca41fb11e7fe8a38a2"
other = "Dark"
[themesNameDefault]
hash = "sha1-469631cb165dcbbfea9e747056c25fbccb28c481"
other = "Default"
[themesNameLight]
hash = "sha1-8080010c5e7d7edf56e89a99d8a2422898417845"
other = "Light"
[titleDownloadLink]
hash = "sha1-92df86371f6c3a06ca1e4754f113142776a32d49"
other = "You can download it from here"

View File

@ -426,6 +426,10 @@ other = "Бағдарламада пайдаланылатын басқа өні
hash = "sha1-ed3f0e507a5b4ed0649d7c768fe0d47413d839ba"
other = "Тіл"
[menuSettingsTheme]
hash = "sha1-553c45f1b84a92b08dc1f088c13f924cde95765e"
other = "Тақырып"
[or]
hash = "sha1-30bb0333ca1583110e4ced513b5d2455b86f529b"
other = "немесе"
@ -522,6 +526,18 @@ other = "Параметрлер"
hash = "sha1-f5b8ed88e9609963035d2235be0a79bbec619976"
other = "FFmpeg функционалдығы тексерілуде..."
[themesNameDark]
hash = "sha1-bd16b234708a2515a9f2d0ca41fb11e7fe8a38a2"
other = "Қараңғы тақырып"
[themesNameDefault]
hash = "sha1-469631cb165dcbbfea9e747056c25fbccb28c481"
other = "Әдепкі бойынша"
[themesNameLight]
hash = "sha1-8080010c5e7d7edf56e89a99d8a2422898417845"
other = "Жеңіл тақырып"
[titleDownloadLink]
hash = "sha1-92df86371f6c3a06ca1e4754f113142776a32d49"
other = "Сіз оны осы жерден жүктей аласыз"

View File

@ -105,6 +105,7 @@ languageSelectionHead = "Выберите язык"
licenseLink = "Сведения о лицензии"
licenseLinkOther = "Лицензии от других продуктов, которые используются в программе"
menuSettingsLanguage = "Язык"
menuSettingsTheme = "Тема"
or = "или"
parameterCheckbox = "Включить параметр"
pathToFfmpeg = "Путь к FFmpeg:"
@ -129,6 +130,9 @@ selectFFPathTitle = "Укажите путь к FFmpeg и к FFprobe"
selectFormat = "Расширение файла:"
settings = "Настройки"
testFF = "Проверка FFmpeg на работоспособность..."
themesNameDark = "Тёмная"
themesNameDefault = "По умолчанию"
themesNameLight = "Светлая"
titleDownloadLink = "Скачать можно от сюда"
total = "Всего"
unzipRun = "Распаковывается..."

View File

@ -12,6 +12,7 @@ import (
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/menu"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/migration"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/setting"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/theme"
"go.etcd.io/bbolt"
"golang.org/x/text/language"
"os"
@ -109,10 +110,13 @@ func main() {
convertorView := convertor.NewView(application)
convertorHandler := handler.NewConvertorHandler(application, convertorView, errorView, convertorRepository, settingDirectoryForSaving)
themeRepository := theme.NewRepository(settingRepository)
themeService := theme.NewTheme(application, themeRepository)
localizerRepository := localizer.NewRepository(settingRepository)
menuView := menu.NewView(application)
menuSettingView := menu.NewViewSetting(application)
mainMenu := handler.NewMenuHandler(application, convertorHandler, menuView, menuSettingView, localizerView, localizerRepository)
menuSettingView := menu.NewViewSetting(application, themeService)
mainMenu := handler.NewMenuHandler(application, convertorHandler, menuView, menuSettingView, localizerView, localizerRepository, themeService)
mainHandler := handler.NewMainHandler(application, convertorHandler, mainMenu, localizerRepository)
mainHandler.Start()

View File

@ -5,6 +5,7 @@ import (
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/widget"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/theme"
"github.com/nicksnyder/go-i18n/v2/i18n"
"image/color"
)
@ -18,15 +19,18 @@ type ViewSettingContract interface {
type SettingForm struct {
Language kernel.Lang
ThemeInfo theme.ThemeInfoContract
}
type ViewSetting struct {
app kernel.AppContract
themeService theme.ThemeContract
}
func NewViewSetting(app kernel.AppContract) *ViewSetting {
func NewViewSetting(app kernel.AppContract, themeService theme.ThemeContract) *ViewSetting {
return &ViewSetting{
app: app,
themeService: themeService,
}
}
@ -37,6 +41,7 @@ func (v ViewSetting) Main(save func(*SettingForm) error, cancel func()) {
viewSettingForm := &SettingForm{
Language: v.app.GetLocalizerService().GetCurrentLanguage().Lang,
ThemeInfo: v.themeService.GetCurrentThemeInfo(),
}
languageItems := []string{}
@ -45,12 +50,25 @@ func (v ViewSetting) Main(save func(*SettingForm) error, cancel func()) {
languageItems = append(languageItems, language.Title)
langByTitle[language.Title] = language
}
selectLanguages := widget.NewSelect(languageItems, func(s string) {
selectLanguage := widget.NewSelect(languageItems, func(s string) {
if lang, ok := langByTitle[s]; ok {
viewSettingForm.Language = lang
}
})
selectLanguages.Selected = v.app.GetLocalizerService().GetCurrentLanguage().Lang.Title
selectLanguage.Selected = v.app.GetLocalizerService().GetCurrentLanguage().Lang.Title
themeItems := []string{}
themeByTitle := map[string]theme.ThemeInfoContract{}
for _, themeInfo := range v.themeService.List() {
themeItems = append(themeItems, themeInfo.GetTitle())
themeByTitle[themeInfo.GetTitle()] = themeInfo
}
selectTheme := widget.NewSelect(themeItems, func(s string) {
if themeInfo, ok := themeByTitle[s]; ok {
viewSettingForm.ThemeInfo = themeInfo
}
})
selectTheme.Selected = v.themeService.GetCurrentThemeInfo().GetTitle()
form := &widget.Form{
Items: []*widget.FormItem{
@ -58,7 +76,13 @@ func (v ViewSetting) Main(save func(*SettingForm) error, cancel func()) {
Text: v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "menuSettingsLanguage",
}),
Widget: selectLanguages,
Widget: selectLanguage,
},
{
Text: v.app.GetLocalizerService().GetMessage(&i18n.LocalizeConfig{
MessageID: "menuSettingsTheme",
}),
Widget: selectTheme,
},
{
Widget: errorMessage,

28
theme/repository.go Normal file
View File

@ -0,0 +1,28 @@
package theme
import "git.kor-elf.net/kor-elf/gui-for-ffmpeg/setting"
type RepositoryContract interface {
GetCode() string
Save(code string) (setting.Setting, error)
}
type Repository struct {
settingRepository setting.RepositoryContract
}
func NewRepository(settingRepository setting.RepositoryContract) *Repository {
return &Repository{settingRepository: settingRepository}
}
func (r Repository) GetCode() string {
name, err := r.settingRepository.GetValue("theme")
if err != nil {
return "default"
}
return name
}
func (r Repository) Save(code string) (setting.Setting, error) {
return r.settingRepository.CreateOrUpdate("theme", code)
}

158
theme/theme.go Normal file
View File

@ -0,0 +1,158 @@
package theme
import (
"fyne.io/fyne/v2"
fyneTheme "fyne.io/fyne/v2/theme"
"git.kor-elf.net/kor-elf/gui-for-ffmpeg/kernel"
"github.com/nicksnyder/go-i18n/v2/i18n"
"image/color"
)
type ThemeContract interface {
List() map[string]ThemeInfoContract
GetCurrentThemeInfo() ThemeInfoContract
SetCurrentTheme(themeInfo ThemeInfoContract) error
}
type theme struct {
app kernel.AppContract
repository RepositoryContract
list map[string]ThemeInfoContract
}
func NewTheme(app kernel.AppContract, repository RepositoryContract) ThemeContract {
theme := &theme{
app: app,
repository: repository,
list: getThemes(app.GetLocalizerService()),
}
theme.init()
return theme
}
func (t theme) init() {
themeInfo := t.GetCurrentThemeInfo()
if themeInfo.GetName() == "default" {
t.app.GetAppFyne().Settings().SetTheme(fyneTheme.DefaultTheme())
return
}
t.app.GetAppFyne().Settings().SetTheme(&forcedVariant{theme: fyneTheme.DefaultTheme(), variant: themeInfo.GetVariant()})
}
func (t theme) GetCurrentThemeInfo() ThemeInfoContract {
themes := t.List()
if themeInfo, ok := themes[t.repository.GetCode()]; ok {
return themeInfo
}
return themes["default"]
}
func (t theme) List() map[string]ThemeInfoContract {
return t.list
}
func (t theme) SetCurrentTheme(themeInfo ThemeInfoContract) error {
_, err := t.repository.Save(themeInfo.GetName())
if err != nil {
return err
}
if themeInfo.GetName() == "default" {
t.app.GetAppFyne().Settings().SetTheme(fyneTheme.DefaultTheme())
return nil
}
t.app.GetAppFyne().Settings().SetTheme(&forcedVariant{theme: fyneTheme.DefaultTheme(), variant: themeInfo.GetVariant()})
return nil
}
type ThemeInfoContract interface {
GetName() string
GetTitle() string
GetVariant() fyne.ThemeVariant
}
type themeInfo struct {
name string
title string
variant fyne.ThemeVariant
}
func (inf themeInfo) GetName() string {
return inf.name
}
func (inf themeInfo) GetTitle() string {
return inf.title
}
func (inf themeInfo) GetVariant() fyne.ThemeVariant {
return inf.variant
}
func getThemes(localizer kernel.LocalizerContract) map[string]ThemeInfoContract {
themesNameDefault := &themeInfo{
name: "default",
title: localizer.GetMessage(&i18n.LocalizeConfig{
MessageID: "themesNameDefault",
}),
}
themesNameLight := &themeInfo{
name: "light",
title: localizer.GetMessage(&i18n.LocalizeConfig{
MessageID: "themesNameLight",
}),
variant: fyneTheme.VariantLight,
}
themesNameDark := &themeInfo{
name: "dark",
title: localizer.GetMessage(&i18n.LocalizeConfig{
MessageID: "themesNameDark",
}),
variant: fyneTheme.VariantDark,
}
list := map[string]ThemeInfoContract{
"default": themesNameDefault,
"light": themesNameLight,
"dark": themesNameDark,
}
localizer.AddChangeCallback("themesNameDefault", func(text string) {
themesNameDefault.title = text
})
localizer.AddChangeCallback("themesNameLight", func(text string) {
themesNameLight.title = text
})
localizer.AddChangeCallback("themesNameDark", func(text string) {
themesNameDark.title = text
})
return list
}
type forcedVariant struct {
theme fyne.Theme
variant fyne.ThemeVariant
}
func (f *forcedVariant) Color(name fyne.ThemeColorName, _ fyne.ThemeVariant) color.Color {
return f.theme.Color(name, f.variant)
}
func (f *forcedVariant) Font(style fyne.TextStyle) fyne.Resource {
return fyneTheme.DefaultTheme().Font(style)
}
func (f *forcedVariant) Icon(name fyne.ThemeIconName) fyne.Resource {
return fyneTheme.DefaultTheme().Icon(name)
}
func (f *forcedVariant) Size(name fyne.ThemeSizeName) float32 {
return fyneTheme.DefaultTheme().Size(name)
}