unit.go raw

   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