mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 15:45:38 +00:00
6722c7960a
As suggested by ~egonelbre, Decorations should not be the source of truth for the windows state, because external gestures may also change state. This breaking change removes Decorations.Perform and exposes Maximized as a bool which is the user's responsibility to set. Fixes: https://todo.sr.ht/~eliasnaur/gio/600 Signed-off-by: Elias Naur <mail@eliasnaur.com>
210 lines
5.7 KiB
Go
210 lines
5.7 KiB
Go
package material
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
|
|
"gioui.org/f32"
|
|
"gioui.org/io/semantic"
|
|
"gioui.org/io/system"
|
|
"gioui.org/layout"
|
|
"gioui.org/op"
|
|
"gioui.org/op/clip"
|
|
"gioui.org/op/paint"
|
|
"gioui.org/unit"
|
|
"gioui.org/widget"
|
|
)
|
|
|
|
// DecorationsStyle provides the style elements for Decorations.
|
|
type DecorationsStyle struct {
|
|
Decorations *widget.Decorations
|
|
Actions system.Action
|
|
Title LabelStyle
|
|
Background color.NRGBA
|
|
Foreground color.NRGBA
|
|
}
|
|
|
|
// Decorations returns the style to decorate a window.
|
|
func Decorations(th *Theme, deco *widget.Decorations, actions system.Action, title string) DecorationsStyle {
|
|
titleStyle := Body1(th, title)
|
|
titleStyle.Color = th.Palette.ContrastFg
|
|
return DecorationsStyle{
|
|
Decorations: deco,
|
|
Actions: actions,
|
|
Title: titleStyle,
|
|
Background: th.Palette.ContrastBg,
|
|
Foreground: th.Palette.ContrastFg,
|
|
}
|
|
}
|
|
|
|
// Layout a window with its title and action buttons.
|
|
func (d DecorationsStyle) Layout(gtx layout.Context) layout.Dimensions {
|
|
rec := op.Record(gtx.Ops)
|
|
dims := d.layoutDecorations(gtx)
|
|
decos := rec.Stop()
|
|
r := clip.Rect{Max: dims.Size}
|
|
paint.FillShape(gtx.Ops, d.Background, r.Op())
|
|
decos.Add(gtx.Ops)
|
|
return dims
|
|
}
|
|
|
|
func (d DecorationsStyle) layoutDecorations(gtx layout.Context) layout.Dimensions {
|
|
gtx.Constraints.Min.Y = 0
|
|
inset := layout.UniformInset(10)
|
|
return layout.Flex{
|
|
Axis: layout.Horizontal,
|
|
Alignment: layout.Middle,
|
|
Spacing: layout.SpaceBetween,
|
|
}.Layout(gtx,
|
|
layout.Flexed(1, func(gtx layout.Context) layout.Dimensions {
|
|
return d.Decorations.LayoutMove(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
return inset.Layout(gtx, d.Title.Layout)
|
|
})
|
|
}),
|
|
layout.Rigid(func(gtx layout.Context) layout.Dimensions {
|
|
// Remove the unmaximize action as it is taken care of by maximize.
|
|
actions := d.Actions &^ system.ActionUnmaximize
|
|
var size image.Point
|
|
for a := system.Action(1); actions != 0; a <<= 1 {
|
|
if a&actions == 0 {
|
|
continue
|
|
}
|
|
actions &^= a
|
|
var w layout.Widget
|
|
switch a {
|
|
case system.ActionMinimize:
|
|
w = minimizeWindow
|
|
case system.ActionMaximize:
|
|
if d.Decorations.Maximized {
|
|
w = maximizedWindow
|
|
} else {
|
|
w = maximizeWindow
|
|
}
|
|
case system.ActionClose:
|
|
w = closeWindow
|
|
default:
|
|
continue
|
|
}
|
|
cl := d.Decorations.Clickable(a)
|
|
dims := cl.Layout(gtx, func(gtx layout.Context) layout.Dimensions {
|
|
semantic.Button.Add(gtx.Ops)
|
|
return layout.Background{}.Layout(gtx,
|
|
func(gtx layout.Context) layout.Dimensions {
|
|
defer clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops).Pop()
|
|
for _, c := range cl.History() {
|
|
drawInk(gtx, c)
|
|
}
|
|
return layout.Dimensions{Size: gtx.Constraints.Min}
|
|
},
|
|
func(gtx layout.Context) layout.Dimensions {
|
|
paint.ColorOp{Color: d.Foreground}.Add(gtx.Ops)
|
|
return inset.Layout(gtx, w)
|
|
},
|
|
)
|
|
})
|
|
size.X += dims.Size.X
|
|
if size.Y < dims.Size.Y {
|
|
size.Y = dims.Size.Y
|
|
}
|
|
op.Offset(image.Pt(dims.Size.X, 0)).Add(gtx.Ops)
|
|
}
|
|
return layout.Dimensions{Size: size}
|
|
}),
|
|
)
|
|
}
|
|
|
|
const (
|
|
winIconSize = unit.Dp(20)
|
|
winIconMargin = unit.Dp(4)
|
|
winIconStroke = unit.Dp(2)
|
|
)
|
|
|
|
// minimizeWindows draws a line icon representing the minimize action.
|
|
func minimizeWindow(gtx layout.Context) layout.Dimensions {
|
|
size := gtx.Dp(winIconSize)
|
|
size32 := float32(size)
|
|
margin := float32(gtx.Dp(winIconMargin))
|
|
width := float32(gtx.Dp(winIconStroke))
|
|
var p clip.Path
|
|
p.Begin(gtx.Ops)
|
|
p.MoveTo(f32.Point{X: margin, Y: size32 - margin})
|
|
p.LineTo(f32.Point{X: size32 - 2*margin, Y: size32 - margin})
|
|
st := clip.Stroke{
|
|
Path: p.End(),
|
|
Width: width,
|
|
}.Op().Push(gtx.Ops)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
st.Pop()
|
|
return layout.Dimensions{Size: image.Pt(size, size)}
|
|
}
|
|
|
|
// maximizeWindow draws a rectangle representing the maximize action.
|
|
func maximizeWindow(gtx layout.Context) layout.Dimensions {
|
|
size := gtx.Dp(winIconSize)
|
|
margin := gtx.Dp(winIconMargin)
|
|
width := gtx.Dp(winIconStroke)
|
|
r := clip.RRect{
|
|
Rect: image.Rect(margin, margin, size-margin, size-margin),
|
|
}
|
|
st := clip.Stroke{
|
|
Path: r.Path(gtx.Ops),
|
|
Width: float32(width),
|
|
}.Op().Push(gtx.Ops)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
st.Pop()
|
|
r.Rect.Max = image.Pt(size-margin, 2*margin)
|
|
st = clip.Outline{
|
|
Path: r.Path(gtx.Ops),
|
|
}.Op().Push(gtx.Ops)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
st.Pop()
|
|
return layout.Dimensions{Size: image.Pt(size, size)}
|
|
}
|
|
|
|
// maximizedWindow draws interleaved rectangles representing the un-maximize action.
|
|
func maximizedWindow(gtx layout.Context) layout.Dimensions {
|
|
size := gtx.Dp(winIconSize)
|
|
margin := gtx.Dp(winIconMargin)
|
|
width := gtx.Dp(winIconStroke)
|
|
r := clip.RRect{
|
|
Rect: image.Rect(margin, margin, size-2*margin, size-2*margin),
|
|
}
|
|
st := clip.Stroke{
|
|
Path: r.Path(gtx.Ops),
|
|
Width: float32(width),
|
|
}.Op().Push(gtx.Ops)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
st.Pop()
|
|
r = clip.RRect{
|
|
Rect: image.Rect(2*margin, 2*margin, size-margin, size-margin),
|
|
}
|
|
st = clip.Stroke{
|
|
Path: r.Path(gtx.Ops),
|
|
Width: float32(width),
|
|
}.Op().Push(gtx.Ops)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
st.Pop()
|
|
return layout.Dimensions{Size: image.Pt(size, size)}
|
|
}
|
|
|
|
// closeWindow draws a cross representing the close action.
|
|
func closeWindow(gtx layout.Context) layout.Dimensions {
|
|
size := gtx.Dp(winIconSize)
|
|
size32 := float32(size)
|
|
margin := float32(gtx.Dp(winIconMargin))
|
|
width := float32(gtx.Dp(winIconStroke))
|
|
var p clip.Path
|
|
p.Begin(gtx.Ops)
|
|
p.MoveTo(f32.Point{X: margin, Y: margin})
|
|
p.LineTo(f32.Point{X: size32 - margin, Y: size32 - margin})
|
|
p.MoveTo(f32.Point{X: size32 - margin, Y: margin})
|
|
p.LineTo(f32.Point{X: margin, Y: size32 - margin})
|
|
st := clip.Stroke{
|
|
Path: p.End(),
|
|
Width: width,
|
|
}.Op().Push(gtx.Ops)
|
|
paint.PaintOp{}.Add(gtx.Ops)
|
|
st.Pop()
|
|
return layout.Dimensions{Size: image.Pt(size, size)}
|
|
}
|