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