144 lines
3.4 KiB
Go
144 lines
3.4 KiB
Go
package mujica
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
|
|
"github.com/egoist/mygo/ui"
|
|
|
|
"mujicaui/theme"
|
|
)
|
|
|
|
// BadgeTone is a badge's emphasis. The zero value is BadgeAccent.
|
|
type BadgeTone int
|
|
|
|
const (
|
|
BadgeAccent BadgeTone = iota
|
|
BadgeNeutral
|
|
BadgeSuccess
|
|
BadgeWarning
|
|
BadgeDanger
|
|
BadgeInfo
|
|
)
|
|
|
|
// BadgePosition places a badge; zero is inline.
|
|
type BadgePosition int
|
|
|
|
const (
|
|
BadgeInline BadgePosition = iota
|
|
BadgeTopRight
|
|
BadgeTopLeft
|
|
BadgeBottomRight
|
|
BadgeBottomLeft
|
|
)
|
|
|
|
// BadgeOptions configure a Badge.
|
|
type BadgeOptions struct {
|
|
// Text shows as is; when empty, Count shows instead.
|
|
Text string
|
|
// Count must not be negative; zero without Text hides it.
|
|
Count int
|
|
// Max caps the shown count as "Max+"; zero means 99.
|
|
Max int
|
|
Tone BadgeTone
|
|
Position BadgePosition
|
|
// Label says what is counted; read before the full value.
|
|
Label string
|
|
}
|
|
|
|
// Badge shows text or a count, inline or cornered.
|
|
func Badge(c *ui.Context, opts BadgeOptions, content func()) *ui.Element {
|
|
if opts.Count < 0 || opts.Max < 0 {
|
|
panic("mujica: Badge Count and Max must not be negative")
|
|
}
|
|
if (opts.Position == BadgeInline) != (content == nil) {
|
|
panic("mujica: Badge needs content exactly when Position is not BadgeInline")
|
|
}
|
|
shown, full := badgeText(opts)
|
|
if opts.Position == BadgeInline {
|
|
if shown == "" {
|
|
return ui.Box(c).Size(0, 0)
|
|
}
|
|
return badgePill(c, opts, shown, full)
|
|
}
|
|
at := badgeAnchor(opts.Position)
|
|
wrap := ui.Box(c).Shrink(0).AlignSelf(ui.Start).AlignItems(ui.Start)
|
|
wrap.Children(func() {
|
|
content()
|
|
if shown != "" {
|
|
badgePill(c, opts, shown, full).Attach(at, ui.AnchorCenter)
|
|
}
|
|
})
|
|
return wrap
|
|
}
|
|
|
|
// badgeText returns what the badge shows and what it reads.
|
|
func badgeText(opts BadgeOptions) (shown, full string) {
|
|
if opts.Text != "" {
|
|
return opts.Text, opts.Text
|
|
}
|
|
if opts.Count == 0 {
|
|
return "", ""
|
|
}
|
|
limit := opts.Max
|
|
if limit == 0 {
|
|
limit = 99
|
|
}
|
|
full = strconv.Itoa(opts.Count)
|
|
if opts.Count > limit {
|
|
return strconv.Itoa(limit) + "+", full
|
|
}
|
|
return full, full
|
|
}
|
|
|
|
func badgePill(c *ui.Context, opts BadgeOptions, shown, full string) *ui.Element {
|
|
k := Tokens(c)
|
|
bg, fg, border := badgeColors(k, opts.Tone)
|
|
label := full
|
|
if opts.Label != "" {
|
|
label = opts.Label + " " + full
|
|
}
|
|
p := ui.Box(c).Row().Center().MinWidth(FontSize(c, 18)).MinHeight(FontSize(c, 18)).Padding(0, 5).Radius(theme.CardRadius).
|
|
Background(bg).Shrink(0).Role(ui.RoleStatus).Label(label)
|
|
if border.A > 0 {
|
|
p.Border(theme.BorderWidth, border)
|
|
}
|
|
p.Children(func() {
|
|
ui.Text(c, shown).FontSize(FontSize(c, theme.CaptionSize-1)).FontWeight(600).TextColor(fg).
|
|
FontFeatures("tnum").SingleLine()
|
|
})
|
|
return p
|
|
}
|
|
|
|
func badgeColors(k theme.Tokens, t BadgeTone) (bg, fg, border ui.Color) {
|
|
switch t {
|
|
case BadgeAccent:
|
|
return k.Accent, k.OnAccent, ui.Color{}
|
|
case BadgeNeutral:
|
|
return k.SurfaceHover, k.Text, k.Border
|
|
case BadgeSuccess:
|
|
return k.Surface, k.Success, k.Success
|
|
case BadgeWarning:
|
|
return k.Surface, k.Warning, k.Warning
|
|
case BadgeDanger:
|
|
return k.DangerFill, k.OnDanger, ui.Color{}
|
|
case BadgeInfo:
|
|
return k.Surface, k.Info, k.Info
|
|
}
|
|
panic(fmt.Sprintf("mujica: unknown BadgeTone %d", t))
|
|
}
|
|
|
|
func badgeAnchor(p BadgePosition) ui.Anchor {
|
|
switch p {
|
|
case BadgeTopRight:
|
|
return ui.AnchorTopRight
|
|
case BadgeTopLeft:
|
|
return ui.AnchorTopLeft
|
|
case BadgeBottomRight:
|
|
return ui.AnchorBottomRight
|
|
case BadgeBottomLeft:
|
|
return ui.AnchorBottomLeft
|
|
}
|
|
panic(fmt.Sprintf("mujica: unknown BadgePosition %d", p))
|
|
}
|