1 // SPDX-License-Identifier: Unlicense OR MIT
2 3 /*
4 5 Package unit implements device independent units and values.
6 7 A Value is a value with a Unit attached.
8 9 Device independent pixel, or dp, is the unit for sizes independent of
10 the underlying display device.
11 12 Scaled pixels, or sp, is the unit for text sizes. An sp is like dp with
13 text scaling applied.
14 15 Finally, pixels, or px, is the unit for display dependent pixels. Their
16 size vary between platforms and displays.
17 18 To maintain a constant visual size across platforms and displays, always
19 use dps or sps to define user interfaces. Only use pixels for derived
20 values.
21 22 */
23 package unit
24 25 import (
26 "fmt"
27 "math"
28 )
29 30 // Value is a value with a unit.
31 type Value struct {
32 V float32
33 U Unit
34 }
35 36 // Unit represents a unit for a Value.
37 type Unit uint8
38 39 // Metric converts Values to device-dependent pixels, px. The zero
40 // value represents a 1-to-1 scale from dp, sp to pixels.
41 type Metric struct {
42 // PxPerDp is the device-dependent pixels per dp.
43 PxPerDp float32
44 // PxPerSp is the device-dependent pixels per sp.
45 PxPerSp float32
46 }
47 48 const (
49 // UnitPx represent device pixels in the resolution of
50 // the underlying display.
51 UnitPx Unit = iota
52 // UnitDp represents device independent pixels. 1 dp will
53 // have the same apparent size across platforms and
54 // display resolutions.
55 UnitDp
56 // UnitSp is like UnitDp but for font sizes.
57 UnitSp
58 )
59 60 // Px returns the Value for v device pixels.
61 func Px(v float32) Value {
62 return Value{V: v, U: UnitPx}
63 }
64 65 // Dp returns the Value for v device independent
66 // pixels.
67 func Dp(v float32) Value {
68 return Value{V: v, U: UnitDp}
69 }
70 71 // Sp returns the Value for v scaled dps.
72 func Sp(v float32) Value {
73 return Value{V: v, U: UnitSp}
74 }
75 76 // Scale returns the value scaled by s.
77 func (v Value) Scale(s float32) Value {
78 v.V *= s
79 return v
80 }
81 82 func (v Value) String() string {
83 return fmt.Sprintf("%g%s", v.V, v.U)
84 }
85 86 func (u Unit) String() string {
87 switch u {
88 case UnitPx:
89 return "px"
90 case UnitDp:
91 return "dp"
92 case UnitSp:
93 return "sp"
94 default:
95 panic("unknown unit")
96 }
97 }
98 99 // Add a list of Values.
100 func Add(c Metric, values ...Value) Value {
101 var sum Value
102 for _, v := range values {
103 sum, v = compatible(c, sum, v)
104 sum.V += v.V
105 }
106 return sum
107 }
108 109 // Max returns the maximum of a list of Values.
110 func Max(c Metric, values ...Value) Value {
111 var max Value
112 for _, v := range values {
113 max, v = compatible(c, max, v)
114 if v.V > max.V {
115 max.V = v.V
116 }
117 }
118 return max
119 }
120 121 func (c Metric) Px(v Value) int {
122 var r float32
123 switch v.U {
124 case UnitPx:
125 r = v.V
126 case UnitDp:
127 s := c.PxPerDp
128 if s == 0 {
129 s = 1
130 }
131 r = s * v.V
132 case UnitSp:
133 s := c.PxPerSp
134 if s == 0 {
135 s = 1
136 }
137 r = s * v.V
138 default:
139 panic("unknown unit")
140 }
141 return int(math.Round(float64(r)))
142 }
143 144 func compatible(c Metric, v1, v2 Value) (Value, Value) {
145 if v1.U == v2.U {
146 return v1, v2
147 }
148 if v1.V == 0 {
149 v1.U = v2.U
150 return v1, v2
151 }
152 if v2.V == 0 {
153 v2.U = v1.U
154 return v1, v2
155 }
156 return Px(float32(c.Px(v1))), Px(float32(c.Px(v2)))
157 }
158