969600b6ee
- assets/icons/ 共 80+ 张真实品牌图标嵌入 UI 包 - entry 新增 IconKey 字段,持久化到 config.Entry.IconName - iconPickerDialog 网格布局图标选择器 - Add 完成后自动弹出图标选择器,Skip 则用 vendor 占位图 - ⋯ 菜单新增 "Change icon..." 项 - vendorIconFor 解析顺序:catalog → vendor 占位 → qr 兜底
138 lines
3.6 KiB
Go
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}
|
|
}
|