github.com/cybriq/giocore@v0.0.7-0.20210703034601-cfb9cb5f3900/unit/unit.go (about)

     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  }