Files
winauth-go/internal/ui/dialog_icon_picker.go
T
iceking2nd 969600b6ee feat(ui): 接入 vendor 真实图标库 + 图标选择器
- assets/icons/ 共 80+ 张真实品牌图标嵌入 UI 包
- entry 新增 IconKey 字段,持久化到 config.Entry.IconName
- iconPickerDialog 网格布局图标选择器
- Add 完成后自动弹出图标选择器,Skip 则用 vendor 占位图
- ⋯ 菜单新增 "Change icon..." 项
- vendorIconFor 解析顺序:catalog → vendor 占位 → qr 兜底
2026-06-12 09:41:30 +08:00

138 lines
3.6 KiB
Go

package ui
import (
"image"
"gioui.org/layout"
"gioui.org/op/clip"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"gioui.org/widget/material"
"git.wxccs.org/iceking2nd/winauth-go/internal/i18n"
)
// iconPickerDialog shows the full catalog of brand icons in a scrollable
// grid. The user clicks an icon to select it, then OK to confirm. A
// fixed column count keeps row layout cheap (no FlowWrap needed).
type iconPickerDialog struct {
icons []string
selected string
clickables []widget.Clickable
list widget.List
okBtn widget.Clickable
cancelBtn widget.Clickable
cols int
}
func newIconPickerDialog(currentKey string) *iconPickerDialog {
keys := allIconKeys()
d := &iconPickerDialog{
icons: keys,
selected: currentKey,
clickables: make([]widget.Clickable, len(keys)),
cols: 6,
}
d.list.Axis = layout.Vertical
return d
}
// iconExistsInCatalog reports whether key is in the published icon
// catalog (as opposed to the fallback vendor icons).
func iconExistsInCatalog(key string) bool {
for _, k := range allIconKeys() {
if k == key {
return true
}
}
return false
}
func (d *iconPickerDialog) Layout(
gtx layout.Context, th *material.Theme,
onDone func(iconKey string, cancel bool),
) layout.Dimensions {
for i := range d.icons {
if d.clickables[i].Clicked(gtx) {
d.selected = d.icons[i]
}
}
if d.cancelBtn.Clicked(gtx) {
onDone("", true)
return layout.Dimensions{Size: gtx.Constraints.Max}
}
if d.okBtn.Clicked(gtx) {
onDone(d.selected, false)
return layout.Dimensions{Size: gtx.Constraints.Max}
}
body := func(gtx layout.Context) layout.Dimensions {
// Cap body height so the picker scrolls instead of overflowing
// the screen on small windows.
maxH := gtx.Dp(unit.Dp(320))
if gtx.Constraints.Max.Y > maxH {
gtx.Constraints.Max.Y = maxH
}
cellDp := unit.Dp(48)
cellPx := gtx.Dp(cellDp)
cols := d.cols
totalRows := (len(d.icons) + cols - 1) / cols
return material.List(th, &d.list).Layout(gtx, totalRows, func(gtx layout.Context, rowIdx int) layout.Dimensions {
start := rowIdx * cols
end := start + cols
if end > len(d.icons) {
end = len(d.icons)
}
rowIcons := d.icons[start:end]
children := make([]layout.FlexChild, len(rowIcons))
for j, key := range rowIcons {
idx := start + j
key := key
sel := key == d.selected
children[j] = layout.Rigid(func(gtx layout.Context) layout.Dimensions {
return layout.Inset{
Right: unit.Dp(4), Bottom: unit.Dp(4),
}.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
gtx.Constraints.Min = image.Pt(cellPx, cellPx)
gtx.Constraints.Max = image.Pt(cellPx, cellPx)
return d.clickables[idx].Layout(gtx, func(gtx layout.Context) layout.Dimensions {
return drawIconCell(gtx, key, sel)
})
})
})
}
return layout.Flex{Axis: layout.Horizontal}.Layout(gtx, children...)
})
}
return modalCard(gtx, th, i18n.T("dialog_icon_picker_title"),
i18n.T("btn_ok"), i18n.T("btn_cancel"),
&d.okBtn, &d.cancelBtn, body)
}
// drawIconCell paints one grid cell: optional selection background +
// the icon scaled to fit. Returns the fixed cell-size dimensions.
func drawIconCell(gtx layout.Context, key string, selected bool) layout.Dimensions {
size := gtx.Constraints.Min
if selected {
rect := clip.Rect{Max: size}.Push(gtx.Ops)
paint.ColorOp{Color: activePalette.RingBg}.Add(gtx.Ops)
paint.PaintOp{}.Add(gtx.Ops)
rect.Pop()
}
img := vendorIconFor(key)
if img != nil {
w := widget.Image{
Src: paint.NewImageOp(img),
Fit: widget.Contain,
Position: layout.Center,
}
w.Layout(gtx)
}
return layout.Dimensions{Size: size}
}