switch.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. // SPDX-License-Identifier: Unlicense OR MIT
  2. package material
  3. import (
  4. "image"
  5. "image/color"
  6. "gioui.org/internal/f32color"
  7. "gioui.org/io/semantic"
  8. "gioui.org/layout"
  9. "gioui.org/op"
  10. "gioui.org/op/clip"
  11. "gioui.org/op/paint"
  12. "gioui.org/widget"
  13. )
  14. type SwitchStyle struct {
  15. Description string
  16. Color struct {
  17. Enabled color.NRGBA
  18. Disabled color.NRGBA
  19. Track color.NRGBA
  20. }
  21. Switch *widget.Bool
  22. }
  23. // Switch is for selecting a boolean value.
  24. func Switch(th *Theme, swtch *widget.Bool, description string) SwitchStyle {
  25. sw := SwitchStyle{
  26. Switch: swtch,
  27. Description: description,
  28. }
  29. sw.Color.Enabled = th.Palette.ContrastBg
  30. sw.Color.Disabled = th.Palette.Bg
  31. sw.Color.Track = f32color.MulAlpha(th.Palette.Fg, 0x88)
  32. return sw
  33. }
  34. // Layout updates the switch and displays it.
  35. func (s SwitchStyle) Layout(gtx layout.Context) layout.Dimensions {
  36. s.Switch.Update(gtx)
  37. trackWidth := gtx.Dp(36)
  38. trackHeight := gtx.Dp(16)
  39. thumbSize := gtx.Dp(20)
  40. trackOff := (thumbSize - trackHeight) / 2
  41. // Draw track.
  42. trackCorner := trackHeight / 2
  43. trackRect := image.Rectangle{Max: image.Point{
  44. X: trackWidth,
  45. Y: trackHeight,
  46. }}
  47. col := s.Color.Disabled
  48. if s.Switch.Value {
  49. col = s.Color.Enabled
  50. }
  51. if !gtx.Enabled() {
  52. col = f32color.Disabled(col)
  53. }
  54. trackColor := s.Color.Track
  55. t := op.Offset(image.Point{Y: trackOff}).Push(gtx.Ops)
  56. cl := clip.UniformRRect(trackRect, trackCorner).Push(gtx.Ops)
  57. paint.ColorOp{Color: trackColor}.Add(gtx.Ops)
  58. paint.PaintOp{}.Add(gtx.Ops)
  59. cl.Pop()
  60. t.Pop()
  61. // Draw thumb ink.
  62. inkSize := gtx.Dp(44)
  63. rr := inkSize / 2
  64. inkOff := image.Point{
  65. X: trackWidth/2 - rr,
  66. Y: -rr + trackHeight/2 + trackOff,
  67. }
  68. t = op.Offset(inkOff).Push(gtx.Ops)
  69. gtx.Constraints.Min = image.Pt(inkSize, inkSize)
  70. cl = clip.UniformRRect(image.Rectangle{Max: gtx.Constraints.Min}, rr).Push(gtx.Ops)
  71. for _, p := range s.Switch.History() {
  72. drawInk(gtx, p)
  73. }
  74. cl.Pop()
  75. t.Pop()
  76. // Compute thumb offset.
  77. if s.Switch.Value {
  78. xoff := trackWidth - thumbSize
  79. defer op.Offset(image.Point{X: xoff}).Push(gtx.Ops).Pop()
  80. }
  81. thumbRadius := thumbSize / 2
  82. circle := func(x, y, r int) clip.Op {
  83. b := image.Rectangle{
  84. Min: image.Pt(x-r, y-r),
  85. Max: image.Pt(x+r, y+r),
  86. }
  87. return clip.Ellipse(b).Op(gtx.Ops)
  88. }
  89. // Draw hover.
  90. if s.Switch.Hovered() || gtx.Focused(s.Switch) {
  91. r := thumbRadius * 10 / 17
  92. background := f32color.MulAlpha(s.Color.Enabled, 70)
  93. paint.FillShape(gtx.Ops, background, circle(thumbRadius, thumbRadius, r))
  94. }
  95. // Draw thumb shadow, a translucent disc slightly larger than the
  96. // thumb itself.
  97. // Center shadow horizontally and slightly adjust its Y.
  98. paint.FillShape(gtx.Ops, argb(0x55000000), circle(thumbRadius, thumbRadius+gtx.Dp(.25), thumbRadius+1))
  99. // Draw thumb.
  100. paint.FillShape(gtx.Ops, col, circle(thumbRadius, thumbRadius, thumbRadius))
  101. // Set up click area.
  102. clickSize := gtx.Dp(40)
  103. clickOff := image.Point{
  104. X: (thumbSize - clickSize) / 2,
  105. Y: (trackHeight-clickSize)/2 + trackOff,
  106. }
  107. defer op.Offset(clickOff).Push(gtx.Ops).Pop()
  108. sz := image.Pt(clickSize, clickSize)
  109. defer clip.Ellipse(image.Rectangle{Max: sz}).Push(gtx.Ops).Pop()
  110. s.Switch.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
  111. if d := s.Description; d != "" {
  112. semantic.DescriptionOp(d).Add(gtx.Ops)
  113. }
  114. semantic.Switch.Add(gtx.Ops)
  115. return layout.Dimensions{Size: sz}
  116. })
  117. dims := image.Point{X: trackWidth, Y: thumbSize}
  118. return layout.Dimensions{Size: dims}
  119. }