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 }