feat(ui): 主题切换 + 语言持久化 + About 对话框
- 新增 light/dark 主题调色板与 Windows 注册表系统主题探测 - 偏好设置对话框:主题三选一 + 语言下拉(en/zh-CN/de),即时生效 - 持久化 Language/Theme/AutoLockMinutes/MinimizeToTray 至 YAML 顶层 - 启动时按 config 中保存的语言初始化 i18n - About 对话框展示 version/runtime/项目地址/许可证 - 全部硬编码颜色迁移到 themePalette,为深色模式做准备
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"gioui.org/layout"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
|
||||
"git.wxccs.org/iceking2nd/winauth-go/internal/i18n"
|
||||
)
|
||||
|
||||
// preferencesDialog edits the persistent UI prefs: UI language and
|
||||
// theme. Auto-lock and minimize-to-tray live in their own bucket and
|
||||
// will appear here later.
|
||||
type preferencesDialog struct {
|
||||
lang string
|
||||
theme themeMode
|
||||
|
||||
// Theme selection — three "radio" buttons.
|
||||
themeSystemBt widget.Clickable
|
||||
themeLightBt widget.Clickable
|
||||
themeDarkBt widget.Clickable
|
||||
|
||||
// Language selection — one button per supported locale. Cheap
|
||||
// substitute for a dropdown until Gio gains a native one.
|
||||
langEnBt widget.Clickable
|
||||
langZhCNBt widget.Clickable
|
||||
langDeBt widget.Clickable
|
||||
|
||||
okBt widget.Clickable
|
||||
cancelBt widget.Clickable
|
||||
}
|
||||
|
||||
// preferencesResult is what Layout's callback receives. cancel skips
|
||||
// any persistence.
|
||||
type preferencesResult struct {
|
||||
cancel bool
|
||||
language string
|
||||
theme themeMode
|
||||
}
|
||||
|
||||
// supportedLanguages is the closed set the prefs dialog exposes. Keep
|
||||
// in sync with internal/i18n/locales/*.toml.
|
||||
var supportedLanguages = []struct {
|
||||
tag string
|
||||
label string
|
||||
}{
|
||||
{"en", "English"},
|
||||
{"zh-CN", "中文 (简体)"},
|
||||
{"de", "Deutsch"},
|
||||
}
|
||||
|
||||
func newPreferencesDialog(currentLang string, currentTheme themeMode) *preferencesDialog {
|
||||
if currentLang == "" {
|
||||
currentLang = "en"
|
||||
}
|
||||
if currentTheme == "" {
|
||||
currentTheme = themeSystem
|
||||
}
|
||||
return &preferencesDialog{lang: currentLang, theme: currentTheme}
|
||||
}
|
||||
|
||||
func (d *preferencesDialog) Layout(
|
||||
gtx layout.Context, th *material.Theme,
|
||||
onDone func(preferencesResult),
|
||||
) layout.Dimensions {
|
||||
if d.cancelBt.Clicked(gtx) {
|
||||
onDone(preferencesResult{cancel: true})
|
||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||
}
|
||||
if d.okBt.Clicked(gtx) {
|
||||
onDone(preferencesResult{language: d.lang, theme: d.theme})
|
||||
return layout.Dimensions{Size: gtx.Constraints.Max}
|
||||
}
|
||||
|
||||
// Map button clicks to model updates before the next layout.
|
||||
switch {
|
||||
case d.themeSystemBt.Clicked(gtx):
|
||||
d.theme = themeSystem
|
||||
case d.themeLightBt.Clicked(gtx):
|
||||
d.theme = themeLight
|
||||
case d.themeDarkBt.Clicked(gtx):
|
||||
d.theme = themeDark
|
||||
case d.langEnBt.Clicked(gtx):
|
||||
d.lang = "en"
|
||||
case d.langZhCNBt.Clicked(gtx):
|
||||
d.lang = "zh-CN"
|
||||
case d.langDeBt.Clicked(gtx):
|
||||
d.lang = "de"
|
||||
}
|
||||
|
||||
// Render one labeled "chip" — emphasised when selected, regular
|
||||
// otherwise. Implemented as a material.Button so the click target
|
||||
// stays generous without us hand-rolling a Pointer area.
|
||||
chip := func(btn *widget.Clickable, label string, selected bool) layout.FlexChild {
|
||||
return layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Inset{Right: unit.Dp(8), Bottom: unit.Dp(4)}.Layout(gtx,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
b := material.Button(th, btn, label)
|
||||
if !selected {
|
||||
b.Background = activePalette.RingBg
|
||||
b.Color = activePalette.OnBackground
|
||||
}
|
||||
return b.Layout(gtx)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
body := func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Vertical}.Layout(gtx,
|
||||
layout.Rigid(material.Body1(th, i18n.T("label_theme")).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
chip(&d.themeSystemBt, i18n.T("theme_system"), d.theme == themeSystem),
|
||||
chip(&d.themeLightBt, i18n.T("theme_light"), d.theme == themeLight),
|
||||
chip(&d.themeDarkBt, i18n.T("theme_dark"), d.theme == themeDark),
|
||||
)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(12)}.Layout),
|
||||
layout.Rigid(material.Body1(th, i18n.T("label_language")).Layout),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(6)}.Layout),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx,
|
||||
chip(&d.langEnBt, supportedLanguages[0].label, d.lang == "en"),
|
||||
chip(&d.langZhCNBt, supportedLanguages[1].label, d.lang == "zh-CN"),
|
||||
chip(&d.langDeBt, supportedLanguages[2].label, d.lang == "de"),
|
||||
)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
return modalCard(gtx, th, i18n.T("dialog_preferences_title"),
|
||||
i18n.T("btn_ok"), i18n.T("btn_cancel"),
|
||||
&d.okBt, &d.cancelBt, body)
|
||||
}
|
||||
Reference in New Issue
Block a user