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:
+51
-9
@@ -10,12 +10,10 @@ import (
|
||||
"time"
|
||||
|
||||
"gioui.org/app"
|
||||
"gioui.org/font/gofont"
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/op/clip"
|
||||
"gioui.org/op/paint"
|
||||
"gioui.org/text"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget"
|
||||
"gioui.org/widget/material"
|
||||
@@ -95,6 +93,8 @@ type appState struct {
|
||||
hotkeyDialog *hotkeyDialog
|
||||
hotkeyTarget *entry
|
||||
tradesDialog *steamTradesDialog
|
||||
prefsDialog *preferencesDialog
|
||||
aboutDialog *aboutDialog
|
||||
|
||||
store *store
|
||||
saveErr string // surfaced in the top bar
|
||||
@@ -102,6 +102,13 @@ type appState struct {
|
||||
hkMgr *win32.HotkeyManager
|
||||
|
||||
toast toast
|
||||
|
||||
// themeMode is the user's current theme preference. drawFrame
|
||||
// rebuilds the material.Theme when this changes.
|
||||
themeMode themeMode
|
||||
// theme is the cached material.Theme matching themeMode. nil forces a
|
||||
// rebuild on the next frame.
|
||||
theme *material.Theme
|
||||
}
|
||||
|
||||
// snapshotEntries returns a freshly serialized slice of config entries.
|
||||
@@ -119,9 +126,6 @@ func (st *appState) snapshotEntries() []config.Entry {
|
||||
func loop(w *app.Window, configPath string) error {
|
||||
const fn = "internal.ui.loop"
|
||||
|
||||
th := material.NewTheme()
|
||||
th.Shaper = text.NewShaper(text.WithCollection(gofont.Collection()))
|
||||
|
||||
state := &appState{}
|
||||
state.list.Axis = layout.Vertical
|
||||
|
||||
@@ -146,6 +150,12 @@ func loop(w *app.Window, configPath string) error {
|
||||
state.absorbConfig(cfg)
|
||||
}
|
||||
|
||||
// Seed the theme cache from the stored preference. Empty / unknown
|
||||
// values resolve to "system" which buildTheme then maps to light or
|
||||
// dark via the OS-specific systemPrefersDark probe.
|
||||
_, themePref, _, _ := state.store.Preferences()
|
||||
state.themeMode = normalizeThemeMode(themePref)
|
||||
|
||||
// Spin up the global hotkey manager and register whatever the user
|
||||
// already had configured. Failures are non-fatal (logged + the row
|
||||
// just won't fire).
|
||||
@@ -170,7 +180,15 @@ func loop(w *app.Window, configPath string) error {
|
||||
return e.Err
|
||||
case app.FrameEvent:
|
||||
gtx := app.NewContext(&ops, e)
|
||||
drawFrame(gtx, th, state, w)
|
||||
if state.theme == nil {
|
||||
th, pal := buildTheme(state.themeMode)
|
||||
state.theme = th
|
||||
activePalette = pal
|
||||
}
|
||||
// Paint the window background using the active palette so
|
||||
// dark mode does not show through as the Gio default grey.
|
||||
fillBackground(gtx, activePalette.Background)
|
||||
drawFrame(gtx, state.theme, state, w)
|
||||
e.Frame(gtx.Ops)
|
||||
}
|
||||
}
|
||||
@@ -320,8 +338,11 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
||||
st.setPwDialog = newSetPasswordDialog()
|
||||
case settingsActionImportLegacy:
|
||||
st.importDialog = newImportLegacyDialog()
|
||||
case settingsActionPreferences:
|
||||
lang, theme, _, _ := st.store.Preferences()
|
||||
st.prefsDialog = newPreferencesDialog(lang, normalizeThemeMode(theme))
|
||||
case settingsActionAbout:
|
||||
// TODO: about dialog (next phase).
|
||||
st.aboutDialog = newAboutDialog()
|
||||
}
|
||||
w.Invalidate()
|
||||
} else {
|
||||
@@ -329,6 +350,27 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
||||
}
|
||||
}
|
||||
|
||||
if st.prefsDialog != nil {
|
||||
return st.prefsDialog.Layout(gtx, th, func(r preferencesResult) {
|
||||
if !r.cancel {
|
||||
_, _, autoLock, minToTray := st.store.Preferences()
|
||||
st.store.SetPreferences(r.language, string(r.theme), autoLock, minToTray)
|
||||
i18n.SetLanguage(r.language)
|
||||
st.themeMode = r.theme
|
||||
st.theme = nil // force rebuild on next frame
|
||||
}
|
||||
st.prefsDialog = nil
|
||||
w.Invalidate()
|
||||
})
|
||||
}
|
||||
|
||||
if st.aboutDialog != nil {
|
||||
return st.aboutDialog.Layout(gtx, th, func() {
|
||||
st.aboutDialog = nil
|
||||
w.Invalidate()
|
||||
})
|
||||
}
|
||||
|
||||
if st.importDialog != nil {
|
||||
return st.importDialog.Layout(gtx, th, func(r importLegacyResult) {
|
||||
if !r.cancel && r.cfg != nil {
|
||||
@@ -427,7 +469,7 @@ func drawFrame(gtx layout.Context, th *material.Theme, st *appState, w *app.Wind
|
||||
return layout.Dimensions{}
|
||||
}
|
||||
lbl := material.Body2(th, msg)
|
||||
lbl.Color = color.NRGBA{R: 0xc0, A: 0xff}
|
||||
lbl.Color = activePalette.ErrorFg
|
||||
return layout.Inset{Top: unit.Dp(4)}.Layout(gtx, lbl.Layout)
|
||||
}),
|
||||
layout.Rigid(layout.Spacer{Height: unit.Dp(8)}.Layout),
|
||||
@@ -487,7 +529,7 @@ func entryRow(gtx layout.Context, th *material.Theme, en *entry) layout.Dimensio
|
||||
}),
|
||||
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
||||
lbl := material.H6(th, en.Code)
|
||||
lbl.Color = color.NRGBA{R: 0x10, G: 0x70, B: 0xff, A: 0xff}
|
||||
lbl.Color = activePalette.RingFg
|
||||
return lbl.Layout(gtx)
|
||||
}),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user