icon.go raw

   1  package gel
   2  
   3  import (
   4  	"image"
   5  	"image/color"
   6  	"image/draw"
   7  	
   8  	l "github.com/p9c/gio/layout"
   9  	"github.com/p9c/gio/op/paint"
  10  	"github.com/p9c/gio/unit"
  11  	"golang.org/x/exp/shiny/iconvg"
  12  )
  13  
  14  type Icon struct {
  15  	*Window
  16  	color string
  17  	src   *[]byte
  18  	size  unit.Value
  19  	// Cached values.
  20  	sz       int
  21  	op       paint.ImageOp
  22  	imgSize  int
  23  	imgColor string
  24  }
  25  
  26  type IconByColor map[color.NRGBA]paint.ImageOp
  27  type IconBySize map[float32]IconByColor
  28  type IconCache map[*[]byte]IconBySize
  29  
  30  // Icon returns a new Icon from iconVG data.
  31  func (w *Window) Icon() *Icon {
  32  	return &Icon{Window: w, size: w.TextSize, color: "DocText"}
  33  }
  34  
  35  // Color sets the color of the icon image. It must be called before creating the image
  36  func (i *Icon) Color(color string) *Icon {
  37  	i.color = color
  38  	return i
  39  }
  40  
  41  // Src sets the icon source to draw from
  42  func (i *Icon) Src(data *[]byte) *Icon {
  43  	_, e := iconvg.DecodeMetadata(*data)
  44  	if E.Chk(e) {
  45  		D.Ln("no image data, crashing")
  46  		panic(e)
  47  		// return nil
  48  	}
  49  	i.src = data
  50  	return i
  51  }
  52  
  53  // Scale changes the size relative to the base font size
  54  func (i *Icon) Scale(scale float32) *Icon {
  55  	i.size = i.Theme.TextSize.Scale(scale)
  56  	return i
  57  }
  58  
  59  func (i *Icon) Size(size unit.Value) *Icon {
  60  	i.size = size
  61  	return i
  62  }
  63  
  64  // Fn renders the icon
  65  func (i *Icon) Fn(gtx l.Context) l.Dimensions {
  66  	ico := i.image(gtx.Px(i.size))
  67  	if i.src == nil {
  68  		panic("icon is nil")
  69  	}
  70  	ico.Add(gtx.Ops)
  71  	paint.PaintOp{
  72  		// Rect: f32.Rectangle{
  73  		// 	Max: Fpt(ico.Size()),
  74  		// },
  75  	}.Add(gtx.Ops)
  76  	return l.Dimensions{Size: ico.Size()}
  77  }
  78  
  79  func (i *Icon) image(sz int) paint.ImageOp {
  80  	// if sz == i.imgSize && i.color == i.imgColor {
  81  	// 	// D.Ln("reusing old icon")
  82  	// 	return i.op
  83  	// }
  84  	if ico, ok := i.Theme.iconCache[i.src]; ok {
  85  		if isz, ok := ico[i.size.V]; ok {
  86  			if icl, ok := isz[i.Theme.Colors.GetNRGBAFromName(i.color)]; ok {
  87  				return icl
  88  			}
  89  		}
  90  	}
  91  	m, _ := iconvg.DecodeMetadata(*i.src)
  92  	dx, dy := m.ViewBox.AspectRatio()
  93  	img := image.NewRGBA(image.Rectangle{Max: image.Point{X: sz,
  94  		Y: int(float32(sz) * dy / dx)}})
  95  	var ico iconvg.Rasterizer
  96  	ico.SetDstImage(img, img.Bounds(), draw.Src)
  97  	m.Palette[0] = color.RGBA(i.Theme.Colors.GetNRGBAFromName(i.color))
  98  	if e := iconvg.Decode(&ico, *i.src, &iconvg.DecodeOptions{
  99  		Palette: &m.Palette,
 100  	}); E.Chk(e) {
 101  	}
 102  	operation := paint.NewImageOp(img)
 103  	// create the maps if they don't exist
 104  	if _, ok := i.Theme.iconCache[i.src]; !ok {
 105  		i.Theme.iconCache[i.src] = make(IconBySize)
 106  	}
 107  	if _, ok := i.Theme.iconCache[i.src][i.size.V]; !ok {
 108  		i.Theme.iconCache[i.src][i.size.V] = make(IconByColor)
 109  	}
 110  	i.Theme.iconCache[i.src][i.size.V][i.Theme.Colors.GetNRGBAFromName(i.color)] = operation
 111  	return operation
 112  }
 113