Files
winauth-go/internal/ui/entry_ring.go
T
iceking2nd fedc198452 feat(ui): vendor 图标系统 + 应用图标占位
- 新增 tools/gen_icons 生成 7 张 vendor 占位 PNG + 256x256 应用图标
- internal/ui/vendor_icons.go 用 //go:embed 加载并按 Authenticator.Name() 查表
- entry 行圆环中心叠 16dp vendor 图标,HOTP 直接用图标占位
- 图标为通用色块 + 首字母,无第三方商标;用户可按文件名替换为真实 logo
- assets/README 说明替换方式与 Windows .syso 嵌入步骤
2026-06-12 03:47:00 +08:00

120 lines
3.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package ui
import (
"image"
"time"
"gioui.org/layout"
"gioui.org/op"
"gioui.org/op/paint"
"gioui.org/unit"
"gioui.org/widget"
"git.wxccs.org/iceking2nd/winauth-go/internal/authenticator"
)
// entryProgressRing draws the per-row TOTP countdown ring with the
// vendor icon nested in the centre. For HOTP entries the ring is
// omitted (no countdown to show) and the icon fills the same slot so
// rows still line up.
func entryProgressRing(gtx layout.Context, en *entry) layout.Dimensions {
const ringDp = 24
const iconDp = 16
if en.Auth == nil {
return layout.Dimensions{Size: image.Pt(gtx.Dp(ringDp), gtx.Dp(ringDp))}
}
vendor := en.Auth.Name()
if vendor == "hotp" {
// HOTP has no period to visualise — just show the icon scaled
// to the full slot so rows still align.
return drawCenteredIcon(gtx, vendor, ringDp, ringDp)
}
period := totpPeriod(en.Auth)
if period <= 0 {
period = authenticator.DefaultPeriod
}
now := time.Now().Unix()
elapsed := now % int64(period)
remaining := int64(period) - elapsed
progress := float32(remaining) / float32(period)
fg := activePalette.RingFg
if remaining <= 5 {
fg = activePalette.RingFgWarn
}
bg := activePalette.RingBg
ringDims := progressRing{
Size: unit.Dp(ringDp),
Stroke: unit.Dp(2.5),
Progress: progress,
Color: fg,
BgColor: bg,
}.Layout(gtx)
// Overlay the vendor icon in the centre of the same slot. We do
// this by recording the icon op into a fixed-size context, then
// offsetting it so its centre matches the ring's centre.
iconPx := gtx.Dp(iconDp)
iconGtx := gtx
iconGtx.Constraints.Min = image.Pt(iconPx, iconPx)
iconGtx.Constraints.Max = image.Pt(iconPx, iconPx)
macro := op.Record(gtx.Ops)
paintIcon(iconGtx, vendor)
call := macro.Stop()
offset := image.Pt(
(ringDims.Size.X-iconPx)/2,
(ringDims.Size.Y-iconPx)/2,
)
st := op.Offset(offset).Push(gtx.Ops)
call.Add(gtx.Ops)
st.Pop()
return ringDims
}
// drawCenteredIcon paints a vendor icon scaled to fit a w×h slot. Used
// for HOTP entries that have no ring around them.
func drawCenteredIcon(gtx layout.Context, vendor string, wDp, hDp unit.Dp) layout.Dimensions {
wPx, hPx := gtx.Dp(wDp), gtx.Dp(hDp)
gtx.Constraints.Min = image.Pt(wPx, hPx)
gtx.Constraints.Max = image.Pt(wPx, hPx)
return paintIcon(gtx, vendor)
}
// paintIcon draws the vendor icon scaled to fit the current
// constraints. Returns its layout dimensions so callers can compose it
// inside a Flex. Returns a zero-sized layout if the icon is missing.
func paintIcon(gtx layout.Context, vendor string) layout.Dimensions {
img := vendorIconFor(vendor)
if img == nil {
return layout.Dimensions{Size: gtx.Constraints.Min}
}
w := widget.Image{
Src: paint.NewImageOp(img),
Fit: widget.Contain,
Position: layout.Center,
}
// Match one source pixel to one device pixel scaled by Fit. We
// don't want device-DPI scaling on top of widget.Fit's own
// scaling, so leave Scale at its 1.0 default.
return w.Layout(gtx)
}
// totpPeriod extracts the configured period from any authenticator whose
// underlying Base we can reach. Returns 0 if the authenticator does not
// expose one (in which case callers fall back to the default).
func totpPeriod(a authenticator.Authenticator) int {
type periodGetter interface{ GetPeriod() int }
if pg, ok := a.(periodGetter); ok {
return pg.GetPeriod()
}
// All current TOTP-like authenticators (Google/Microsoft/Okta/Steam/
// BattleNet) embed authenticator.Base whose default period is 30s,
// matching the C# original. Hard-code that here.
return authenticator.DefaultPeriod
}