feat: Merge box-drawing lines into junctions where they meet - #94
Open
FXschwartz wants to merge 1 commit into
Open
feat: Merge box-drawing lines into junctions where they meet#94FXschwartz wants to merge 1 commit into
FXschwartz wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR improves terminal UI rendering by introducing box-drawing “line blending” so overlapping borders/dividers merge into the correct Unicode junction glyphs (e.g. ├ ┤ ┬ ┴ ┼) instead of leaving gaps or overwriting each other.
Changes:
- Added arm/weight-based merging for box-drawing characters via
mergeBoxCharacters()/isMergeableBoxCharacter(). - Extended
TerminalCanvas.drawText()withblendBoxLinesto optionally merge box characters while drawing. - Enabled blending behavior for
Divider/VerticalDivider(including half-arm end caps for negative indents) and forBoxBordercorners; added tests + a demo example.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| test/utils/box_line_merging_test.dart | Unit tests for low-level box-character merging behavior (tees/crosses/weights/mergeability). |
| test/rendering/border_blend_test.dart | Rendering tests validating overlay border corners tee into underlying borders and background doesn’t erase underlying border ring cells. |
| test/components/divider_blend_test.dart | Rendering tests ensuring dividers tee into borders with negative indents and form crosses when intersecting. |
| lib/src/utils/box_line_merging.dart | Implements the arm/weight model and merge logic for Unicode box-drawing characters. |
| lib/src/framework/terminal_canvas.dart | Adds blendBoxLines option to drawText() to merge box-drawing characters with existing buffer content. |
| lib/src/components/divider.dart | Makes dividers blend by default (except ascii), adds half-arm caps for negative indents, and updates component docs. |
| lib/src/components/decorated_box.dart | Adjusts background fill to avoid erasing border ring cells; enables blending for border corner cells. |
| lib/nocterm.dart | Exports the new box line merging utilities. |
| example/box_line_blending_demo.dart | Adds an example showcasing divider/border blending and negative-indent behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| charToUse = chars.horizontal; | ||
| } | ||
| _setCell(canvas, left, top, charToUse, borderStyle); | ||
| _setCell(canvas, left, top, charToUse, borderStyle, blend: true); |
| final leftTopChar = | ||
| !border.left.isNone ? chars.topLeft : chars.horizontal; | ||
| _setCell(canvas, left, top, leftTopChar, borderStyle); | ||
| _setCell(canvas, left, top, leftTopChar, borderStyle, blend: true); |
| final rightTopChar = | ||
| !border.right.isNone ? chars.topRight : chars.horizontal; | ||
| _setCell(canvas, right, top, rightTopChar, borderStyle); | ||
| _setCell(canvas, right, top, rightTopChar, borderStyle, blend: true); |
| charToUse = chars.horizontal; | ||
| } | ||
| _setCell(canvas, left, bottom, charToUse, style); | ||
| _setCell(canvas, left, bottom, charToUse, style, blend: true); |
| final leftBottomChar = | ||
| !border.left.isNone ? chars.bottomLeft : chars.horizontal; | ||
| _setCell(canvas, left, bottom, leftBottomChar, style); | ||
| _setCell(canvas, left, bottom, leftBottomChar, style, blend: true); |
| final rightBottomChar = | ||
| !border.right.isNone ? chars.bottomRight : chars.horizontal; | ||
| _setCell(canvas, right, bottom, rightBottomChar, style); | ||
| _setCell(canvas, right, bottom, rightBottomChar, style, blend: true); |
Comment on lines
+16
to
+20
| /// junctions instead of overwriting them. With a negative [indent] or | ||
| /// [endIndent] it reaches outside its own bounds; those cells paint half-arm | ||
| /// characters (`╶`/`╴`), so an end landing on a box border becomes a tee | ||
| /// (`├`/`┤`). An end landing on a cell that is not a box-drawing character | ||
| /// keeps the half-arm stub, so only reach into cells known to hold borders. |
Comment on lines
+72
to
+76
| /// junctions instead of overwriting them. With a negative [indent] or | ||
| /// [endIndent] it reaches outside its own bounds; those cells paint half-arm | ||
| /// characters (`╷`/`╵`), so an end landing on a box border becomes a tee | ||
| /// (`┬`/`┴`). An end landing on a cell that is not a box-drawing character | ||
| /// keeps the half-arm stub, so only reach into cells known to hold borders. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Borders and dividers previously painted their cells independently, so a divider ending at a border stopped one cell short or overwrote it, the UI showed gaps where ├ ┤ ┬ ┴ ┼ junctions belong.
What this does
Adds arm-based box-character merging (the approach ratatui/tview use): every char in U+2500–257F maps to four arms with weights, drawing one line char onto another ORs the arms and emits the junction glyph.
mergeBoxCharacters()+isMergeableBoxCharacter()inlib/src/utils/box_line_merging.dart, exported fromnocterm.dartTerminalCanvas.drawText(..., blendBoxLines:), the internal mechanism the components paint with, defaults to false so ordinary text never merges (a│inside a log line still overwrites its cell)Divider/VerticalDivider: always merge; negativeindent/endIndentreach into a border row/column and cap with half-arms (╷+─→┬)BoxBorder: corner cells always merge (overlaid panels tee into the border underneath), edges/titles/backgrounds occludeBehavior change
Blending is always on for
Divider,VerticalDivider, andBoxBorder, there is no per-component flag. Existing apps change appearance wherever lines overlap:│inside a log line) renders as a junction.Added Example
dart example/box_line_blending_demo.dart- side-by-sideindent: 0vsindent: -1, crossings, mixed-weight tees, and an overlaid panel.