lazy-widgets
Version:
Typescript retained mode GUI for the HTML canvas API
318 lines (317 loc) • 13.3 kB
TypeScript
import { FillStyle } from '../theme/FillStyle.js';
/**
* The type of a {@link TextRenderGroup}.
*
* @category Helper
*/
export declare enum TextRenderGroupType {
Range = 0,
Inline = 1
}
/** Base interface for {@link TextRenderGroup}. */
export interface BaseTextRenderGroup {
type: TextRenderGroupType;
rangeStart: number;
rangeEnd: number;
right: number;
overridesWidth: boolean;
visible: boolean;
}
/**
* A text render group that represents a range of existing text. Contains all
* neccessary information to position that piece of text.
*
* For characters that override width, the text range should have a length of 1
* and will not be merged with other text render groups, else, it is a hint
* containing the pre-measured size, for optimisation reasons, and may be merged
* with other text render groups. Width-overidding groups where the text range
* length is greater than 1 will have the length of each individual character as
* an interpolation of the total length, where each character has equal width,
* which is cheap, but innacurate; this is the reason why it is preferred to
* have length 1 text range for width-overriding groups although you may still
* want this for specific reasons (such as trailing space removal).
* Width-overriding groups are also only meant to be used for whitespaces and
* will therefore not be painted.
*
* Note that 0-width text render groups are valid and used for empty lines.
*
* Note also that, for optimisation reasons, text render groups will also be
* created for spaces. This maximises cache hits when calling measureTextDims.
*
* @category Helper
*/
export interface RangeTextRenderGroup extends BaseTextRenderGroup {
type: TextRenderGroupType.Range;
}
/**
* In-place text. Instead of representing an existing range of text (the text
* range is 0-sized, and the start of the range is the offset where the in-place
* text should be inserted), this render group represents a piece of text that
* is not present in the original text. This is useful for inserting ellipsis on
* overflow.
*
* @category Helper
*/
export interface InlineTextRenderGroup extends BaseTextRenderGroup {
type: TextRenderGroupType.Inline;
text: string;
}
/**
* A text render group. Contains all neccessary information to position a piece
* of text.
*
* Contains the inclusive index where the piece of text starts, the exclusive
* index where the piece of text ends (including characters that aren't
* rendered, such as newlines), the right horizontal offset of the piece of text
* and whether the piece of text overrides width or not.
*
* @category Helper
*/
export type TextRenderGroup = RangeTextRenderGroup | InlineTextRenderGroup;
/**
* A line range. Contains all neccessary information to render a line of text.
* An array of text render groups.
*
* @category Helper
*/
export type LineRange = Array<TextRenderGroup>;
/**
* The mode to use for text wrapping in {@link TextHelper}.
*
* @category Helper
*/
export declare enum WrapMode {
/** No text wrapping. Text will overflow if it exceeds the maximum width. */
None = "none",
/**
* No text wrapping, but instead of clipping the text, the end is replaced
* with ellipsis. If the text won't even fit ellipsis, then the text is
* clipped.
*/
Ellipsis = "ellipsis",
/**
* Whitespaces always have width. The default wrapping mode for input
* widgets
*/
Normal = "normal",
/**
* Whitespaces at the end of a line which result in an overflow have no
* width. The default wrapping mode for widgets that display text, since
* spaces at the beginning of a line due to wrapping looks weird in
* {@link Label | labels}. Whitespaces at the beginning of a new line are
* still kept, as they are deliberate.
*/
Shrink = "shrink"
}
/**
* The mode to use for text alignment in {@link TextHelper}.
*
* @category Helper
*/
export declare enum TextAlignMode {
/** Align to the start of the line. Equivalent to a ratio of 0. */
Start = 0,
/** Align to the center of the line. Equivalent to a ratio of 0.5. */
Center = 0.5,
/** Align to the end of the line. Equivalent to a ratio of 0.5. */
End = 1
}
/**
* An aggregate helper class for widgets that contain text.
*
* Contains utilities for measuring text dimensions, converting between offsets
* in pixels and text indices and painting.
*
* @category Helper
*/
export declare class TextHelper {
/** The current string of text. */
text: string;
/** The current font used for rendering text. */
font: string;
/**
* The current maximum text width. If not Infinite and
* {@link TextHelper#wrapMode} is not `WrapMode.None` and not
* `WrapMode.Ellipsis`, then text will be wrapped and width will be set to
* maxWidth.
*/
maxWidth: number;
/**
* The height of each line of text when wrapped. If null, then the helper
* will try to automatically detect it.
*/
lineHeight: number | null;
/**
* The amount of spacing between lines. If null, then the helper will try to
* automatically detect it.
*/
lineSpacing: number | null;
/**
* The amount of spaces that each tab character is equivalent to. By
* default, it is equivalent to 4 spaces.
*/
tabWidth: number;
/** The mode for text wrapping */
wrapMode: WrapMode;
/**
* The text alignment mode. Can also be a ratio.
*
* Note that this only aligns text in the text's width. If
* {@link TextHelper#maxWidth} is infinite, then you may still need to align
* the widget that uses this text helper with a {@link BaseContainer}
* because the width will be set to the longest line range's width.
*/
alignMode: TextAlignMode | number;
/** The current largest text width. May be outdated. */
private _width;
/** The current total text height. May be outdated. */
private _height;
/** The current {@link TextHelper#lineHeight}. May be outdated */
private _lineHeight;
/** The current {@link TextHelper#lineSpacing}. May be outdated */
private _lineSpacing;
/** The actual {@link TextHelper#tabWidth} in pixels. May be outdated */
private _tabWidth;
/** The width of a space in pixels. May be outdated */
private _spaceWidth;
/** Does the text need to be re-measured? */
private measureDirty;
/** Does the line height or spacing need to be re-measured? */
private lineHeightSpacingDirty;
/** Do the space and tab widths need to be re-measured? */
private tabWidthDirty;
/** {@link TextHelper#dirty} but for internal use. */
private _dirty;
/** See {@link TextHelper#lineRanges}. For internal use only. */
private _lineRanges;
/**
* Has the text (or properties associated with it) changed? Resets to false
* after reading the current value.
*/
get dirty(): boolean;
/** Resets {@link TextHelper#dirty} to false */
cleanDirtyFlag(): void;
/**
* Measure a slice of text taking left offset into account. If left offset
* is 0, then this will also add the left bounding box overhang. If not,
* then it will just return the width.
*
* Only for slices of text which have no width-overriding characters, else,
* you will get wrong measurements.
*
* @returns Returns the new horizontal offset
*/
private measureTextSlice;
/**
* Get width from line range start to index. Handles out of bounds indices,
* but keeps them in the same line
*/
private getLineRangeWidthUntil;
/**
* Similar to {@link measureTextDims}, but uses text render groups for
* optimisation purposes and for the ability of individual characters to
* override their natively measured size; tabs having a dynamic size that
* aligns them to multiples of a value and newlines having no length.
*
* @param start - The inclusive index to start measuring at. If there are render groups and unmeasured text before this index, then this value will be overridden to include the unmeasured text. Render groups will also be merged if they don't override width.
* @param end - The exclusive index to stop measuring at.
* @param lineRange - The current text render groups for this line of text. This will be updated in place.
* @param maxWidth - The maximum width of a line of text. If the line contains a single character, this will be ignored.
* @returns Returns true if the line range was modified and it fit into the maximum width
*/
private measureText;
/**
* Update {@link TextHelper#_width}, {@link TextHelper#_lineHeight} and
* {@link TextHelper#_lineSpacing}. Sets {@link TextHelper#measureDirty} to
* false. Does nothing if measurement is not needed.
*/
private updateTextDims;
/**
* Paint a single text render group with a given offset and left value for
* checking if the group is zero-width. left value must not be shifted.
*
* Used mainly for injecting debug code; you won't get much use out of this
* method unless you have a very specific need.
*/
paintGroup(ctx: CanvasRenderingContext2D, group: TextRenderGroup, left: number, x: number, y: number): void;
/** Paint all line ranges. */
paint(ctx: CanvasRenderingContext2D, fillStyle: FillStyle, x: number, y: number): void;
/**
* Get the horizontal offset, in pixels, of the beginning of a character at
* a given index.
*
* See {@link TextHelper#findIndexOffsetFromOffset} for the opposite.
*
* @param preferLineEnd - If true, the offset at the end of the line will be returned, instead of at the beginning of the line. Only applies to line ranges that were wrapped.
* @returns Returns a 2-tuple containing the offset, in pixels. Vertical offset in the tuple is at the top of the character. Note that this is not neccessarily an integer.
*/
findOffsetFromIndex(index: number, preferLineEnd?: boolean): [x: number, yTop: number];
/**
* Get the index and horizontal offset, in pixels, of the beginning of a
* character at a given offset.
*
* See {@link TextHelper#findOffsetFromIndex} for the opposite.
*
* @returns Returns a 2-tuple containing the index of the character at the offset and a 2-tuple containing the offset, in pixels. Note that this is not neccessarily an integer. Note that the returned offset is not the same as the input offset. The returned offset is exactly at the beginning of the character. This is useful for implementing selectable text.
*/
findIndexOffsetFromOffset(offset: [number, number]): [number, [number, number]];
/**
* Get a line number from a given cursor index. If out of bounds, returns
* nearest in-bounds line. Line numbers start at 0.
*/
getLine(index: number): number;
/**
* Get the index of the start of a line. If out of bounds, returns the
* nearest in-bounds index
*/
getLineStart(line: number): number;
/**
* Get the index of the end of a line.
*
* @param includeNewlines - If false, newline characters will be ignored and the end will be at their index, instead of after their index
*/
getLineEnd(line: number, includeNewlines?: boolean): number;
/**
* Get the horizontal offset, in pixels, of the start of a line. Takes text
* wrapping into account. Line indices before first line will be treated as
* the first line, after the last line will be treated as a new empty line.
*/
getLineShift(line: number): number;
/** The current text width. Re-measures text if neccessary. */
get width(): number;
/** The current total text height. Re-measures text if neccessary. */
get height(): number;
/**
* Which range of text indices are used for each line.
*
* If there is no text wrapping (`maxWidth` is `Infinity`, or `wrapMode` is
* `WrapMode.None` or `wrapMode` is `WrapMode.Ellipsis`), then this will
* contain a single tuple containing `[0, (text length)]`.
*
* If there is text wrapping, then this will be an array where each member
* is a tuple containing the starting index of a line of text and the ending
* index (exclusive) of a line of text.
*/
get lineRanges(): Array<LineRange>;
/**
* Get the current line height, even if {@link TextHelper#lineHeight} is
* null. Re-measures line height if neccessary.
*/
get actualLineHeight(): number;
/**
* Get the current line spacing, even if {@link TextHelper#lineSpacing} is
* null. Re-measures line spacing if neccessary.
*/
get actualLineSpacing(): number;
/** Get the current tab width in pixels. Re-measures if neccessary */
get actualTabWidth(): number;
/** Get the current space width in pixels. Re-measures if necessary */
get spaceWidth(): number;
/**
* Get the height between the start of each line; the full line height.
*
* Equivalent to the sum of {@link TextHelper#actualLineHeight} and
* {@link TextHelper#actualLineSpacing}
*/
get fullLineHeight(): number;
}