Skip to content

Add text labels: click to type on the board - #136

Merged
marcosqlbi merged 2 commits into
mainfrom
feature/text-labels
Sep 17, 2026
Merged

marcosqlbi merged 2 commits into
mainfrom
feature/text-labels

Conversation

@marcosqlbi

@marcosqlbi marcosqlbi commented Sep 17, 2026

Copy link
Copy Markdown
Collaborator

Closes #132, the fourth pull request of the 1.6.0 design objects plan
(docs/design-objects.md).

Why

A whiteboard needs a word written on it as often as it needs a drawing of one:
a heading over a sketch, a name beside an arrow, a note that has to be legible
in a recording. Until now the only text a board could hold was a pasted-code
container, with a title bar and a language, which is the wrong object for two
words over a diagram. A label is the other one: click, type, and it is on the
board with the font, size, color, style, and quarter turn you last used.

What it affects

  • The model. FreeTextBoardObject joins BoardObjects.cs as a container:
    its layout rectangle, turned about its centre, is what Bounds is the box of.
    Core.Geometry.RotatedRectangle computes that box, the four turned corners,
    and the inverse the hit test asks, and snaps an angle to a multiple of 45.
    Core never measures text: the window measures and writes the layout size in.
  • The archive. CurrentVersion is 7, and a board asks for it only when it
    holds a label — frames alone are still 6, and neither is still 5. The check is
    a one-line NeedsVersion7 that the shapes and connector pull requests extend.
    Everything a label is written with is optional in the file and normalized on
    read: an uninstalled font becomes Segoe UI, an angle of 50° becomes 45°, and a
    size outside what a label can be read at is brought back into range.
  • Settings. A curated font list and nine sizes in Core.Settings.LabelStyles,
    and AppSettings.Label remembering what the last label was written with. Like
    the pen's color and size it is last-used rather than configured, so it has no
    row in Preferences, and the settings version stays 19.
  • The board. Labels are drawn by BoardSurface in z-order with everything
    else, so they show in an export, a preview, the Explorer thumbnail, and the VS
    Code preview. A selected label is outlined along its turned rectangle rather
    than by the box around it; the corner handle stays on the box, which is where
    the gesture looks for it, and dragging it scales the font.
  • The Insert tab. A new tab between View and Help (Alt+I), with Text as
    the last button of its row. The shapes pull request is building the same tab in
    parallel with eight buttons in front of Text; whichever merges second keeps one
    tab and both sets of buttons. The optional toolbar Insert flyout and the two
    Toolbar preferences belong to that pull request, and this branch does not
    touch the floating toolbar at all.
  • The property bar gains a Font row for a selection of labels: the fonts, the
    sizes, B / I / U, and ↶ ↷ stepping the angle by 45°. Its "applies when" tests
    are now small named statics (HasPrimaryColor, HasThickness, IsLabel) that
    the next pull requests extend by a line each, and the existing Color row now
    reaches a label's text. A pen tap on a combo box opens its list and a tap on a
    button clicks it, as it already did for a toggle.
  • Export. The overlay picture that carries the ink now carries the labels
    too, in the editable deck and in the vector PDF path, so nothing vanishes from
    either. Native rotated text is pull request 6. BoardPartitioner names an area
    after a label's first line when a label is the thing it is about.
  • One fix on the way past. A command row that stays open — Edit, and now
    Insert — offered its access keys to every plain letter, so typing a word with a
    T in it while a label was open picked the Text tool and committed the label
    half-written. Text being typed now answers the keyboard first, for the label
    editor and for the text container alike.

Decisions the plan did not settle

  • AfterInsert is not wired. The preference belongs to the shapes pull
    request and is not on main yet, so the Text tool is sticky: it stays for the
    next click, and Escape puts it down. When the two branches meet, the
    coordinator has one place to hook it — the end of CommitLabelEdit.
  • A size read from a file is clamped to 12–96, as the plan says. A label
    scaled past 96 by the corner handle therefore comes back smaller than it was
    saved; the layout is scaled by the same factor so the text still fills its box.
    If a board should keep a 200-pixel heading instead, the clamp in
    LabelStyles.ClampFontSize is the one line to widen.
  • Growing text keeps the layout's top-left corner, unrotated, which is what
    PowerPoint does, so a label written at an angle grows along its own baseline.
  • The font row applies live while a label is being typed, rather than on
    commit: the change goes into the label and into the editor at once, and the
    whole edit is still one step in the history.
  • The Insert row is sticky, like Edit and Help: picking a tool is the start
    of drawing with it, and the row is where the next one is picked from.
  • Escape clears a selection before it puts the tool down, so with a label
    selected the first Escape deselects and the second returns to Select.
  • The Text glyph is drawn by hand on the same 24-unit grid as the Fluent
    icons around it: Fluent has no plain T at this weight.

To try by hand

Driven with a pen, and with the mouse under both Mouse drawing settings:

  • Insert → Text, then click on the canvas: the caret appears where you clicked,
    and what you type stays where you started it as it grows.
  • Ctrl+Enter commits; a click anywhere else commits and starts the next label,
    because the tool stays. Escape cancels — a new label leaves nothing behind,
    and an edited one comes back as it was. An empty label disappears.
  • F2 on a selected label reopens it; a double-click still centres and fits it.
  • ↶ and ↷ in the property bar at 45° and 90°: the outline follows the turn, the
    label stays where it was, and the editor is turned with it when you reopen it.
  • Change the font, the size, and B / I / U with a label selected and again while
    one is being typed; then insert the next label and check it is written the
    same way, including after a restart.
  • Drag a label by its middle and by its edge; drag the corner handle to scale
    the text; select several labels with a rubber band and recolor them at once.
  • Draw ink across a label, move the label, and check the ink moves with it;
    delete the label and check the ink goes too.
  • Ctrl+Z through all of it, and Ctrl+C on a single label to paste its text.
  • Save, reopen, and export a board with a turned label to a deck and to a PDF:
    the label is in both, in the overlay picture.

With the mouse, in the running application, I inserted a label from the Insert
row, typed it, committed it, turned it 45°, changed its size from the property
bar, dragged it across the board, and undid all of it back to an unmodified
board. The rest of the list above — the pen, the finger, Escape, F2, several
labels at once, linked ink, the clipboard, and a saved and exported board — is
for the morning.

One to watch while you do: choosing a font or a size from the property bar
while the Text tool is still active. Automating that click left a stray empty
label behind once, which a hand could not reproduce and which leaves nothing in
the board either way, but it is worth a look.

🤖 Generated with Claude Code

marcosqlbi and others added 2 commits September 17, 2026 12:42
FreeTextBoardObject with its rotated-rectangle geometry, archive version 7,
the Label defaults, the Text tool on a new Insert tab, the label editor on
the text editor layer, the font row in the property bar, and labels in the
editable export overlay.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A row that stays open - Edit, and now Insert - offered its access keys to
every plain letter, so typing a T into a label picked the Text tool and
committed it. Text being typed answers first now, for the label editor and
for the text container alike, and the label editor is given room for its
border and caret so a line is never clipped while it is written.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@marcosqlbi
marcosqlbi merged commit d99ba41 into main Sep 17, 2026
5 checks passed
@marcosqlbi
marcosqlbi deleted the feature/text-labels branch September 17, 2026 11:00
marcosqlbi added a commit that referenced this pull request Sep 17, 2026
…bar (#137)

Pull request 3 of the 1.6.0 plan in `docs/design-objects.md`, closing
#130. It adds the
eight shapes, the Insert tab they come from, and the two Toolbar
preferences that decide
where the Insert tools live and what happens after one has been drawn.
It builds on #135,
which moved the per-type questions onto the records themselves — this
adds a record and a
geometry and touches almost none of the window's switches.

## Why it is shaped this way

**A shape is taken hold of by its outline, never by its interior**
(decision 11). That is
the whole reason a shape can be a container: ink drawn inside one links
to it and travels
with it, and a tap in the middle still reaches whatever is drawn there.
The band is the
same eight screen pixels a frame's edge uses.

**One description of the outline serves everybody.**
`Core.Geometry.ShapeGeometry` returns
a shape's outline as a start point and a list of line and arc steps. The
surface hands the
arcs straight to WPF, so a circle stays a circle at any zoom; the hit
test and the area
tests walk the same outline flattened to a polygon, sixty-four points to
a full circle.
Nothing derives the geometry a second time, which is what stops the
renderer and the hit
test disagreeing about where a shape's edge is — and it is what pull
request 6 will read
to write native PowerPoint and PDF geometry.

**The area asks the outline too.** A rubber band over the empty corner
of an ellipse's box
does not take the ellipse; one that crosses the curve does. A box test
would have been
cheaper and would have felt wrong for exactly the shapes people draw
around things.

**Version 7 is asked for by the board that needs it.** `NeedsVersion7`
is a one-line
predicate the label and connector work extends; a board with only frames
still writes as 6
and one with neither as 5, so a board keeps opening in the release that
can read it.

**The two "applies when" tests in the property bar are named statics.**
`HasPrimaryColor`
and `HasThickness` are each one line for the text and connector pull
requests to extend,
rather than a predicate repeated at every row. Applying a color, a
thickness, or a fill
replaces every selected stroke and shape as one step and becomes what
the next shape is
drawn with.

**Free resize is the one thing a shape does differently.** Its corner
handle takes the
width and the height the pointer is at, and Shift keeps the aspect — the
opposite way round
from a picture, which is aspect-locked. It applies only when the
selection is exactly one
shape; a set still scales as a set.

**The editable export keeps the shapes visible.** The overlay picture
now carries the ink
and the shapes, and the vector PDF path — which draws the ink itself —
still gets an
overlay for the shapes alone. Native shapes are pull request 6; until
then nothing
disappears from a deck.

## The toolbar's width

Measured from the XAML for the floating palette, at the default where
the Eraser is not on
the bar. With the preference off both new controls are `Collapsed` and
contribute nothing,
so the width is the 1.5.2 width to the pixel.

| Layout | Insert and Lasso on the toolbar off | on |
| --- | --- | --- |
| Compact (Chevron or Icons beside the size chips) | **140** | **202** |
| Dual palette (the default) | **286** | **310** |

The compact pair grows by the Insert button (42) and the Select chevron
(20); the dual pair
by 24, which is what the Insert button adds to the shared color column
that sets the
palette's width. Both pairs were confirmed against a running copy: the
measured deltas are
62 and 24 exactly. With the Eraser on the bar — that is, with finger or
mouse drawing on —
the compact pair is 182 and 244.

## Decisions the plan did not settle

- **The shape defaults start at the pen's defaults, not at the live
pen.** Decision 5 says
a shape's first outline color and thickness are "the current pen's".
Reading the pen's
setting at the moment a shape is drawn would make the same gesture
produce different
shapes depending on which color was last chosen for ink, so
`ShapeSettings` instead
starts at `InkPalettes.DefaultPen` (vermillion, 4) and is last-used from
then on, which
  is what the settings table asks for.
- **Proportional corners and slants.** A rounded rectangle's radius is a
fifth of its short
side, a parallelogram slants by a quarter of its width, a block arrow's
head starts at
three fifths and its shaft is half the height. Proportional rather than
absolute, so a
  shape dragged larger keeps the look it had when it was drawn.
- **The pentagon fills its box** rather than being inscribed in a circle
inside it: a shape
  dragged out of a rectangle should occupy that rectangle.
- **The Insert row is sticky**, which is what #136 settled while this
was in flight:
picking a tool is the start of drawing with it, and the row is where the
next one comes
from. This branch had it closing on a pick and took main's answer
instead.
- **The Select chevron is in the compact layouts only.** The plan names
the Insert button
for the Dual palette layout and says nothing about the chevron there;
the dual palette's
widths are set by a shared column and a chevron would disturb it for one
setting. Lasso
  stays reachable from the Edit row in every layout.
- **The glyphs are drawn by hand**, as the plan allows: Fluent UI System
Icons has no
outline shape set. Each is two nested contours filled even-odd on the
same 24-unit grid,
  including the Insert button's.
- **Alt+I opens the Insert tab.** F, E, V and H were taken.
- **Ctrl+C with one shape selected still copies nothing**, as it does
for a frame today.
Decision 12 names a text container and a picture as the single-object
cases and sends
everything else through the rasterizer only when more than one thing is
selected. Worth a
  look when the native export lands.

## Taking in the text labels (#136)

`origin/main` was merged in after #136 landed, so the two sit side by
side rather than in
two places:

- **One Insert tab and one row**: the eight shapes, a separator, then
Text, which is the
order the flyout shows and the order the connectors will be inserted
into.
- `NeedsVersion7` covers labels and shapes; the archive keeps both cases
and both sets of
  DTO fields.
- The property bar's tests now read: Color for a stroke, a shape, or a
label; Thickness for
a stroke or a shape; Fill for a shape; Font for a label. A color reaches
strokes and
shapes as one step, and labels through their own path — the one that
also changes the box
  being typed into while a label is open.
- The overlay picture carries the ink, the labels, and the shapes, and
the vector path
  still emits one for the design objects alone.
- **After inserting an object now governs the Text tool too.** It was
written for the
shapes, but decision 3 is about every Insert tool, so a committed label
either leaves the
  tool for the next click or hands it back to Select.
- The toolbar's Insert flyout ends with Text, and the Insert button
reads as on for either
  tool.

## Tests

Core harness: the outline of every kind at a known box; the band hitting
the outline and
missing both the middle and the outside, and narrowing with the zoom; an
area taking a
shape by its outline under both rules and refusing an empty corner of
its box; a stroke
touching only a shape linking to it; the archive round trip with and
without a fill, the
three versions, and an unknown kind normalizing to a rounded rectangle;
the round trip and
normalization of both Toolbar settings and the shape defaults.

## To try by hand

- Draw each of the eight from the Insert tab with the pen, and with a
finger under Finger
  drawing. Only a mouse was driven here.
- Shift while dragging one out, for the square.
- Tap without dragging, for the 160 x 120 shape at the tap.
- Turn on **Insert and Lasso on the toolbar** and check the flyout and
the Select chevron
with a pen, which reaches them through the palette's own stylus path
rather than through
  mouse promotion.
- Compare the toolbar's width against 1.5.2 with the preference off, in
both layouts.
- Draw ink inside a shape, move the shape, and check the ink comes with
it.
- Set **After inserting an object** to Return to Select and draw one.
- Export a board with shapes to an editable deck and to a PDF, and check
they are in both.
- A drag that is long in one direction and flat in the other makes a
shape with almost no
height. It is what was dragged and Ctrl+Z removes it, but it is worth a
look before the
  connectors land on top of it.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
marcosqlbi added a commit that referenced this pull request Sep 17, 2026
)

Pull request 7 of the 1.6.0 plan in `docs/design-objects.md`: the
release cut. The five
features are on `main` — the grid (#134), selection by area (#135), text
labels (#136),
shapes and the Insert tab (#137), and connectors (#139) — and none of
them touched the
prose, so this is where a person is told what they got. **#138, the
native editable export,
must merge before this one**: the release notes and the guide both say
shapes and labels
leave as objects in a deck and a PDF, which is true once it lands.

`VersionPrefix` goes 1.5.2 → 1.6.0, so `CHANGELOG.md` gains the matching
section and the
**Release notes** check applies to this pull request.
`./scripts/release-notes.ps1 -Mode
Verify -Version 1.6.0` passes, and `dotnet build Whiteboard.sln -c
Release` is clean.

## What it affects

- **`CHANGELOG.md`** — one `## 1.6.0 - 17 September 2026` section with
three entries, which
is the shape of 1.5.0 (3), 1.5.1 (1) and 1.5.2 (2): the Insert tab with
shapes,
connectors and text, selection by area, and the grid. The toolbar
preference is one
sentence inside the first entry rather than a fourth entry, because it
is where somebody
meets it. The first entry also carries the one thing a person deciding
whether to upgrade
has to know: a board holding any of the new objects does not open in
1.5.x.
- **`README.md`** — three bullets in *Included in the application*,
seven rows in
*Controls*, the Preferences lists in the bullet and in the table, and
the containers
paragraph, which now says shapes and labels are containers and that a
shape is picked up
  by its outline.
- **`site/guide.html`** — a new *Shapes, connectors, and text* section
after *Text and
code*, a paragraph on area selection under *Containers*, one sentence on
the grid under
  *Navigation*, and the Insert tab named in the command-strip section.
- **`site/shortcuts.html`** — Insert, Lasso and Grid rows, the rubber
band and Ctrl-to-add,
and the Alt-mnemonic row extended with Alt+I, Edit L, View G, and Insert
T.
- **`site/index.html`** — two feature tiles, shapes/connectors/text and
select by area.
- **`docs/decisions.md`** — decision 31, why design objects are ordinary
board objects,
why a shape is taken by its outline, and why the grid is an application
preference. The
rest stays in `docs/design-objects.md`, whose status line is now shipped
in 1.6.0.
- **`TODO.md`** — the current version, which still said 1.3.0, and what
1.6.0 left out.

## Readings taken, since nobody could be asked

- **`site/compare.html` is untouched.** It has no row saying Whiteboard
lacks shapes, text,
a grid or multi-select; the nearest, *Ink and objects*, is a statement
about strokes
travelling with a container and is still true. The brief said to change
such cells and
  nothing else, so nothing else changed.
- **`site/faq.html` is untouched.** No answer on it became wrong.
- ***Architecture* in `README.md` is untouched.** The `Core` bullet
names no types — it
says "world geometry, camera math, retained board objects, commands, hit
testing" — so
there was no list for `ShapeGeometry`, `ConnectorGeometry` and
`RotatedRectangle` to join,
  and the brief said to leave it alone in that case.
- **Escape is an edited row, not a new one**, in both the README table
and the shortcut
  list: Escape already had one, and a second would contradict it.
- **`site/changelog.html` is untouched**, as it is generated from
`CHANGELOG.md` at
  deployment.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create text

1 participant