icns

Easily create .icns files (Mac Icons) with this Go library or the included CLI.
Log | Files | Refs | LICENSE

appearance.go (3443B)


      1 package appicon
      2 
      3 import (
      4 	"encoding/json"
      5 	"fmt"
      6 )
      7 
      8 // Appearance names a variant a value is specialised for. The zero value is
      9 // the appearance the others vary from.
     10 type Appearance string
     11 
     12 // The appearances an icon is drawn in.
     13 const (
     14 	// AppearanceDefault is the appearance the others vary from.
     15 	AppearanceDefault Appearance = ""
     16 	// AppearanceDark is the icon as drawn in dark mode.
     17 	AppearanceDark Appearance = "dark"
     18 	// AppearanceTinted is the icon as drawn when the system tints it.
     19 	AppearanceTinted Appearance = "tinted"
     20 )
     21 
     22 // Specialized is a value that differs by appearance. An entry with the
     23 // default appearance is the one the others vary from, and is written first.
     24 type Specialized[T any] struct {
     25 	Appearance Appearance `json:"appearance,omitempty"`
     26 	Value      T          `json:"value"`
     27 }
     28 
     29 // For returns the value specialised for an appearance, falling back to the
     30 // default entry. The boolean reports whether either was found.
     31 func For[T any](values []Specialized[T], a Appearance) (T, bool) {
     32 	var (
     33 		out   T
     34 		found bool
     35 	)
     36 	for _, v := range values {
     37 		if v.Appearance == a {
     38 			return v.Value, true
     39 		}
     40 		if v.Appearance == AppearanceDefault {
     41 			out, found = v.Value, true
     42 		}
     43 	}
     44 	return out, found
     45 }
     46 
     47 // Fill is how a surface is filled. Exactly one of the three is written, in
     48 // the order they are listed here.
     49 type Fill struct {
     50 	// Gradient is a list of colour stops, first to last, each a colour space
     51 	// and its components, such as "srgb:1.00000,0.25279,1.00000,1.00000".
     52 	Gradient []string
     53 	// Solid is a single colour, written the same way as a stop.
     54 	Solid string
     55 	// Name is a fill the system provides, such as "automatic",
     56 	// "system-light" or "system-dark".
     57 	Name string
     58 }
     59 
     60 // NamedFill returns a fill the system provides.
     61 func NamedFill(name string) Fill { return Fill{Name: name} }
     62 
     63 // SolidFill returns a fill of one colour, such as
     64 // SolidFill("srgb:1.00000,0.25279,1.00000,1.00000").
     65 func SolidFill(colour string) Fill { return Fill{Solid: colour} }
     66 
     67 // GradientFill returns a linear gradient through the colours given.
     68 func GradientFill(stops ...string) Fill { return Fill{Gradient: stops} }
     69 
     70 // MarshalJSON writes the fill the way the manifest holds it: a bare string
     71 // for a named fill, and an object naming the kind for the others.
     72 func (f Fill) MarshalJSON() ([]byte, error) {
     73 	switch {
     74 	case len(f.Gradient) > 0:
     75 		return json.Marshal(map[string][]string{"linear-gradient": f.Gradient})
     76 	case f.Solid != "":
     77 		return json.Marshal(map[string]string{"solid": f.Solid})
     78 	case f.Name != "":
     79 		return json.Marshal(f.Name)
     80 	}
     81 	return nil, fmt.Errorf("%w: a fill names no colour", ErrEmptyFill)
     82 }
     83 
     84 // UnmarshalJSON reads a fill written either way.
     85 func (f *Fill) UnmarshalJSON(data []byte) error {
     86 	var name string
     87 	if err := json.Unmarshal(data, &name); err == nil {
     88 		*f = Fill{Name: name}
     89 		return nil
     90 	}
     91 	var object struct {
     92 		Gradient []string `json:"linear-gradient"`
     93 		Solid    string   `json:"solid"`
     94 	}
     95 	if err := json.Unmarshal(data, &object); err != nil {
     96 		return fmt.Errorf("reading a fill: %w", err)
     97 	}
     98 	*f = Fill{Gradient: object.Gradient, Solid: object.Solid}
     99 	return nil
    100 }
    101 
    102 // Position is where a layer sits relative to the space it is drawn in.
    103 type Position struct {
    104 	// Scale multiplies the layer's size, 1 leaving it as it is.
    105 	Scale float64 `json:"scale"`
    106 	// Translation moves it, across and down, in points.
    107 	Translation [2]float64 `json:"translation-in-points"`
    108 }