feat: F4.8a the documentation items in model and file, measured against Java's
New oracle DocuRun on Java's JDocuElement. Italic is kept (the model knew only bold), an item keeps its file's size and font size (Swift's 32 x 24 and 8-72 changed Java's files), an item without a colour is Java's light grey. Java's new items, colour lists, font sizes and styles, the drag's smallest sizes and Java's stacking; document commands for the editor. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,15 @@
|
||||
// ios/FiliusPad/TopologyEditor/Model/TopologyDocumentation.swift
|
||||
// Java counterpart: filius.gui.netzwerksicht.GUIDocuItem (RECT = 1, TEXT = 2).
|
||||
// Rectangles and text boxes drawn over the network.
|
||||
//
|
||||
// Java's rules where they differ from Swift's (measured with
|
||||
// tools/java-oracle's DocuRun, F4.8a): a new item is 100 × 50, a text black
|
||||
// in Dialog 12, a rectangle grey (204, 204, 204); an item keeps the size and
|
||||
// font size its file gives it (Swift enlarges to 32 × 24 and keeps sizes
|
||||
// within 8…72); a text may be italic (Java's style 2) as well as bold; an
|
||||
// item without a colour shows light grey (Color.LIGHT_GRAY); a drag leaves
|
||||
// a rectangle at least 20 × 20 and a text 88 × 20; texts lie over
|
||||
// rectangles, and of one kind the earlier over the later.
|
||||
|
||||
import { compareIds, type Id } from '../ids.js';
|
||||
|
||||
@@ -31,12 +40,21 @@ export interface DocumentationItem {
|
||||
fontName: string;
|
||||
fontSize: number;
|
||||
isBold: boolean;
|
||||
isItalic: boolean;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export const DOCUMENTATION_DEFAULT_TEXT_SIZE = { width: 220, height: 96 } as const;
|
||||
export const DOCUMENTATION_DEFAULT_RECTANGLE_SIZE = { width: 240, height: 140 } as const;
|
||||
export const DOCUMENTATION_MINIMUM_SIZE = { width: 32, height: 24 } as const;
|
||||
/** A new item's size, either kind (JDocuElement's constructor). */
|
||||
export const DOCUMENTATION_NEW_SIZE = { width: 100, height: 50 } as const;
|
||||
|
||||
/**
|
||||
* The smallest size a drag leaves (JDocuElement: RECT_MIN_WIDTH/HEIGHT; a
|
||||
* text's TEXT_MIN_WIDTH/HEIGHT hold for its frame enlarged by the focus,
|
||||
* which is 12 wider and 28 higher than the text).
|
||||
*/
|
||||
export function documentationMinimumSize(kind: DocumentationItemKind): { readonly width: number; readonly height: number } {
|
||||
return kind === 'rectangle' ? { width: 20, height: 20 } : { width: 88, height: 20 };
|
||||
}
|
||||
/** The editor's coordinate range; keeps Java `int` export and rendering safe. */
|
||||
export const DOCUMENTATION_WORLD_BOUNDS: Rect = { x: -10_000, y: -10_000, width: 20_000, height: 20_000 };
|
||||
|
||||
@@ -49,8 +67,31 @@ export function documentationColor(red: number, green: number, blue: number, alp
|
||||
return { red: clampUnit(red), green: clampUnit(green), blue: clampUnit(blue), alpha: clampUnit(alpha) };
|
||||
}
|
||||
|
||||
export const DOCUMENTATION_BLACK = documentationColor(0, 0, 0);
|
||||
export const DOCUMENTATION_PALE_YELLOW = documentationColor(255 / 255, 255 / 255, 227 / 255, 0.82);
|
||||
const byte = (red: number, green: number, blue: number) => documentationColor(red / 255, green / 255, blue / 255);
|
||||
|
||||
export const DOCUMENTATION_BLACK = byte(0, 0, 0);
|
||||
/** A new rectangle's colour (JDocuElement.panelColor[1]). */
|
||||
export const DOCUMENTATION_GREY = byte(204, 204, 204);
|
||||
/** What Java shows for an item its file gives no colour (JDocuElement.setColor(null)). */
|
||||
export const DOCUMENTATION_LIGHT_GRAY = byte(192, 192, 192);
|
||||
|
||||
/** The colours Java's menu offers a text, in its order, three rows (JDocuElement.fontColor). */
|
||||
export const DOCUMENTATION_TEXT_COLORS: readonly DocumentationColor[] = [
|
||||
[0, 0, 0], [102, 102, 102], [153, 153, 153], [179, 179, 179], [255, 255, 255], [255, 0, 0], [179, 163, 199], [79, 130, 189],
|
||||
[156, 186, 89], [148, 138, 84], [148, 56, 54], [94, 74, 122], [31, 74, 125], [110, 135, 56], [89, 84, 48],
|
||||
].map(([red, green, blue]) => byte(red!, green!, blue!));
|
||||
|
||||
/** The colours Java's menu offers a rectangle, in its order, three rows (JDocuElement.panelColor). */
|
||||
export const DOCUMENTATION_RECTANGLE_COLORS: readonly DocumentationColor[] = [
|
||||
[0, 0, 0], [204, 204, 204], [235, 235, 235], [255, 255, 255], [255, 201, 201], [252, 201, 163], [255, 255, 227], [212, 207, 181],
|
||||
[222, 212, 255], [209, 232, 255], [191, 255, 219], [204, 222, 173],
|
||||
].map(([red, green, blue]) => byte(red!, green!, blue!));
|
||||
|
||||
/** The font sizes Java's menu offers: „Normal“, „Groß“, „Sehr Groß“. */
|
||||
export const DOCUMENTATION_FONT_SIZES = [12, 18, 24] as const;
|
||||
|
||||
/** The font styles Java's menu offers, one at a time: „Normal“, „Kursiv“, „Fett“. */
|
||||
export type DocumentationFontStyle = 'plain' | 'italic' | 'bold';
|
||||
|
||||
/** CGRect.standardized: a negative width or height moves the origin instead. */
|
||||
function standardized(frame: Rect): Rect {
|
||||
@@ -59,14 +100,14 @@ function standardized(frame: Rect): Rect {
|
||||
return { x, y, width: Math.abs(frame.width), height: Math.abs(frame.height) };
|
||||
}
|
||||
|
||||
/** At least the minimum size, at most the world, moved inside the world. */
|
||||
/** Its own size (at least one, at most the world), moved inside the world. */
|
||||
export function normalizedFrame(frame: Rect): Rect {
|
||||
const world = DOCUMENTATION_WORLD_BOUNDS;
|
||||
const fallback = { x: world.x, y: world.y, ...DOCUMENTATION_MINIMUM_SIZE };
|
||||
const fallback = { x: world.x, y: world.y, ...DOCUMENTATION_NEW_SIZE };
|
||||
if (![frame.x, frame.y, frame.width, frame.height].every(Number.isFinite)) return fallback;
|
||||
const rect = standardized(frame);
|
||||
const width = Math.min(Math.max(rect.width, DOCUMENTATION_MINIMUM_SIZE.width), world.width);
|
||||
const height = Math.min(Math.max(rect.height, DOCUMENTATION_MINIMUM_SIZE.height), world.height);
|
||||
const width = Math.min(Math.max(rect.width, 1), world.width);
|
||||
const height = Math.min(Math.max(rect.height, 1), world.height);
|
||||
const x = Math.min(Math.max(rect.x, world.x), world.x + world.width - width);
|
||||
const y = Math.min(Math.max(rect.y, world.y), world.y + world.height - height);
|
||||
return { x, y, width, height };
|
||||
@@ -85,12 +126,12 @@ export function isSafeFrame(frame: Rect): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
/** A font size as Java keeps it: any positive one; else Java's 12. */
|
||||
export function normalizedFontSize(value: number): number {
|
||||
if (!Number.isFinite(value)) return 14;
|
||||
return Math.min(Math.max(value, 8), 72);
|
||||
return Number.isFinite(value) && value > 0 ? value : 12;
|
||||
}
|
||||
|
||||
/** A documentation item as Swift's initializer normalizes it. */
|
||||
/** A documentation item as Swift's initializer normalizes it, with Java's sizes and font. */
|
||||
export function documentationItem(fields: {
|
||||
id: Id;
|
||||
kind: DocumentationItemKind;
|
||||
@@ -100,23 +141,40 @@ export function documentationItem(fields: {
|
||||
fontName?: string;
|
||||
fontSize?: number;
|
||||
isBold?: boolean;
|
||||
isItalic?: boolean;
|
||||
order: number;
|
||||
}): DocumentationItem {
|
||||
const fontName = (fields.fontName ?? 'System').trim();
|
||||
const fontName = (fields.fontName ?? 'Dialog').trim();
|
||||
return {
|
||||
id: fields.id,
|
||||
kind: fields.kind,
|
||||
frame: normalizedFrame(fields.frame),
|
||||
text: fields.kind === 'text' ? (fields.text ?? '') : '',
|
||||
color: fields.color,
|
||||
fontName: fontName === '' ? 'System' : fontName,
|
||||
fontSize: normalizedFontSize(fields.fontSize ?? 14),
|
||||
fontName: fontName === '' ? 'Dialog' : fontName,
|
||||
fontSize: normalizedFontSize(fields.fontSize ?? 12),
|
||||
isBold: fields.isBold ?? false,
|
||||
isItalic: fields.isItalic ?? false,
|
||||
order: Math.max(0, fields.order),
|
||||
};
|
||||
}
|
||||
|
||||
/** Rectangles below text, then by order, then by id. */
|
||||
/** Java's style of a font (Font.PLAIN 0, BOLD 1, ITALIC 2, both 3). */
|
||||
export function documentationFontStyle(item: Pick<DocumentationItem, 'isBold' | 'isItalic'>): number {
|
||||
return (item.isBold ? 1 : 0) | (item.isItalic ? 2 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The order to draw them in, the last on top: as Java's panel stacks them
|
||||
* (GUIDocumentationPanel.updateViewport, the first component on top) —
|
||||
* texts over rectangles, of one kind the earlier over the later.
|
||||
*/
|
||||
export function inJavaDrawingOrder<T extends Pick<DocumentationItem, 'id' | 'kind' | 'order'>>(items: readonly T[]): T[] {
|
||||
const layer = (item: T): number => (item.kind === 'rectangle' ? 0 : 1);
|
||||
return [...items].sort((lhs, rhs) => layer(lhs) - layer(rhs) || rhs.order - lhs.order || compareIds(rhs.id, lhs.id));
|
||||
}
|
||||
|
||||
/** Rectangles first, then text, each by order, then by id: the list of the file. */
|
||||
export function inDeterministicRenderOrder(items: readonly DocumentationItem[]): DocumentationItem[] {
|
||||
const layer = (item: DocumentationItem): number => (item.kind === 'rectangle' ? 0 : 1);
|
||||
return [...items].sort(
|
||||
@@ -148,3 +206,36 @@ export function deterministicDocumentationUuid(seed: string): Id {
|
||||
const hex = high.toString(16).padStart(16, '0') + low.toString(16).padStart(16, '0');
|
||||
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`.toUpperCase();
|
||||
}
|
||||
|
||||
/** The edges a drag on an item's border moves (JDocuElement: LEFT_SIZING, RIGHT_SIZING, UPPER_SIZING, LOWER_SIZING). */
|
||||
export interface DocumentationEdges {
|
||||
readonly left?: boolean;
|
||||
readonly right?: boolean;
|
||||
readonly top?: boolean;
|
||||
readonly bottom?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* JDocuElement's drag on its border: a moved edge follows the pointer, the
|
||||
* opposite one stays; below the kind's smallest size the moved edge stops
|
||||
* there. `pointer` is where the pointer is, in the drawing's coordinates.
|
||||
*/
|
||||
export function resizedFrame(frame: Rect, kind: DocumentationItemKind, edges: DocumentationEdges, pointer: { readonly x: number; readonly y: number }): Rect {
|
||||
const minimum = documentationMinimumSize(kind);
|
||||
let { x, y, width, height } = frame;
|
||||
if (edges.left === true) {
|
||||
const right = frame.x + frame.width;
|
||||
width = Math.max(right - pointer.x, minimum.width);
|
||||
x = right - width;
|
||||
} else if (edges.right === true) {
|
||||
width = Math.max(pointer.x - frame.x, minimum.width);
|
||||
}
|
||||
if (edges.top === true) {
|
||||
const bottom = frame.y + frame.height;
|
||||
height = Math.max(bottom - pointer.y, minimum.height);
|
||||
y = bottom - height;
|
||||
} else if (edges.bottom === true) {
|
||||
height = Math.max(pointer.y - frame.y, minimum.height);
|
||||
}
|
||||
return { x, y, width, height };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user