mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-01 07:35:40 +00:00
acf5635575
Decorations buttons already use widget.Clickable and draw press ink, but they don't draw a hover/focus background. This makes custom window decorations appear unresponsive when the pointer is over minimize, maximize, or close buttons. Register each button's system action over its clickable area and draw the same hovered background used by other material buttons. This also lets platforms query the button action from the material decorations hit area. Signed-off-by: qiannian <qianniancn@gmail.com>
198 lines
5.4 KiB
Go
198 lines
5.4 KiB
Go
package material
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
|
|
"gioui.org/f32"
|
|
"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 := Clickable(gtx, cl, func(gtx layout.Context) layout.Dimensions {
|
|
system.ActionInputOp(a).Add(gtx.Ops)
|
|
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)}
|
|
}
|