The Shape tool, implied rectangles, and the palette floating again
A Shapes button right of Text draws a rectangle, an ellipse, a line of taps or an arrow in the pen's colour and width, with a fill colour of its own; a hold, a secondary press or two quick clicks open its menu. Text and Hide rectangles draw nothing on the page and show blue corners while the pointer hovers over them; free text's red corner mark is gone. Arrange moves and resizes shapes. The colour palette floats below "+" again instead of reflowing the toolbar. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
46a82bf3b7
commit
03907da8c9
12 files changed
+1502
-297
No files matched your search
@@ -12,7 +12,6 @@
|
||||
import { codeAtEnd } from './character_codes.js';
|
||||
import { randomId } from './crypto.js';
|
||||
import { textBounds } from './page_model.js';
|
||||
import { TEXT_FRAME_WIDTH, TEXT_MARK } from './page_file.js';
|
||||
import { HOLD_MS, HOLD_SLACK_PX } from './pen_input.js';
|
||||
import { LEFT_GAP, LINE_HEIGHT, TOP_GAP, baselineOf, fontMetrics, layoutText, trimTrailingBlankLines } from './text_layout.js';
|
||||
|
||||
@@ -67,15 +66,14 @@ export class TextEditor {
|
||||
}
|
||||
});
|
||||
|
||||
/** Free text's corner mark while it is being edited (the renderer leaves the block out then). */
|
||||
this.mark = document.createElement('div');
|
||||
this.mark.className = 'text_mark';
|
||||
this.mark.hidden = true;
|
||||
// The block's marks (free text's red L, a rectangle's blue corners)
|
||||
// are drawn by the renderer while it is edited as at any other time
|
||||
// (since 2026-10-14; the editor showed its own before).
|
||||
/** Shows a rectangle while it is being dragged open. */
|
||||
this.preview = document.createElement('div');
|
||||
this.preview.className = 'rect_preview';
|
||||
this.preview.hidden = true;
|
||||
this.pageElement.append(this.field, this.mark, this.preview);
|
||||
this.pageElement.append(this.field, this.preview);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -216,7 +214,6 @@ export class TextEditor {
|
||||
this.block = null;
|
||||
this.renderer.editingText = null;
|
||||
this.field.hidden = true;
|
||||
this.mark.hidden = true;
|
||||
this.field.value = '';
|
||||
trimTrailingBlankLines(block);
|
||||
if (block.text.trim() === '') this.target.removeText(block.id);
|
||||
@@ -237,7 +234,6 @@ export class TextEditor {
|
||||
this.block = null;
|
||||
this.renderer.editingText = null;
|
||||
this.field.hidden = true;
|
||||
this.mark.hidden = true;
|
||||
this.field.value = '';
|
||||
return block;
|
||||
}
|
||||
@@ -306,12 +302,6 @@ export class TextEditor {
|
||||
const { ascent, descent } = fontMetrics(block.size, block.font);
|
||||
// In a line box the baseline lies half the leading and the ascent down.
|
||||
const baselineInLine = (lineHeight - (ascent + descent) * scale) / 2 + ascent * scale;
|
||||
// A box's field is exactly the box, on whole pixels, and draws the
|
||||
// frame as an outline inside its edge, which takes no room, at the
|
||||
// width the canvas draws it.
|
||||
const frame = Math.max(1, Math.round(TEXT_FRAME_WIDTH * scale * dpr)) / dpr;
|
||||
style.outlineWidth = isRect ? `${frame}px` : '0';
|
||||
style.outlineOffset = `${-frame}px`;
|
||||
const padTop = firstBaseline - top - baselineInLine;
|
||||
style.paddingTop = `${padTop}px`;
|
||||
style.paddingLeft = `${textLeft - left}px`;
|
||||
@@ -324,13 +314,12 @@ export class TextEditor {
|
||||
style.color = block.color;
|
||||
style.whiteSpace = block.width === null ? 'pre' : 'pre-wrap';
|
||||
if (isRect) {
|
||||
// A box's field is exactly the box, on whole pixels.
|
||||
const bounds = textBounds(block);
|
||||
// The canvas strokes the far lines on the pixel at the edge itself,
|
||||
// so the field reaches one pixel past it.
|
||||
style.boxSizing = 'border-box';
|
||||
style.width = `${whole((bounds.left + bounds.width - page.left) * scale) - left + 1 / dpr}px`;
|
||||
style.width = `${whole((bounds.left + bounds.width - page.left) * scale) - left}px`;
|
||||
style.height = `${Math.max(block.lines.length * lineHeight + padTop + slack,
|
||||
whole((bounds.top + bounds.height - page.top) * scale) - top + 1 / dpr)}px`;
|
||||
whole((bounds.top + bounds.height - page.top) * scale) - top)}px`;
|
||||
} else {
|
||||
style.boxSizing = 'content-box';
|
||||
// A little wider than the block, so that the field never wraps a
|
||||
@@ -339,11 +328,5 @@ export class TextEditor {
|
||||
style.height = `${block.lines.length * lineHeight}px`;
|
||||
}
|
||||
this.field.scrollTop = 0;
|
||||
this.mark.hidden = isRect;
|
||||
this.mark.style.left = `${left}px`;
|
||||
this.mark.style.top = `${top}px`;
|
||||
this.mark.style.width = `${whole(TEXT_MARK * scale)}px`;
|
||||
this.mark.style.height = `${whole(TEXT_MARK * scale)}px`;
|
||||
this.mark.style.borderWidth = `${Math.max(1, Math.round(TEXT_FRAME_WIDTH * scale * dpr)) / dpr}px`;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user