decorations.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. package material
  2. import (
  3. "image"
  4. "image/color"
  5. "gioui.org/f32"
  6. "gioui.org/io/semantic"
  7. "gioui.org/io/system"
  8. "gioui.org/layout"
  9. "gioui.org/op"
  10. "gioui.org/op/clip"
  11. "gioui.org/op/paint"
  12. "gioui.org/unit"
  13. "gioui.org/widget"
  14. )
  15. // DecorationsStyle provides the style elements for Decorations.
  16. type DecorationsStyle struct {
  17. Decorations *widget.Decorations
  18. Actions system.Action
  19. Title LabelStyle
  20. Background color.NRGBA
  21. Foreground color.NRGBA
  22. }
  23. // Decorations returns the style to decorate a window.
  24. func Decorations(th *Theme, deco *widget.Decorations, actions system.Action, title string) DecorationsStyle {
  25. titleStyle := Body1(th, title)
  26. titleStyle.Color = th.Palette.ContrastFg
  27. return DecorationsStyle{
  28. Decorations: deco,
  29. Actions: actions,
  30. Title: titleStyle,
  31. Background: th.Palette.ContrastBg,
  32. Foreground: th.Palette.ContrastFg,
  33. }
  34. }
  35. // Layout a window with its title and action buttons.
  36. func (d DecorationsStyle) Layout(gtx layout.Context) layout.Dimensions {
  37. rec := op.Record(gtx.Ops)
  38. dims := d.layoutDecorations(gtx)
  39. decos := rec.Stop()
  40. r := clip.Rect{Max: dims.Size}
  41. paint.FillShape(gtx.Ops, d.Background, r.Op())
  42. decos.Add(gtx.Ops)
  43. return dims
  44. }
  45. func (d DecorationsStyle) layoutDecorations(gtx layout.Context) layout.Dimensions {
  46. gtx.Constraints.Min.Y = 0
  47. inset := layout.UniformInset(10)
  48. return layout.Flex{
  49. Axis: layout.Horizontal,
  50. Alignment: layout.Middle,
  51. Spacing: layout.SpaceBetween,
  52. }.Layout(gtx,
  53. layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
  54. return d.Decorations.LayoutMove(gtx, func(gtx layout.Context) layout.Dimensions {
  55. return inset.Layout(gtx, d.Title.Layout)
  56. })
  57. }),
  58. layout.Rigid(func(gtx layout.Context) layout.Dimensions {
  59. // Remove the unmaximize action as it is taken care of by maximize.
  60. actions := d.Actions &^ system.ActionUnmaximize
  61. var size image.Point
  62. for a := system.Action(1); actions != 0; a <<= 1 {
  63. if a&actions == 0 {
  64. continue
  65. }
  66. actions &^= a
  67. var w layout.Widget
  68. switch a {
  69. case system.ActionMinimize:
  70. w = minimizeWindow
  71. case system.ActionMaximize:
  72. if d.Decorations.Maximized {
  73. w = maximizedWindow
  74. } else {
  75. w = maximizeWindow
  76. }
  77. case system.ActionClose:
  78. w = closeWindow
  79. default:
  80. continue
  81. }
  82. cl := d.Decorations.Clickable(a)
  83. dims := cl.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
  84. semantic.Button.Add(gtx.Ops)
  85. return layout.Background{}.Layout(gtx,
  86. func(gtx layout.Context) layout.Dimensions {
  87. defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
  88. for _, c := range cl.History() {
  89. drawInk(gtx, c)
  90. }
  91. return layout.Dimensions{Size: gtx.Constraints.Min}
  92. },
  93. func(gtx layout.Context) layout.Dimensions {
  94. paint.ColorOp{Color: d.Foreground}.Add(gtx.Ops)
  95. return inset.Layout(gtx, w)
  96. },
  97. )
  98. })
  99. size.X += dims.Size.X
  100. if size.Y < dims.Size.Y {
  101. size.Y = dims.Size.Y
  102. }
  103. op.Offset(image.Pt(dims.Size.X, 0)).Add(gtx.Ops)
  104. }
  105. return layout.Dimensions{Size: size}
  106. }),
  107. )
  108. }
  109. const (
  110. winIconSize = unit.Dp(20)
  111. winIconMargin = unit.Dp(4)
  112. winIconStroke = unit.Dp(2)
  113. )
  114. // minimizeWindows draws a line icon representing the minimize action.
  115. func minimizeWindow(gtx layout.Context) layout.Dimensions {
  116. size := gtx.Dp(winIconSize)
  117. size32 := float32(size)
  118. margin := float32(gtx.Dp(winIconMargin))
  119. width := float32(gtx.Dp(winIconStroke))
  120. var p clip.Path
  121. p.Begin(gtx.Ops)
  122. p.MoveTo(f32.Point{X: margin, Y: size32 - margin})
  123. p.LineTo(f32.Point{X: size32 - 2*margin, Y: size32 - margin})
  124. st := clip.Stroke{
  125. Path: p.End(),
  126. Width: width,
  127. }.Op().Push(gtx.Ops)
  128. paint.PaintOp{}.Add(gtx.Ops)
  129. st.Pop()
  130. return layout.Dimensions{Size: image.Pt(size, size)}
  131. }
  132. // maximizeWindow draws a rectangle representing the maximize action.
  133. func maximizeWindow(gtx layout.Context) layout.Dimensions {
  134. size := gtx.Dp(winIconSize)
  135. margin := gtx.Dp(winIconMargin)
  136. width := gtx.Dp(winIconStroke)
  137. r := clip.RRect{
  138. Rect: image.Rect(margin, margin, size-margin, size-margin),
  139. }
  140. st := clip.Stroke{
  141. Path: r.Path(gtx.Ops),
  142. Width: float32(width),
  143. }.Op().Push(gtx.Ops)
  144. paint.PaintOp{}.Add(gtx.Ops)
  145. st.Pop()
  146. r.Rect.Max = image.Pt(size-margin, 2*margin)
  147. st = clip.Outline{
  148. Path: r.Path(gtx.Ops),
  149. }.Op().Push(gtx.Ops)
  150. paint.PaintOp{}.Add(gtx.Ops)
  151. st.Pop()
  152. return layout.Dimensions{Size: image.Pt(size, size)}
  153. }
  154. // maximizedWindow draws interleaved rectangles representing the un-maximize action.
  155. func maximizedWindow(gtx layout.Context) layout.Dimensions {
  156. size := gtx.Dp(winIconSize)
  157. margin := gtx.Dp(winIconMargin)
  158. width := gtx.Dp(winIconStroke)
  159. r := clip.RRect{
  160. Rect: image.Rect(margin, margin, size-2*margin, size-2*margin),
  161. }
  162. st := clip.Stroke{
  163. Path: r.Path(gtx.Ops),
  164. Width: float32(width),
  165. }.Op().Push(gtx.Ops)
  166. paint.PaintOp{}.Add(gtx.Ops)
  167. st.Pop()
  168. r = clip.RRect{
  169. Rect: image.Rect(2*margin, 2*margin, size-margin, size-margin),
  170. }
  171. st = clip.Stroke{
  172. Path: r.Path(gtx.Ops),
  173. Width: float32(width),
  174. }.Op().Push(gtx.Ops)
  175. paint.PaintOp{}.Add(gtx.Ops)
  176. st.Pop()
  177. return layout.Dimensions{Size: image.Pt(size, size)}
  178. }
  179. // closeWindow draws a cross representing the close action.
  180. func closeWindow(gtx layout.Context) layout.Dimensions {
  181. size := gtx.Dp(winIconSize)
  182. size32 := float32(size)
  183. margin := float32(gtx.Dp(winIconMargin))
  184. width := float32(gtx.Dp(winIconStroke))
  185. var p clip.Path
  186. p.Begin(gtx.Ops)
  187. p.MoveTo(f32.Point{X: margin, Y: margin})
  188. p.LineTo(f32.Point{X: size32 - margin, Y: size32 - margin})
  189. p.MoveTo(f32.Point{X: size32 - margin, Y: margin})
  190. p.LineTo(f32.Point{X: margin, Y: size32 - margin})
  191. st := clip.Stroke{
  192. Path: p.End(),
  193. Width: width,
  194. }.Op().Push(gtx.Ops)
  195. paint.PaintOp{}.Add(gtx.Ops)
  196. st.Pop()
  197. return layout.Dimensions{Size: image.Pt(size, size)}
  198. }