From 1558a989365fe61683eaf3793826c197c20f6ddd Mon Sep 17 00:00:00 2001 From: tannevaled Date: Thu, 22 Oct 2026 19:17:07 +0200 Subject: [PATCH] feat: Ribbon.TabRect and Ribbon.ControlRect Where a tab's label and a shown control are, in surface coordinates, for a test harness that drives a page with synthetic events (the go-slides playground's browser tests click them). Co-Authored-By: Claude Sonnet 5.5 --- ribbon.go | 29 +++++++++++++++++++++++++++++ ribbon_rect_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 ribbon_rect_test.go diff --git a/ribbon.go b/ribbon.go index 3e2b38a..9bffcc2 100644 --- a/ribbon.go +++ b/ribbon.go @@ -621,3 +621,32 @@ func (r *Ribbon) bodyRect() Rect { b := r.Bounds() return Rect{X: b.X, Y: b.Y + m.tabH + 1, W: b.W, H: m.bodyH + m.labelH + 1} } + +// TabRect is where tab i's label is, in surface coordinates; false when there is +// no such tab. A test harness that drives the page with synthetic events clicks +// there. +func (r *Ribbon) TabRect(i int) (Rect, bool) { + tabs, _ := r.tabRects(r.metrics()) + if i < 0 || i >= len(tabs) { + return Rect{}, false + } + return tabs[i], true +} + +// ControlRect is where the control (or folded group) labelled label is, in surface +// coordinates, among those the ribbon shows now; false when it is not shown (on +// another tab, hidden, or folded into a group's pop-up). +func (r *Ribbon) ControlRect(label string) (Rect, bool) { + for _, c := range r.cells() { + l := "" + if c.form == formPopup { + l = c.grp.Label + } else { + l = c.ctrl.label() + } + if l == label { + return c.rect, true + } + } + return Rect{}, false +} diff --git a/ribbon_rect_test.go b/ribbon_rect_test.go new file mode 100644 index 0000000..6674de9 --- /dev/null +++ b/ribbon_rect_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2026 the go-widgets/toolkit authors. All rights reserved. +// Use of this source code is governed by a BSD-3-Clause license that can be +// found in the LICENSE file at the root of this repository. + +package toolkit + +import "testing" + +func TestRibbonTabRectAndControlRect(t *testing.T) { + r, run := rtRibbon(2000) + tab, ok := r.TabRect(1) + if !ok || !tab.Contains(tab.X+1, tab.Y+1) { + t.Fatalf("TabRect(1) = %v %v", tab, ok) + } + if _, ok := r.TabRect(-1); ok { + t.Error("a negative tab has no rect") + } + if _, ok := r.TabRect(99); ok { + t.Error("a tab past the end has no rect") + } + // A control's rect is where a click runs it. + rc, ok := r.ControlRect("Undo") + if !ok { + t.Fatal("Undo is shown") + } + x, y := rtCenter(rc) + rtClick(r, x, y) + if len(run.ids) != 1 || run.ids[0] != "undo" { + t.Errorf("a click at ControlRect ran %v", run.ids) + } + // Not shown: another tab's control, and a folded group's own control. + if _, ok := r.ControlRect("Image"); ok { + t.Error("Image is on the Insert tab") + } + small, _ := rtRibbon(360) + if _, ok := small.ControlRect("Kinds"); ok { + t.Error("Kinds is folded into the Theme group's pop-up at 360px") + } + if rc, ok := small.ControlRect("Theme"); !ok || rc.W == 0 { + t.Error("the folded group's button is shown under its label") + } +}