Files
MujicaUI/price_text.go
T

172 lines
4.0 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 mujica
import (
"fmt"
"log/slog"
"math"
"time"
"github.com/egoist/mygo/ui"
"mujicaui/icons"
"mujicaui/theme"
)
// Trend picks the rise color: green, or red (China).
type Trend int
const (
GreenUp Trend = iota
RedUp
)
type trendKey struct{}
// UseTrend sets the window's rise and fall colors.
func UseTrend(c *ui.Context, t Trend) {
if t != GreenUp && t != RedUp {
panic(fmt.Sprintf("mujica: unknown Trend %d", t))
}
*ui.Local(c.Root(), trendKey{}, func() Trend { return GreenUp }) = t
}
// trendColor colors a rise (sign > 0), fall or flat.
func trendColor(c *ui.Context, sign int) ui.Color {
k := Tokens(c)
up, down := k.Success, k.Danger
if *ui.Local(c.Root(), trendKey{}, func() Trend { return GreenUp }) == RedUp {
up, down = k.Danger, k.Success
}
switch {
case sign > 0:
return up
case sign < 0:
return down
}
return k.TextMuted
}
// trendIcon is the arrow that says the direction without color.
func trendIcon(sign int) *ui.SVG {
switch {
case sign > 0:
return icons.Must("arrow-up")
case sign < 0:
return icons.Must("arrow-down")
}
return icons.Must("minus")
}
// trendWord names the direction for assistive technology.
func trendWord(c *ui.Context, sign int) string {
switch {
case sign > 0:
return msg(c, "price_text.up")
case sign < 0:
return msg(c, "price_text.down")
}
return msg(c, "price_text.flat")
}
func trendSign(v float64) int {
switch {
case v > 0:
return 1
case v < 0:
return -1
}
return 0
}
// priceDecimals resolves a Decimals option: zero means 2, negative panics.
func priceDecimals(name string, d int) int {
if d < 0 {
panic("mujica: " + name + " with negative Decimals")
}
if d == 0 {
return 2
}
return d
}
// priceTextFlash is the tint's fade, three state steps long.
const priceTextFlash = 3 * theme.StateDuration
// PriceTextOptions configure a PriceText.
type PriceTextOptions struct {
// Decimals shown; zero means 2.
Decimals int
// NoGrouping drops the thousands separators.
NoGrouping bool
// Compare marks the price's move from Reference.
Compare bool
Reference float64
// NoFlash keeps the price still when it changes.
NoFlash bool
// Size is the font size; zero is the body size.
Size float32
}
type priceTextState struct {
last float64
seen bool
at time.Time
dir int
}
// PriceText shows a price; a change tints it once.
func PriceText(c *ui.Context, value float64, opts PriceTextOptions) *ui.Element {
dec := priceDecimals("PriceText", opts.Decimals)
if opts.Size < 0 {
panic("mujica: PriceText with negative Size")
}
size := opts.Size
if size == 0 {
size = theme.BodySize
}
text := statisticFormat(value, dec)
if opts.NoGrouping {
text = priceUngrouped(value, dec)
}
e := ui.Row(c).Gap(Density(c).Unit()).AlignItems(ui.Center).Padding(0, 2).Radius(theme.ControlRadius).Shrink(0)
st := ui.Local(e, "price", func() priceTextState { return priceTextState{} })
if st.seen && value != st.last {
st.at, st.dir = c.Now(), trendSign(value-st.last)
slog.Debug("mujica: price_text changed", "dir", st.dir)
}
st.last, st.seen = value, true
sign := 0
if opts.Compare {
sign = trendSign(value - opts.Reference)
}
col := Tokens(c).Text
spoken := text
if opts.Compare {
col = trendColor(c, sign)
spoken += ", " + trendWord(c, sign)
}
if d := Motion(c, priceTextFlash); d > 0 && !opts.NoFlash && !st.at.IsZero() {
if t := c.Now().Sub(st.at); t < d {
e.Background(trendColor(c, st.dir).Alpha(0.2 * float32(1-float64(t)/float64(d))))
c.AnimationFrame()
}
}
e.Role(ui.RoleText).Label(spoken).TextColor(col)
e.Children(func() {
if opts.Compare {
ui.Icon(c, trendIcon(sign)).Size(size*0.85, size*0.85)
}
ui.Text(c, text).FontSize(FontSize(c, size)).FontFeatures("tnum").FontWeight(500).SingleLine()
})
return e
}
// priceUngrouped writes v with dec decimals and no separators.
func priceUngrouped(v float64, dec int) string {
s := fmt.Sprintf("%.*f", dec, math.Abs(v))
if v < 0 && s != fmt.Sprintf("%.*f", dec, 0.0) {
return "−" + s
}
return s
}