From 869f87dd4f332286b06d01a71daef7a97316b15d Mon Sep 17 00:00:00 2001 From: Sander Schobers Date: Sun, 24 May 2020 20:15:40 +0200 Subject: [PATCH] Added a debug overlay. --- ui/context.go | 1 + ui/debug.go | 149 +++++++++++++++++++++++++++++++++++++++++++++++ ui/debug_test.go | 21 +++++++ 3 files changed, 171 insertions(+) create mode 100644 ui/debug.go create mode 100644 ui/debug_test.go diff --git a/ui/context.go b/ui/context.go index 1d916fe..0bef705 100644 --- a/ui/context.go +++ b/ui/context.go @@ -47,6 +47,7 @@ func newContext(r Renderer, s *Style, view Control) *context { fonts: NewFonts(r), textures: NewTextures(r)} ctx.overlays.AddOnTop(uiDefaultTooltipOverlay, ctx.tooltip, false) + ctx.overlays.AddOnTop(DefaultDebugOverlay, NewDebugOverlay(view), false) return ctx } diff --git a/ui/debug.go b/ui/debug.go new file mode 100644 index 0000000..1fb007c --- /dev/null +++ b/ui/debug.go @@ -0,0 +1,149 @@ +package ui + +import ( + "image/color" + "reflect" + + "opslag.de/schobers/geom" + + "opslag.de/schobers/zntg" +) + +const DefaultDebugOverlay = "ui-default-debug" + +func controlChildren(control Control) []Control { + container, ok := control.(*ContainerBase) + if ok { + return container.Children + } + + proxy, ok := control.(*Proxy) + if ok { + return []Control{proxy.Content} + } + return controlChildrenReflect(reflect.ValueOf(control)) +} + +func controlChildrenReflect(control reflect.Value) []Control { + switch control.Kind() { + case reflect.Interface: + return controlChildrenReflect(control.Elem()) + case reflect.Ptr: + return controlChildrenReflect(control.Elem()) + } + if reflect.TypeOf(ContainerBase{}) == control.Type() { + container := control.Interface().(ContainerBase) + return container.Children + } + if reflect.TypeOf(Proxy{}) == control.Type() { + proxy := control.Interface().(Proxy) + return []Control{proxy.Content} + } + if control.NumField() == 0 { + return nil + } + field := control.Type().Field(0) + if !field.Anonymous { + return nil + } + return controlChildrenReflect(control.Field(0)) +} + +func controlName(control Control) string { + typ := reflect.TypeOf(control) + return typ.Elem().Name() +} + +type debugOverlay struct { + ControlBase + + root Control + + hoverNodes *controlNode + + boundsColor color.Color + textColor color.Color + textShadowColor color.Color +} + +func NewDebugOverlay(root Control) *debugOverlay { + return &debugOverlay{ + root: root, + boundsColor: zntg.MustHexColor(`#00FF003F`), + textColor: zntg.MustHexColor(`#FFFFFF3F`), + textShadowColor: zntg.MustHexColor(`#0000003F`), + } +} + +func (o *debugOverlay) renderControl(ctx Context, control Control) { + renderer := ctx.Renderer() + // bounds := control.Bounds() + // renderer.Rectangle(bounds, o.boundsColor, 1) + + var maxY float32 + var renderHoverNode func(pos geom.PointF32, node *controlNode) + renderHoverNode = func(pos geom.PointF32, node *controlNode) { + if node == nil { + return + } + nameTexture, err := ctx.Fonts().TextTexture("debug", color.White, node.Name) + if err != nil { + return + } + defer nameTexture.Destroy() + renderer.FillRectangle(pos.RectRel2D(nameTexture.Width(), nameTexture.Height()), color.Black) + renderer.DrawTexturePoint(nameTexture, pos) + childPos := pos.Add2D(nameTexture.Width()+8, 0) + for _, child := range node.Children { + if childPos.Y == maxY { + childPos.Y = maxY + nameTexture.Height() + } + renderHoverNode(childPos, child) + maxY = childPos.Y + childPos.Y += nameTexture.Height() + 8 + } + } + renderHoverNode(geom.PtF32(4, 4), o.hoverNodes) + + children := controlChildren(control) + for _, child := range children { + o.renderControl(ctx, child) + } +} + +func createHoverNodes(hover geom.PointF32, control Control) *controlNode { + if !hover.In(control.Bounds()) { + return nil + } + node := &controlNode{Name: controlName(control)} + for _, child := range controlChildren(control) { + childNode := createHoverNodes(hover, child) + if childNode != nil { + node.Children = append(node.Children, childNode) + } + } + return node +} + +func (o *debugOverlay) Handle(ctx Context, e Event) bool { + switch e := e.(type) { + case *MouseMoveEvent: + o.hoverNodes = createHoverNodes(e.Pos(), o.root) + case *MouseLeaveEvent: + o.hoverNodes = nil + } + return false +} + +func (o *debugOverlay) Hidden() {} + +func (o *debugOverlay) Render(ctx Context) { + o.renderControl(ctx, o.root) +} + +func (o *debugOverlay) Shown() {} + +type controlNode struct { + Name string + Children []*controlNode +} diff --git a/ui/debug_test.go b/ui/debug_test.go new file mode 100644 index 0000000..b0ac196 --- /dev/null +++ b/ui/debug_test.go @@ -0,0 +1,21 @@ +package ui + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestControlName(t *testing.T) { + assert.Equal(t, "ControlBase", controlName(&ControlBase{})) + assert.Equal(t, "Label", controlName(&Label{})) +} + +func TestControlChildren(t *testing.T) { + assert.Len(t, controlChildren(&ContainerBase{}), 0) + assert.Len(t, controlChildren(&ControlBase{}), 0) + assert.Len(t, controlChildren(&ContainerBase{Children: []Control{nil, nil}}), 2) + assert.Len(t, controlChildren(&StackPanel{ContainerBase: ContainerBase{Children: []Control{nil, nil}}}), 2) + assert.Len(t, controlChildren(&Proxy{Content: &ControlBase{}}), 1) + assert.Len(t, controlChildren(&overflow{Proxy: Proxy{Content: &ControlBase{}}}), 1) +}