~whereswaldon/gio-x

36194a9b166cf407257c2c777b7fc788ffe1549e — Chris Waldon 5 months ago 854264d debug
debug: display constraint/size values as text

This commit adds an overlay displaying the numerical values of the constraints.

Signed-off-by: Chris Waldon <christopher.waldon.dev@gmail.com>
1 files changed, 159 insertions(+), 14 deletions(-)

M debug/debug.go
M debug/debug.go => debug/debug.go +159 -14
@@ 2,11 2,16 @@
package debug

import (
	"fmt"
	"image"
	"image/color"
	"strconv"
	"strings"
	"sync"

	"gioui.org/f32"
	"gioui.org/font"
	"gioui.org/font/opentype"
	"gioui.org/gesture"
	"gioui.org/io/event"
	"gioui.org/io/pointer"


@@ 14,18 19,27 @@ import (
	"gioui.org/op"
	"gioui.org/op/clip"
	"gioui.org/op/paint"
	"gioui.org/text"
	"gioui.org/unit"
	"gioui.org/widget"
	"golang.org/x/image/font/gofont/gomono"
)

var (
	mapLock  sync.RWMutex
	stateMap map[any]*ConstraintEditor
	mapLock    sync.RWMutex
	stateMap   map[any]*ConstraintEditor
	shaperLock sync.Mutex
	shaper     *text.Shaper
)

func init() {
	mapLock.Lock()
	defer mapLock.Unlock()
	stateMap = make(map[any]*ConstraintEditor)
	shaperLock.Lock()
	defer shaperLock.Unlock()
	coll, _ := opentype.ParseCollection(gomono.TTF)
	shaper = text.NewShaper(text.NoSystemFonts(), text.WithCollection(coll))
}

func getTag(tag any) *ConstraintEditor {


@@ 112,6 126,18 @@ func (d *dragBox) Reset() {
type ConstraintEditor struct {
	maxBox dragBox
	minBox dragBox

	// LineWidth is the width of debug overlay lines like those outlining the constraints
	// and widget size.
	LineWidth unit.Dp
	// MinSize is the side length of the smallest the editor is allowed to go. If the editor
	// makes the constraints smaller than this, it will reset itself. If the constraints are
	// already smaller than this, the editor will not display itself.
	MinSize unit.Dp
	// TextSize determines the size of the on-screen contextual help text.
	TextSize unit.Sp

	MinColor, MaxColor, SizeColor, SurfaceColor color.NRGBA
}

func outline(ops *op.Ops, width int, area image.Point) clip.PathSpec {


@@ 141,29 167,146 @@ func (c *ConstraintEditor) Wrap(w layout.Widget) layout.Widget {
	}
}

// rgb converts a string of the form "#abcdef" or "#abcdef01" into an NRGBA color.
// If the hex does not provide alpha, max alpha is assumed.
func rgb(s string) color.NRGBA {
	s = strings.TrimPrefix(s, "#")
	if len(s)%2 != 0 || len(s) > 8 {
		panic(fmt.Errorf("invalid color #%s", s))
	}
	r, err := strconv.ParseUint(s[:2], 16, 8)
	if err != nil {
		panic(err)
	}
	g, err := strconv.ParseUint(s[2:4], 16, 8)
	if err != nil {
		panic(err)
	}
	b, err := strconv.ParseUint(s[4:6], 16, 8)
	if err != nil {
		panic(err)
	}
	a := uint64(255)
	if len(s) > 6 {
		a, err = strconv.ParseUint(s[6:8], 16, 8)
		if err != nil {
			panic(err)
		}
	}
	return color.NRGBA{R: uint8(r), G: uint8(g), B: uint8(b), A: uint8(a)}
}

func (c *ConstraintEditor) init() {
	if c.LineWidth == 0 {
		c.LineWidth = 1
	}
	if c.MinSize == 0 {
		c.MinSize = 5
	}
	if c.TextSize == 0 {
		c.TextSize = 12
	}
	if c.MinColor == (color.NRGBA{}) {
		c.MinColor = rgb("#c077f9")
	}
	if c.MaxColor == (color.NRGBA{}) {
		c.MaxColor = rgb("#f95f98")
	}
	if c.SizeColor == (color.NRGBA{}) {
		c.SizeColor = rgb("#1fbd51")
	}
	if c.SurfaceColor == (color.NRGBA{}) {
		// TODO: find better color for this.
		c.SurfaceColor = color.NRGBA{R: 1, G: 1, B: 1, A: 0}
	}
}

func record(gtx layout.Context, w layout.Widget) (op.CallOp, layout.Dimensions) {
	macro := op.Record(gtx.Ops)
	dims := w(gtx)
	return macro.Stop(), dims
}

func colorMaterial(ops *op.Ops, col color.NRGBA) op.CallOp {
	macro := op.Record(ops)
	paint.ColorOp{Color: col}.Add(ops)
	return macro.Stop()
}

func labelOp(gtx layout.Context, sz unit.Sp, col color.NRGBA, str string) (op.CallOp, layout.Dimensions) {
	gtx.Constraints.Min = image.Point{}
	return record(gtx, func(gtx layout.Context) layout.Dimensions {
		shaperLock.Lock()
		defer shaperLock.Unlock()
		return widget.Label{
			MaxLines: 1,
		}.Layout(gtx, shaper, font.Font{}, sz, str, colorMaterial(gtx.Ops, col))
	})
}

func recorded(call op.CallOp, dims layout.Dimensions) layout.Widget {
	return func(gtx layout.Context) layout.Dimensions {
		call.Add(gtx.Ops)
		return dims
	}
}

// Layout the constraint editor to debug the layout of w.
func (c *ConstraintEditor) Layout(gtx layout.Context, w layout.Widget) layout.Dimensions {
	c.init()
	originalConstraints := gtx.Constraints
	gtx.Constraints = gtx.Constraints.SubMax(c.maxBox.CurrentDrag())
	gtx.Constraints = gtx.Constraints.AddMin(image.Point{}.Sub(c.minBox.CurrentDrag()))
	if gtx.Constraints.Max.X < gtx.Dp(5) || gtx.Constraints.Max.Y < gtx.Dp(5) {
	if minSize := gtx.Dp(c.MinSize); gtx.Constraints.Max.X < minSize || gtx.Constraints.Max.Y < minSize {
		gtx.Constraints = originalConstraints
		c.maxBox.Reset()
		c.minBox.Reset()
	}
	dims := w(gtx)
	oneDp := gtx.Dp(1)
	minSpec := outline(gtx.Ops, oneDp, gtx.Constraints.Min)
	maxSpec := outline(gtx.Ops, oneDp, gtx.Constraints.Max)
	sizeSpec := outline(gtx.Ops, oneDp, dims.Size)
	rec := op.Record(gtx.Ops)
	lineWidth := gtx.Dp(c.LineWidth)
	minSpec := outline(gtx.Ops, lineWidth, gtx.Constraints.Min)
	maxSpec := outline(gtx.Ops, lineWidth, gtx.Constraints.Max)
	sizeSpec := outline(gtx.Ops, lineWidth, dims.Size)
	// Display the static widget size.
	paint.FillShape(gtx.Ops, color.NRGBA{G: 255, A: 150}, clip.Outline{Path: sizeSpec}.Op())
	paint.FillShape(gtx.Ops, color.NRGBA{G: 255, A: 50}, clip.Rect{Max: dims.Size}.Op())
	paint.FillShape(gtx.Ops, c.SizeColor, clip.Outline{Path: sizeSpec}.Op())
	sizeFill := c.SizeColor
	sizeFill.A = 50
	paint.FillShape(gtx.Ops, sizeFill, clip.Rect{Max: dims.Size}.Op())

	// Display textual overlays.
	minText := fmt.Sprintf("(%d,%d) Min", gtx.Constraints.Min.X, gtx.Constraints.Min.Y)
	minOp, minDims := labelOp(gtx, c.TextSize, c.MinColor, minText)
	maxText := fmt.Sprintf("(%d,%d) Max", gtx.Constraints.Max.X, gtx.Constraints.Max.Y)
	maxOp, maxDims := labelOp(gtx, c.TextSize, c.MaxColor, maxText)
	szText := fmt.Sprintf("(%d,%d) Size", dims.Size.X, dims.Size.Y)
	szOp, szDims := labelOp(gtx, c.TextSize, c.SizeColor, szText)
	rec := op.Record(gtx.Ops)

	flexAxis := layout.Vertical
	if minDims.Size.Y+maxDims.Size.Y+szDims.Size.Y > gtx.Constraints.Max.Y {
		flexAxis = layout.Horizontal
	}
	layout.Stack{}.Layout(gtx,
		layout.Expanded(func(gtx layout.Context) layout.Dimensions {
			paint.FillShape(gtx.Ops, c.SurfaceColor, clip.Rect{Max: gtx.Constraints.Min}.Op())
			return layout.Dimensions{Size: gtx.Constraints.Min}
		}),
		layout.Stacked(func(gtx layout.Context) layout.Dimensions {
			return layout.Flex{
				Axis: flexAxis,
			}.Layout(gtx,
				layout.Rigid(recorded(minOp, minDims)),
				layout.Rigid(recorded(maxOp, maxDims)),
				layout.Rigid(recorded(szOp, szDims)),
			)
		}),
	)
	// Display the interactive max constraint controls.
	paint.FillShape(gtx.Ops, color.NRGBA{R: 255, A: 150}, clip.Outline{Path: maxSpec}.Op())
	paint.FillShape(gtx.Ops, c.MaxColor, clip.Outline{Path: maxSpec}.Op())
	if c.maxBox.Active(gtx.Queue) {
		paint.FillShape(gtx.Ops, color.NRGBA{R: 255, A: 50}, clip.Rect{Max: gtx.Constraints.Max}.Op())
		maxFill := c.MaxColor
		maxFill.A = 50
		paint.FillShape(gtx.Ops, maxFill, clip.Rect{Max: gtx.Constraints.Max}.Op())
	}

	maxDragArea := clip.Rect{Max: gtx.Constraints.Max}.Push(gtx.Ops)


@@ 171,9 314,11 @@ func (c *ConstraintEditor) Layout(gtx layout.Context, w layout.Widget) layout.Di
	maxDragArea.Pop()

	// Display the interactive min constraint controls.
	paint.FillShape(gtx.Ops, color.NRGBA{B: 255, A: 150}, clip.Outline{Path: minSpec}.Op())
	paint.FillShape(gtx.Ops, c.MinColor, clip.Outline{Path: minSpec}.Op())
	if c.minBox.Active(gtx.Queue) {
		paint.FillShape(gtx.Ops, color.NRGBA{B: 255, A: 50}, clip.Rect{Max: gtx.Constraints.Min}.Op())
		minFill := c.MinColor
		minFill.A = 50
		paint.FillShape(gtx.Ops, minFill, clip.Rect{Max: gtx.Constraints.Min}.Op())
	}

	minDragArea := clip.Rect{Max: gtx.Constraints.Min}.Push(gtx.Ops)