Skip to content

feat: draw table styles, cell backgrounds and borders, and merged cells - #29

Merged
michaelstonis merged 4 commits into
mainfrom
housekeeping/table-styling
Oct 21, 2026
Merged

michaelstonis merged 4 commits into
mainfrom
housekeeping/table-styling

Conversation

@michaelstonis

Copy link
Copy Markdown
Contributor

Summary

The table model had properties that layout and drawing ignored: TableStyle, TableCell.Background, TableCell.Border, and column/row spans. They now take effect. Tables that set none of them look the same as before.

  • Layout: uses the table's border width and cell padding. A merged cell covers the columns and rows it spans, including the grid lines between them. If its content is taller than those rows, the last row it covers grows. A span that runs off the table, or into a slot another cell already covers, is cut short.
  • Drawing: cell backgrounds are filled first. If a cell has no colour of its own and the table has a header row, the first row uses the header colour, or a faint tint if none is set. Each shared border is drawn once, at the wider of the two cells' widths. No border line is drawn through a merged cell, and covered cells aren't drawn.
  • Tapping, the caret, Tab and resizing:
    • Tapping inside the area of a merged cell lands in that cell.
    • The caret is placed relative to the merged cell.
    • Tab and Shift+Tab skip the cells a merge covers.
    • Column and row resizing use the table's border width.
  • New public API:
    • TableLayoutBlock: BorderWidth, BorderColor, per-slot CellInfo, GetCellBounds, GetOwningCell, GetColumnX and GetRowY.
    • TableBlock.GetSpanAnchor.

Default change: TableStyle.BorderColor used to default to black, but tables were always drawn in grey (160,160,160). Its default is now that grey, so existing tables look the same now that the property is used. This is listed under Changed in the CHANGELOG.

Testing

  • 23 new tests in TableStylingTests:
    • layout sizes, with the default style and a custom one;
    • merged-cell geometry, row growth, and spans that are cut short;
    • tapping into covered cells and the caret in a merged cell;
    • exact fill rectangles and colours for the grid, header row, cell background and per-cell border;
    • inner lines and covered-cell text not being drawn;
    • Tab skipping covered cells;
    • HTML export still writing colspan/rowspan.
  • Core.Tests pass (817) and SkiaSharp.Tests pass (29). The SkiaSharp Release build has 0 warnings, and the MAUI shared code compiles cleanly.

Not included

  • Cell borders always use the table's border colour; the model has no per-cell colour.
  • Header rows get a background colour but not bold text.
  • After changing the style of a table that's already on screen, call NotifyChanged. The docs say so.

Checklist

  • CHANGELOG entries under Unreleased → Added and Changed
  • docs/tables.md updated

Generated by Claude Code

…spans

TableStyle, TableCell.Background, TableCell.Border and ColumnSpan/RowSpan
were stored in the model but ignored by layout and rendering: every table
was drawn with a fixed 1-unit grey grid, 4-unit padding and one cell per slot.

- Layout uses TableStyle.BorderWidth/CellPadding and resolves spans: a
  spanning cell is laid out at its combined width, covers the grid lines
  between its slots, and a tall row-spanning cell grows the last row it
  covers. Covered slots are skipped. Spans are clamped to the grid and to
  slots not already claimed by an earlier cell.
- TableLayoutBlock carries BorderWidth, BorderColor and per-slot CellInfo
  (span, effective background, border) with GetCellBounds/GetOwningCell
  helpers shared by the renderer, hit tester and caret renderer.
- The renderer fills cell backgrounds (cell colour, else header-row fill)
  and draws collapsed borders: each edge once, with the wider of the two
  adjacent sides, skipping edges inside a span.
- Hit-testing and the caret resolve covered slots to the spanning cell;
  Tab/Shift+Tab skip covered cells. TableBlock.GetSpanAnchor exposes the
  slot-to-cell mapping. MAUI table resize uses the layout's border width.
- TableStyle.BorderColor now defaults to the grey tables were always drawn
  with, so unstyled tables render as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197tHRWhTiagB1MTsDuPkoa
Copilot AI balanced review requested due to automatic review settings October 21, 2026 16:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Merged-cell interaction bugs, border overflow, and quadratic Tab traversal remain unresolved.

7 open issues
What changed in this PR

Makes existing table styling and merged-cell properties affect the editor’s layout, rendering, and interaction.

Changes:

  • Renders table styles, cell backgrounds, borders, and merged cells.
  • Updates hit-testing, caret placement, Tab navigation, and resize geometry.
  • Adds regression tests and documents behavior and defaults.
File Description
tests/​MintedTextEditor.Core.Tests/​TableStylingTests.cs Adds styling and merged-cell tests.
src/​MintedTextEditor.Maui/​MintedEditorView.cs Uses styled border widths for resizing.
src/​MintedTextEditor.Core/​Rendering/​DocumentRenderer.cs Draws backgrounds, merged content, and shared borders.
src/​MintedTextEditor.Core/​Layout/​TextLayoutEngine.cs Applies styles and calculates merged geometry.
src/​MintedTextEditor.Core/​Layout/​TableLayoutBlock.cs Exposes cell ownership, bounds, and styling.
src/​MintedTextEditor.Core/​Input/​EditorInputController.cs Skips covered cells during Tab navigation.
src/​MintedTextEditor.Core/​Input/​DocumentHitTester.cs Resolves taps to merged-cell anchors.
src/​MintedTextEditor.Core/​Editing/​CaretRenderer.cs Positions the caret relative to merged cells.
src/​MintedTextEditor.Core/​Document/​TableStyle.cs Defines rendering defaults and documents styling.
src/​MintedTextEditor.Core/​Document/​TableCell.cs Clarifies decoration and merge documentation.
src/​MintedTextEditor.Core/​Document/​TableBlock.cs Adds span ownership and clamping.
docs/​tables.md Documents styling and merged-cell behavior.
CHANGELOG.md Records features and the border-color default change.

Review effort: Balanced

# Conflicts:
#	CHANGELOG.md
…ize handles

- Caret positions in a slot covered by a merged cell are mapped to the
  spanning cell (offsets clamped to its content) whenever the controller
  moves the caret, and before edits, so Ctrl+End, arrows, Tab, table
  row/column commands and typing never target the hidden cell.
  GetBlockEndPosition/MoveDocEnd use the cell covering the last slot,
  and IsLastCell treats it as the last cell so Right leaves the table.
- Tab decides whether to leave the table from the terminal slot it
  reached, so a merged last cell exits instead of inserting a tab.
- Tab builds the span ownership grid once instead of on every step.
- Wide outer bottom and right borders are inset so they stay within
  the table's height and width; the bottom line's visibility check uses
  its inset position.
- Column/row resize ignore grid lines hidden inside a merged cell in the
  pointer's row/column (TableLayoutBlock.FindColumnResizeBoundary /
  FindRowResizeBoundary, used by MintedEditorView).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0197tHRWhTiagB1MTsDuPkoa
# Conflicts:
#	CHANGELOG.md
#	src/MintedTextEditor.Core/Rendering/DocumentRenderer.cs
@michaelstonis
michaelstonis merged commit a0afb29 into main Oct 21, 2026
hflexgrig pushed a commit to hflexgrig/MintedTextEditor that referenced this pull request Oct 30, 2026
…e bitmap cache (TheEightBot#9)

- Shape text with HarfBuzz (HarfBuzzSharp) so ZWJ sequences, flags and VS16
  sequences form single glyphs; draw and measure from the same shaped result.
  Shapers are shared process-wide and load only shaping tables (never sbix/
  CBDT/glyf), and shaped runs are cached, so full shaping costs about the same
  as unshaped drawing. SkiaDrawingContext.ShapeAllText = false limits shaping
  to fallback-font segments.
- Read grapheme clusters with StringInfo (UAX TheEightBot#29) so 👩‍💻 is never split and
  TruncateWithEllipsis cannot cut an emoji in half.
- Resolve VS16 / emoji-plane clusters through the color emoji typeface
  (matched via und-Zsye), so ❤️ and 😀 no longer fall back to monochrome fonts.
- Cache SKImage only for immutable bitmaps; mutable bitmaps get a fresh
  snapshot per draw. Bitmaps the MAUI view loads are marked immutable.
- Add MintedTextEditor.SkiaSharp.Tests and run it in CI with Noto Color Emoji.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K5aL8htyk8HUJ3FnsZYDva
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants