@niivue/uikit 1.0.0-rc.2 → 1.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +702 -8
- package/dist/button.d.ts +97 -0
- package/dist/buttonOverlay.d.ts +121 -0
- package/dist/colorControl.d.ts +130 -0
- package/dist/colorControlOverlay.d.ts +72 -0
- package/dist/controls.d.ts +151 -0
- package/dist/dialog.d.ts +138 -0
- package/dist/dialogOverlay.d.ts +115 -0
- package/dist/filePicker.d.ts +95 -0
- package/dist/filePickerOverlay.d.ts +75 -0
- package/dist/host.d.ts +87 -0
- package/dist/index.d.ts +61 -0
- package/dist/menu.d.ts +227 -0
- package/dist/menuOverlay.d.ts +149 -0
- package/dist/numberInput.d.ts +172 -0
- package/dist/numberInputOverlay.d.ts +131 -0
- package/dist/pointLabel.d.ts +68 -0
- package/dist/pointLabelOverlay.d.ts +39 -0
- package/dist/rect.d.ts +27 -0
- package/dist/rectOverlay.d.ts +13 -0
- package/dist/render/glRectRenderer.d.ts +13 -0
- package/dist/render/shaders.d.ts +3 -0
- package/dist/render/wgpuRectRenderer.d.ts +18 -0
- package/dist/scroll.d.ts +70 -0
- package/dist/segmented.d.ts +127 -0
- package/dist/segmentedOverlay.d.ts +87 -0
- package/dist/select.d.ts +66 -0
- package/dist/selectOverlay.d.ts +102 -0
- package/dist/slider.d.ts +152 -0
- package/dist/sliderOverlay.d.ts +92 -0
- package/dist/text/layout.d.ts +8 -0
- package/dist/textArea.d.ts +216 -0
- package/dist/textAreaOverlay.d.ts +145 -0
- package/dist/textEdit.d.ts +72 -0
- package/dist/textField.d.ts +33 -0
- package/dist/textInput.d.ts +123 -0
- package/dist/textInputOverlay.d.ts +139 -0
- package/dist/toggle.d.ts +115 -0
- package/dist/toggleOverlay.d.ts +80 -0
- package/dist/uikit.js +6269 -221
- package/package.json +3 -3
package/dist/menu.d.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { ButtonLayout, ButtonSpec, ButtonStyle, ButtonVisual } from './button';
|
|
2
|
+
import { LineData } from './line';
|
|
3
|
+
import { RectData } from './rect';
|
|
4
|
+
import { UIKitBox } from './scroll';
|
|
5
|
+
import { UIKitFontMetrics } from './text/font';
|
|
6
|
+
import { RGBA } from './text/layout';
|
|
7
|
+
import { UIKitTextItem } from './textOverlay';
|
|
8
|
+
export type MenuItemKind = 'action' | 'check' | 'radio' | 'separator';
|
|
9
|
+
export interface MenuItemSpec {
|
|
10
|
+
/** Stable key within the menu: the handle for state and the callbacks. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Shown text. Ignored for a separator. */
|
|
13
|
+
label?: string;
|
|
14
|
+
/**
|
|
15
|
+
* 'action' (default) fires and closes; 'check' keeps a boolean the user
|
|
16
|
+
* flips; 'radio' is one of an exclusive group; 'separator' is a rule.
|
|
17
|
+
*/
|
|
18
|
+
kind?: MenuItemKind;
|
|
19
|
+
/** Current state of a check or radio item. Default false. */
|
|
20
|
+
checked?: boolean;
|
|
21
|
+
/** Radio group key. Radios without one share the menu's default group. */
|
|
22
|
+
group?: string;
|
|
23
|
+
/** A disabled item draws dimmed and cannot be highlighted or activated. */
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
/** A hint drawn right-aligned (a key combination, say). Not bound to any key. */
|
|
26
|
+
shortcut?: string;
|
|
27
|
+
/** Fired when this item is activated (after any state change is applied). */
|
|
28
|
+
onSelect?: (id: string) => void;
|
|
29
|
+
}
|
|
30
|
+
/** Every visual knob on a menu popup. All lengths are in canvas pixels. */
|
|
31
|
+
export interface MenuStyle {
|
|
32
|
+
panelFill: RGBA;
|
|
33
|
+
panelBorder: RGBA;
|
|
34
|
+
panelBorderWidth: number;
|
|
35
|
+
panelRadius: number;
|
|
36
|
+
/** Space between the panel edge and the rows. */
|
|
37
|
+
panelPadding: number;
|
|
38
|
+
/** Height of an action, check or radio row. */
|
|
39
|
+
itemHeight: number;
|
|
40
|
+
/** Space between the row edge and its contents. */
|
|
41
|
+
itemPaddingX: number;
|
|
42
|
+
/** Row color under the highlighted item. */
|
|
43
|
+
highlightFill: RGBA;
|
|
44
|
+
textColor: RGBA;
|
|
45
|
+
disabledTextColor: RGBA;
|
|
46
|
+
shortcutColor: RGBA;
|
|
47
|
+
/** Item em size. */
|
|
48
|
+
textSizePx: number;
|
|
49
|
+
/** Check-mark and radio-dot color. */
|
|
50
|
+
checkColor: RGBA;
|
|
51
|
+
/** Side of the square the check mark or radio dot is drawn in. */
|
|
52
|
+
markSize: number;
|
|
53
|
+
/** Check-mark stroke width. */
|
|
54
|
+
markWidth: number;
|
|
55
|
+
/** Space between the mark slot and the label. */
|
|
56
|
+
markGap: number;
|
|
57
|
+
/** Least space between the longest label and the shortcut column. */
|
|
58
|
+
shortcutGap: number;
|
|
59
|
+
separatorColor: RGBA;
|
|
60
|
+
/** Height of a separator row (the rule is centred in it). */
|
|
61
|
+
separatorHeight: number;
|
|
62
|
+
/** Space between the button and the popup. */
|
|
63
|
+
gapToButton: number;
|
|
64
|
+
/** Width of the chevron on the button, and its stroke width. */
|
|
65
|
+
chevronSize: number;
|
|
66
|
+
chevronWidth: number;
|
|
67
|
+
/** Space between the label and the chevron. */
|
|
68
|
+
chevronGap: number;
|
|
69
|
+
/** Least popup width. */
|
|
70
|
+
minWidth: number;
|
|
71
|
+
/** Height of the strips at each end of a scrolled popup (they hold the arrows). */
|
|
72
|
+
scrollStripHeight: number;
|
|
73
|
+
/** Width of the scroll arrows, and their stroke width. */
|
|
74
|
+
scrollArrowSize: number;
|
|
75
|
+
scrollArrowWidth: number;
|
|
76
|
+
scrollArrowColor: RGBA;
|
|
77
|
+
}
|
|
78
|
+
export declare const DEFAULT_MENU_STYLE: MenuStyle;
|
|
79
|
+
/** The button style menus start from: a push button that does not shrink. */
|
|
80
|
+
export declare const DEFAULT_MENU_BUTTON_STYLE: ButtonStyle;
|
|
81
|
+
export interface MenuButtonSpec {
|
|
82
|
+
/** Stable key: the handle for updates, open state and the callbacks. */
|
|
83
|
+
id: string;
|
|
84
|
+
label: string;
|
|
85
|
+
/** Top-left corner of the button, in canvas pixels. */
|
|
86
|
+
x: number;
|
|
87
|
+
y: number;
|
|
88
|
+
/** Fixed button size. Omit either to size that axis to the label and chevron. */
|
|
89
|
+
width?: number;
|
|
90
|
+
height?: number;
|
|
91
|
+
items: readonly MenuItemSpec[];
|
|
92
|
+
/** A disabled menu button draws dimmed and cannot open. Default true. */
|
|
93
|
+
enabled?: boolean;
|
|
94
|
+
/** Per-menu overrides of the overlay's default button style. */
|
|
95
|
+
buttonStyle?: Partial<ButtonStyle>;
|
|
96
|
+
/** Per-menu overrides of the overlay's default popup style. */
|
|
97
|
+
style?: Partial<MenuStyle>;
|
|
98
|
+
/** Fired when any enabled item is activated, after its own `onSelect`. */
|
|
99
|
+
onSelect?: (itemId: string, menuId: string) => void;
|
|
100
|
+
/**
|
|
101
|
+
* Fired for each check or radio item whose state changed: once for a check
|
|
102
|
+
* item, and for a radio once for the newly checked item plus once for each
|
|
103
|
+
* item it unchecked.
|
|
104
|
+
*/
|
|
105
|
+
onChange?: (itemId: string, checked: boolean, menuId: string) => void;
|
|
106
|
+
}
|
|
107
|
+
/** One row of an open popup, in canvas pixels. */
|
|
108
|
+
export interface MenuRowLayout {
|
|
109
|
+
/** Index into the menu's items. */
|
|
110
|
+
index: number;
|
|
111
|
+
x: number;
|
|
112
|
+
y: number;
|
|
113
|
+
width: number;
|
|
114
|
+
height: number;
|
|
115
|
+
}
|
|
116
|
+
/** The popup panel and its rows, in canvas pixels. */
|
|
117
|
+
export interface MenuPopupLayout {
|
|
118
|
+
x: number;
|
|
119
|
+
y: number;
|
|
120
|
+
width: number;
|
|
121
|
+
height: number;
|
|
122
|
+
/**
|
|
123
|
+
* The visible rows only. When the popup scrolls these are the run from
|
|
124
|
+
* `firstRow` that fits between the strips.
|
|
125
|
+
*/
|
|
126
|
+
rows: MenuRowLayout[];
|
|
127
|
+
/** Whether the panel sits above the button (it would overflow below). */
|
|
128
|
+
above: boolean;
|
|
129
|
+
/** True when the items do not all fit and the popup scrolls by rows. */
|
|
130
|
+
scrollable: boolean;
|
|
131
|
+
/** Index of the first visible item (0 when not scrollable). */
|
|
132
|
+
firstRow: number;
|
|
133
|
+
/** Index after the last visible item (the item count when not scrollable). */
|
|
134
|
+
endRow: number;
|
|
135
|
+
/** The strips at the top and bottom of a scrollable popup, else null. */
|
|
136
|
+
scrollUp: UIKitBox | null;
|
|
137
|
+
scrollDown: UIKitBox | null;
|
|
138
|
+
}
|
|
139
|
+
/** A rectangle popups must stay within (the overlay frame's bounds). */
|
|
140
|
+
export interface MenuBounds {
|
|
141
|
+
x: number;
|
|
142
|
+
y: number;
|
|
143
|
+
width: number;
|
|
144
|
+
height: number;
|
|
145
|
+
}
|
|
146
|
+
export declare function resolveMenuStyle(base: MenuStyle, override?: Partial<MenuStyle>): MenuStyle;
|
|
147
|
+
/** True for an item the user can highlight and activate. */
|
|
148
|
+
export declare function isMenuItemSelectable(item: MenuItemSpec): boolean;
|
|
149
|
+
/** The `ButtonSpec` a menu button draws as (label plus room for the chevron). */
|
|
150
|
+
export declare function menuButtonSpec(spec: MenuButtonSpec, buttonStyle: ButtonStyle, style: MenuStyle, metrics: UIKitFontMetrics): ButtonSpec;
|
|
151
|
+
/** The button's box: fitted to the label plus the chevron unless fixed. */
|
|
152
|
+
export declare function layoutMenuButton(spec: MenuButtonSpec, buttonStyle: ButtonStyle, style: MenuStyle, metrics: UIKitFontMetrics): ButtonLayout;
|
|
153
|
+
/** The height of one row: a separator's or an item's. */
|
|
154
|
+
export declare function menuRowHeight(item: MenuItemSpec, style: MenuStyle): number;
|
|
155
|
+
/**
|
|
156
|
+
* Place the popup under the button (above it when it would overflow the
|
|
157
|
+
* bounds below), clamped into the bounds horizontally, and lay out its rows.
|
|
158
|
+
* When the items fit neither below nor above, the popup takes the roomier
|
|
159
|
+
* side and scrolls: it keeps a strip at each end and shows the whole rows
|
|
160
|
+
* from `firstRow` (clamped so the list never scrolls past its end) that fit
|
|
161
|
+
* between them. Without bounds a popup never scrolls.
|
|
162
|
+
*/
|
|
163
|
+
export declare function layoutMenuPopup(items: readonly MenuItemSpec[], style: MenuStyle, metrics: UIKitFontMetrics, button: ButtonLayout, bounds?: MenuBounds | null, firstRow?: number): MenuPopupLayout;
|
|
164
|
+
/**
|
|
165
|
+
* The `firstRow` that brings item `index` into view in a scrollable popup,
|
|
166
|
+
* moving as little as possible (see `revealRow`). The current `firstRow` when
|
|
167
|
+
* the popup does not scroll or the item is already visible.
|
|
168
|
+
*/
|
|
169
|
+
export declare function menuRevealRow(items: readonly MenuItemSpec[], style: MenuStyle, layout: MenuPopupLayout, index: number): number;
|
|
170
|
+
/**
|
|
171
|
+
* The scroll strip under a canvas point of a scrollable popup: -1 for the top
|
|
172
|
+
* strip, 1 for the bottom one, 0 for neither.
|
|
173
|
+
*/
|
|
174
|
+
export declare function menuScrollStripAt(layout: MenuPopupLayout, px: number, py: number): -1 | 0 | 1;
|
|
175
|
+
/** Scale every length in a menu spec and its styles by `k`. */
|
|
176
|
+
export declare function scaleMenu(spec: MenuButtonSpec, buttonStyle: ButtonStyle, style: MenuStyle, k: number): {
|
|
177
|
+
spec: MenuButtonSpec;
|
|
178
|
+
buttonStyle: ButtonStyle;
|
|
179
|
+
style: MenuStyle;
|
|
180
|
+
};
|
|
181
|
+
/** True when canvas point (px, py) lies inside the popup panel. */
|
|
182
|
+
export declare function popupContains(layout: MenuPopupLayout, px: number, py: number): boolean;
|
|
183
|
+
/** The index of the row under a canvas point, or -1 (separators count as rows). */
|
|
184
|
+
export declare function menuRowAt(layout: MenuPopupLayout, px: number, py: number): number;
|
|
185
|
+
/**
|
|
186
|
+
* The next selectable index from `from` in `direction`, wrapping around and
|
|
187
|
+
* skipping separators and disabled items. Pass -1 to start from the end the
|
|
188
|
+
* search enters at. Returns -1 when nothing is selectable.
|
|
189
|
+
*/
|
|
190
|
+
export declare function nextSelectableIndex(items: readonly MenuItemSpec[], from: number, direction: 1 | -1): number;
|
|
191
|
+
/**
|
|
192
|
+
* The item states after activating `items[index]`: a check item flips, a
|
|
193
|
+
* radio item becomes the checked one in its group, anything else is unchanged.
|
|
194
|
+
* `changed` lists every item whose `checked` differs, the activated one first.
|
|
195
|
+
*/
|
|
196
|
+
export declare function activateMenuItem(items: readonly MenuItemSpec[], index: number): {
|
|
197
|
+
items: MenuItemSpec[];
|
|
198
|
+
changed: {
|
|
199
|
+
id: string;
|
|
200
|
+
checked: boolean;
|
|
201
|
+
}[];
|
|
202
|
+
};
|
|
203
|
+
/** Visual state of a menu button: a push button that also shows whether it is open. */
|
|
204
|
+
export interface MenuButtonVisual extends ButtonVisual {
|
|
205
|
+
open: boolean;
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The draw data for a menu button: the face and label (shifted left to leave
|
|
209
|
+
* room for the chevron) and a chevron pointing down, or up while open.
|
|
210
|
+
*/
|
|
211
|
+
export declare function buildMenuButton(spec: MenuButtonSpec, buttonStyle: ButtonStyle, style: MenuStyle, metrics: UIKitFontMetrics, layout: ButtonLayout, visual: MenuButtonVisual): {
|
|
212
|
+
rect: RectData;
|
|
213
|
+
text: UIKitTextItem;
|
|
214
|
+
lines: LineData[];
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* The draw data for an open popup: the panel, the highlight under
|
|
218
|
+
* `highlightIndex` (when that item is selectable), then per visible row the
|
|
219
|
+
* mark (check mark or radio dot when checked), the label, the shortcut and
|
|
220
|
+
* the separator rules, and on a scrollable popup an arrow on each strip that
|
|
221
|
+
* has rows beyond it.
|
|
222
|
+
*/
|
|
223
|
+
export declare function buildMenuPopup(items: readonly MenuItemSpec[], style: MenuStyle, metrics: UIKitFontMetrics, layout: MenuPopupLayout, highlightIndex: number): {
|
|
224
|
+
rects: RectData[];
|
|
225
|
+
lines: LineData[];
|
|
226
|
+
text: UIKitTextItem[];
|
|
227
|
+
};
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { UIKitOverlayFrame, UIKitOverlayRenderer } from '@niivue/niivue';
|
|
2
|
+
import { ButtonLayout, ButtonStyle } from './button';
|
|
3
|
+
import { UIKitInteractive, UIKitKeyEvent, UIKitRedrawSource } from './controls';
|
|
4
|
+
import { MenuBounds, MenuButtonSpec, MenuItemSpec, MenuPopupLayout, MenuStyle } from './menu';
|
|
5
|
+
import { UIKitFont } from './text/font';
|
|
6
|
+
export interface UIKitMenuOverlayOptions {
|
|
7
|
+
/** Called whenever the overlay needs another frame. A control layer fills it in. */
|
|
8
|
+
requestRedraw?: () => void;
|
|
9
|
+
/** Default button style for every menu; a spec's `buttonStyle` overrides per key. */
|
|
10
|
+
buttonStyle?: Partial<ButtonStyle>;
|
|
11
|
+
/** Default popup style for every menu; a spec's `style` overrides per key. */
|
|
12
|
+
style?: Partial<MenuStyle>;
|
|
13
|
+
/**
|
|
14
|
+
* Interpret spec positions and style lengths as CSS pixels and scale them by
|
|
15
|
+
* the frame's device pixel ratio, instead of canvas pixels.
|
|
16
|
+
*/
|
|
17
|
+
cssUnits?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare class UIKitMenuOverlay implements UIKitOverlayRenderer, UIKitInteractive {
|
|
20
|
+
readonly hoverCursor = "pointer";
|
|
21
|
+
private readonly font;
|
|
22
|
+
private readonly rects;
|
|
23
|
+
private readonly lines;
|
|
24
|
+
private readonly labels;
|
|
25
|
+
private readonly popupRects;
|
|
26
|
+
private readonly popupLines;
|
|
27
|
+
private readonly popupText;
|
|
28
|
+
private readonly entries;
|
|
29
|
+
private baseButtonStyle;
|
|
30
|
+
private baseStyle;
|
|
31
|
+
private readonly cssUnits;
|
|
32
|
+
private requestRedraw;
|
|
33
|
+
private layer;
|
|
34
|
+
private inLayer;
|
|
35
|
+
private scale;
|
|
36
|
+
private geometryDirty;
|
|
37
|
+
private popupDirty;
|
|
38
|
+
private bounds;
|
|
39
|
+
private openId;
|
|
40
|
+
private popup;
|
|
41
|
+
private highlight;
|
|
42
|
+
/** First visible row of a scrolled popup (the layout clamps it). */
|
|
43
|
+
private firstRow;
|
|
44
|
+
private wheelCarry;
|
|
45
|
+
/** The button the pointer went down on and still holds. */
|
|
46
|
+
private pressedId;
|
|
47
|
+
/** Whether the held press opened the menu (a release then keeps it open). */
|
|
48
|
+
private pressOpened;
|
|
49
|
+
private hoverId;
|
|
50
|
+
private focusedId;
|
|
51
|
+
constructor(font: UIKitFont, options?: UIKitMenuOverlayOptions);
|
|
52
|
+
/**
|
|
53
|
+
* Add a menu, or replace the one with the same id. A replacement takes the
|
|
54
|
+
* new spec's items and their `checked` states. An open menu stays open and
|
|
55
|
+
* is laid out again (it may have moved) unless the replacement disables it
|
|
56
|
+
* or changes its items' ids or kinds: then it closes.
|
|
57
|
+
*/
|
|
58
|
+
addMenu(spec: MenuButtonSpec): void;
|
|
59
|
+
/** Replace the whole menu set. */
|
|
60
|
+
setMenus(specs: readonly MenuButtonSpec[]): void;
|
|
61
|
+
removeMenu(id: string): void;
|
|
62
|
+
/** Patch one menu's spec. Items are kept unless the patch sets `items`. */
|
|
63
|
+
updateMenu(id: string, patch: Partial<Omit<MenuButtonSpec, 'id'>>): void;
|
|
64
|
+
setEnabled(id: string, enabled: boolean): void;
|
|
65
|
+
/** Replace a menu's items (closing it if open and the ids or kinds change). */
|
|
66
|
+
setItems(id: string, items: readonly MenuItemSpec[]): void;
|
|
67
|
+
/** The current items of a menu (with live `checked` states), or an empty list. */
|
|
68
|
+
getItems(id: string): readonly MenuItemSpec[];
|
|
69
|
+
/** A check or radio item's current state. */
|
|
70
|
+
isItemChecked(menuId: string, itemId: string): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Set a check or radio item's state from code. Fires no callbacks. Checking
|
|
73
|
+
* a radio unchecks the rest of its group; unchecking one leaves the group empty.
|
|
74
|
+
*/
|
|
75
|
+
setItemChecked(menuId: string, itemId: string, checked: boolean): void;
|
|
76
|
+
/** The ids of all menus, in draw order. */
|
|
77
|
+
get ids(): string[];
|
|
78
|
+
/** A menu button's box in spec units (canvas pixels, or CSS pixels with `cssUnits`). */
|
|
79
|
+
getLayout(id: string): ButtonLayout | null;
|
|
80
|
+
/** The open popup's panel and rows in spec units, or null when no menu is open. */
|
|
81
|
+
getPopupLayout(): MenuPopupLayout | null;
|
|
82
|
+
/** The id of the open menu, if any. */
|
|
83
|
+
get openMenu(): string | null;
|
|
84
|
+
isOpen(id: string): boolean;
|
|
85
|
+
/** The index of the highlighted item in the open menu, or -1. */
|
|
86
|
+
get highlightedIndex(): number;
|
|
87
|
+
/** The menu button with keyboard focus, if any. */
|
|
88
|
+
get focusedMenu(): string | null;
|
|
89
|
+
/** Close an open popup without choosing (the control layer's outside-press hook). */
|
|
90
|
+
dismiss(): void;
|
|
91
|
+
/** True while a popup is open: the overlay then wants every event first. */
|
|
92
|
+
isModal(): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* The area popups must stay inside, in canvas pixels. `drawOverlay` refreshes
|
|
95
|
+
* it from every frame; call it yourself when driving the overlay without one.
|
|
96
|
+
*/
|
|
97
|
+
setBounds(bounds: MenuBounds | null): void;
|
|
98
|
+
/**
|
|
99
|
+
* Open a menu's popup (closing any other) with `highlightIndex` highlighted,
|
|
100
|
+
* or nothing by default. Opening the already open menu only moves the highlight.
|
|
101
|
+
*/
|
|
102
|
+
open(id: string, highlightIndex?: number): void;
|
|
103
|
+
/** Close the open popup, if any, without activating anything. */
|
|
104
|
+
close(): void;
|
|
105
|
+
/** Give a menu button keyboard focus, or null to clear. Disabled buttons refuse. */
|
|
106
|
+
focus(id: string | null): void;
|
|
107
|
+
hitTest(x: number, y: number): boolean;
|
|
108
|
+
pointerDown(x: number, y: number): boolean;
|
|
109
|
+
pointerMove(x: number, y: number): boolean;
|
|
110
|
+
/** Activates the highlighted row when released over it; a release on the button keeps the menu open. */
|
|
111
|
+
pointerUp(x: number, y: number): boolean;
|
|
112
|
+
pointerCancel(): void;
|
|
113
|
+
/**
|
|
114
|
+
* While a popup is open the wheel scrolls it by rows (a trackpad's small
|
|
115
|
+
* deltas add up) and the row under the pointer takes the highlight. Every
|
|
116
|
+
* wheel turn is consumed while a menu is up; a closed menu ignores it.
|
|
117
|
+
*/
|
|
118
|
+
wheel(x: number, y: number, _deltaX: number, deltaY: number): boolean;
|
|
119
|
+
/**
|
|
120
|
+
* Open: arrows, Home and End move the highlight, Enter or Space activate it,
|
|
121
|
+
* Escape closes; every other key is swallowed while the menu is up. Closed
|
|
122
|
+
* and focused: Enter, Space or ArrowDown open the menu on its first item,
|
|
123
|
+
* ArrowUp on its last.
|
|
124
|
+
*/
|
|
125
|
+
keyDown(e: UIKitKeyEvent): boolean;
|
|
126
|
+
blur(): void;
|
|
127
|
+
bindLayer(layer: UIKitRedrawSource): void;
|
|
128
|
+
drawOverlay(frame: UIKitOverlayFrame): void;
|
|
129
|
+
/** Draw the open popup; a control layer calls this after every widget. */
|
|
130
|
+
drawPopup(frame: UIKitOverlayFrame): void;
|
|
131
|
+
/** Release GPU resources on both backends. */
|
|
132
|
+
destroy(): void;
|
|
133
|
+
private invalidate;
|
|
134
|
+
/**
|
|
135
|
+
* Scroll the open popup by `rows` (negative is up), drop a highlight that
|
|
136
|
+
* scrolled out of view, and re-highlight whatever row is now under (x, y).
|
|
137
|
+
*/
|
|
138
|
+
private scrollBy;
|
|
139
|
+
private setHighlight;
|
|
140
|
+
/** Apply an item's state change, fire the callbacks, and close the menu. */
|
|
141
|
+
private activate;
|
|
142
|
+
private scaled;
|
|
143
|
+
private layoutOf;
|
|
144
|
+
private popupLayout;
|
|
145
|
+
/** The topmost (last added) enabled menu button under a canvas point. */
|
|
146
|
+
private hitEntry;
|
|
147
|
+
private rebuild;
|
|
148
|
+
private rebuildPopup;
|
|
149
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { LineData } from './line';
|
|
2
|
+
import { RectData } from './rect';
|
|
3
|
+
import { ScrollWindow, UIKitBox } from './scroll';
|
|
4
|
+
import { UIKitFontMetrics } from './text/font';
|
|
5
|
+
import { RGBA } from './text/layout';
|
|
6
|
+
import { TextEditState } from './textEdit';
|
|
7
|
+
import { UIKitTextItem } from './textOverlay';
|
|
8
|
+
/** Every visual knob on a number input. All lengths are in canvas pixels. */
|
|
9
|
+
export interface NumberInputStyle {
|
|
10
|
+
/** Field color at rest. */
|
|
11
|
+
fill: RGBA;
|
|
12
|
+
/** Field color while the pointer hovers it. */
|
|
13
|
+
hoverFill: RGBA;
|
|
14
|
+
/** Field color when disabled. */
|
|
15
|
+
disabledFill: RGBA;
|
|
16
|
+
border: RGBA;
|
|
17
|
+
/** Border color while the field has keyboard focus. */
|
|
18
|
+
focusBorder: RGBA;
|
|
19
|
+
/** Border color while the typed text is not a number. */
|
|
20
|
+
invalidBorder: RGBA;
|
|
21
|
+
borderWidth: number;
|
|
22
|
+
radius: number;
|
|
23
|
+
textColor: RGBA;
|
|
24
|
+
disabledTextColor: RGBA;
|
|
25
|
+
/** Label and value em size. */
|
|
26
|
+
textSizePx: number;
|
|
27
|
+
/** Halo outline width around the label (it sits on the scene); 0 draws none. */
|
|
28
|
+
textOutlineWidth: number;
|
|
29
|
+
/** Halo color; null picks black or white for contrast with `textColor`. */
|
|
30
|
+
textOutlineColor: RGBA | null;
|
|
31
|
+
/** Space between the field's left border and the text. */
|
|
32
|
+
paddingX: number;
|
|
33
|
+
/** Space between the cap-height box and the field's top and bottom edges. */
|
|
34
|
+
paddingY: number;
|
|
35
|
+
/** Space between a label and the field. */
|
|
36
|
+
labelGap: number;
|
|
37
|
+
/** Field width when the spec gives none. */
|
|
38
|
+
fieldWidth: number;
|
|
39
|
+
/** Width of the spinner column (the up and down buttons) inside the field. */
|
|
40
|
+
spinnerWidth: number;
|
|
41
|
+
spinnerFill: RGBA;
|
|
42
|
+
spinnerHoverFill: RGBA;
|
|
43
|
+
spinnerPressedFill: RGBA;
|
|
44
|
+
/** Color of the lines separating the spinner from the text and its two halves. */
|
|
45
|
+
dividerColor: RGBA;
|
|
46
|
+
/** Spinner chevron width, stroke width and color. */
|
|
47
|
+
arrowSize: number;
|
|
48
|
+
arrowWidth: number;
|
|
49
|
+
arrowColor: RGBA;
|
|
50
|
+
caretColor: RGBA;
|
|
51
|
+
caretWidth: number;
|
|
52
|
+
/** Color behind selected text. */
|
|
53
|
+
selectionFill: RGBA;
|
|
54
|
+
}
|
|
55
|
+
export declare const DEFAULT_NUMBER_INPUT_STYLE: NumberInputStyle;
|
|
56
|
+
export interface NumberInputSpec {
|
|
57
|
+
/** Stable key: the handle for updates, pointer tracking and the callbacks. */
|
|
58
|
+
id: string;
|
|
59
|
+
/** Optional label, drawn to the left of the field. */
|
|
60
|
+
label?: string;
|
|
61
|
+
/** Top-left corner of the whole control (label included), in canvas pixels. */
|
|
62
|
+
x: number;
|
|
63
|
+
y: number;
|
|
64
|
+
/** Field width (the label adds to it). Default: the style's `fieldWidth`. */
|
|
65
|
+
width?: number;
|
|
66
|
+
/** Lower bound; omit for none. */
|
|
67
|
+
min?: number;
|
|
68
|
+
/** Upper bound; omit for none. */
|
|
69
|
+
max?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Value granularity, counted from `min` (or 0). Omit for any number; the
|
|
72
|
+
* spinner and arrow keys then move by 1.
|
|
73
|
+
*/
|
|
74
|
+
step?: number;
|
|
75
|
+
/** Initial value (clamped and snapped). */
|
|
76
|
+
value: number;
|
|
77
|
+
/** Formatter for the displayed value. Default: the step's decimal places. */
|
|
78
|
+
format?: (value: number) => string;
|
|
79
|
+
/**
|
|
80
|
+
* Parser for typed text, returning NaN for text that is not a number.
|
|
81
|
+
* Default: `Number` on the trimmed text, with empty text invalid.
|
|
82
|
+
*/
|
|
83
|
+
parse?: (text: string) => number;
|
|
84
|
+
/** A disabled input draws dimmed and ignores input. Default true. */
|
|
85
|
+
enabled?: boolean;
|
|
86
|
+
/** Per-input overrides of the overlay's default style. */
|
|
87
|
+
style?: Partial<NumberInputStyle>;
|
|
88
|
+
/**
|
|
89
|
+
* Fired while the user edits: for every keystroke that leaves a valid
|
|
90
|
+
* number different from the last one reported, and for every step.
|
|
91
|
+
*/
|
|
92
|
+
onInput?: (value: number, id: string) => void;
|
|
93
|
+
/**
|
|
94
|
+
* Fired once when a changed value is committed: on Enter, on losing focus,
|
|
95
|
+
* and on every step (spinner, arrow keys, wheel).
|
|
96
|
+
*/
|
|
97
|
+
onChange?: (value: number, id: string) => void;
|
|
98
|
+
}
|
|
99
|
+
/** The control's boxes in canvas pixels. */
|
|
100
|
+
export interface NumberInputLayout {
|
|
101
|
+
/** The whole control: label and field. */
|
|
102
|
+
x: number;
|
|
103
|
+
y: number;
|
|
104
|
+
width: number;
|
|
105
|
+
height: number;
|
|
106
|
+
/** The bordered field. */
|
|
107
|
+
field: UIKitBox;
|
|
108
|
+
/** Where the text, caret and selection draw: the field left of the spinner. */
|
|
109
|
+
textArea: UIKitBox;
|
|
110
|
+
spinUp: UIKitBox;
|
|
111
|
+
spinDown: UIKitBox;
|
|
112
|
+
/** Baseline of the label and the field text. */
|
|
113
|
+
baseline: number;
|
|
114
|
+
}
|
|
115
|
+
/** The transient state that changes how a number input looks. */
|
|
116
|
+
export interface NumberInputVisual {
|
|
117
|
+
/** The text shown: the formatted value, or the edit in progress. */
|
|
118
|
+
text: string;
|
|
119
|
+
/** The edit in progress while the input has keyboard focus, else null. */
|
|
120
|
+
edit: TextEditState | null;
|
|
121
|
+
/** Index of the first glyph the text area shows (the overlay keeps it between frames). */
|
|
122
|
+
firstGlyph: number;
|
|
123
|
+
/** False while the text is not a number. */
|
|
124
|
+
valid: boolean;
|
|
125
|
+
hover: boolean;
|
|
126
|
+
/** The spinner half under the pointer: 1 up, -1 down, 0 neither. */
|
|
127
|
+
spinHover: 1 | -1 | 0;
|
|
128
|
+
/** The spinner half held down. */
|
|
129
|
+
spinPressed: 1 | -1 | 0;
|
|
130
|
+
enabled: boolean;
|
|
131
|
+
}
|
|
132
|
+
export declare function resolveNumberInputStyle(base: NumberInputStyle, override?: Partial<NumberInputStyle>): NumberInputStyle;
|
|
133
|
+
type Range = Pick<NumberInputSpec, 'min' | 'max' | 'step'>;
|
|
134
|
+
/** Clamp `value` into the spec's bounds and snap it to its step grid. NaN clamps to the low end, or 0. */
|
|
135
|
+
export declare function snapNumberInput(spec: Range, value: number): number;
|
|
136
|
+
/** Move `value` by one step (ten with `big`) in `direction`, snapped. */
|
|
137
|
+
export declare function stepNumberInput(spec: Range, value: number, direction: 1 | -1, big?: boolean): number;
|
|
138
|
+
/** The displayed text for a value: the spec's formatter, else the step's precision. */
|
|
139
|
+
export declare function formatNumberInput(spec: Pick<NumberInputSpec, 'step' | 'format'>, value: number): string;
|
|
140
|
+
/** The number typed text means, or NaN when it is not one. */
|
|
141
|
+
export declare function parseNumberInput(spec: Pick<NumberInputSpec, 'parse'>, text: string): number;
|
|
142
|
+
/** True for a character a number field accepts from the keyboard. */
|
|
143
|
+
export declare function acceptNumberChar(ch: string): boolean;
|
|
144
|
+
/** The control's boxes for a spec. */
|
|
145
|
+
export declare function layoutNumberInput(spec: NumberInputSpec, style: NumberInputStyle, metrics: UIKitFontMetrics): NumberInputLayout;
|
|
146
|
+
/** Scale every length in a spec and style by `k` (CSS pixels to canvas pixels). */
|
|
147
|
+
export declare function scaleNumberInput(spec: NumberInputSpec, style: NumberInputStyle, k: number): {
|
|
148
|
+
spec: NumberInputSpec;
|
|
149
|
+
style: NumberInputStyle;
|
|
150
|
+
};
|
|
151
|
+
/** True when canvas point (px, py) lies inside the field (the label is not interactive). */
|
|
152
|
+
export declare function numberInputContains(layout: NumberInputLayout, px: number, py: number): boolean;
|
|
153
|
+
/** The spinner half under a canvas point: 1 up, -1 down, 0 neither. */
|
|
154
|
+
export declare function numberInputSpinAt(layout: NumberInputLayout, px: number, py: number): 1 | -1 | 0;
|
|
155
|
+
/**
|
|
156
|
+
* The run of glyphs the text area shows, starting at `firstGlyph` but
|
|
157
|
+
* scrolled the least distance that keeps `caret` in view.
|
|
158
|
+
*/
|
|
159
|
+
export declare function numberInputTextWindow(layout: NumberInputLayout, style: NumberInputStyle, metrics: UIKitFontMetrics, text: string, firstGlyph: number, caret: number): ScrollWindow;
|
|
160
|
+
/** The caret index for a canvas x over text shown from `firstGlyph`. */
|
|
161
|
+
export declare function numberInputCaretAt(layout: NumberInputLayout, style: NumberInputStyle, metrics: UIKitFontMetrics, text: string, firstGlyph: number, px: number): number;
|
|
162
|
+
/**
|
|
163
|
+
* The draw data for one number input in one visual state: the field, the
|
|
164
|
+
* spinner with its chevrons, the selection, the visible run of text, the
|
|
165
|
+
* caret and the label.
|
|
166
|
+
*/
|
|
167
|
+
export declare function buildNumberInput(spec: NumberInputSpec, style: NumberInputStyle, layout: NumberInputLayout, metrics: UIKitFontMetrics, visual: NumberInputVisual): {
|
|
168
|
+
rects: RectData[];
|
|
169
|
+
lines: LineData[];
|
|
170
|
+
text: UIKitTextItem[];
|
|
171
|
+
};
|
|
172
|
+
export {};
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { UIKitOverlayFrame, UIKitOverlayRenderer } from '@niivue/niivue';
|
|
2
|
+
import { UIKitInteractive, UIKitKeyEvent, UIKitRedrawSource } from './controls';
|
|
3
|
+
import { NumberInputLayout, NumberInputSpec, NumberInputStyle } from './numberInput';
|
|
4
|
+
import { UIKitFont } from './text/font';
|
|
5
|
+
export interface UIKitNumberInputOverlayOptions {
|
|
6
|
+
/** Called whenever the overlay needs another frame. A control layer fills it in. */
|
|
7
|
+
requestRedraw?: () => void;
|
|
8
|
+
/** Default style for every input; a spec's `style` overrides per key. */
|
|
9
|
+
style?: Partial<NumberInputStyle>;
|
|
10
|
+
/**
|
|
11
|
+
* Interpret spec positions and style lengths as CSS pixels and scale them by
|
|
12
|
+
* the frame's device pixel ratio, instead of canvas pixels.
|
|
13
|
+
*/
|
|
14
|
+
cssUnits?: boolean;
|
|
15
|
+
/** Wheel pixels per step over a focused field. Default 40. */
|
|
16
|
+
wheelStepPx?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare class UIKitNumberInputOverlay implements UIKitOverlayRenderer, UIKitInteractive {
|
|
19
|
+
private readonly font;
|
|
20
|
+
private readonly rects;
|
|
21
|
+
private readonly lines;
|
|
22
|
+
private readonly labels;
|
|
23
|
+
private readonly entries;
|
|
24
|
+
private baseStyle;
|
|
25
|
+
private readonly cssUnits;
|
|
26
|
+
private readonly wheelCarry;
|
|
27
|
+
private requestRedraw;
|
|
28
|
+
private layer;
|
|
29
|
+
private scale;
|
|
30
|
+
private geometryDirty;
|
|
31
|
+
private hoverId;
|
|
32
|
+
private cursor;
|
|
33
|
+
private focusedId;
|
|
34
|
+
/** The held press: which input and what part of it. */
|
|
35
|
+
private pressId;
|
|
36
|
+
private pressPart;
|
|
37
|
+
constructor(font: UIKitFont, options?: UIKitNumberInputOverlayOptions);
|
|
38
|
+
/** CSS cursor for the hovered part: a text beam over the field, a pointer over the spinner. */
|
|
39
|
+
get hoverCursor(): string;
|
|
40
|
+
/** Replace the default style for every input (per-spec overrides still win). */
|
|
41
|
+
setDefaultStyle(style: Partial<NumberInputStyle>): void;
|
|
42
|
+
/**
|
|
43
|
+
* Add an input, or replace the one with the same id. The spec's `value` is
|
|
44
|
+
* clamped and snapped; a replacement takes the new spec's value and ends
|
|
45
|
+
* any edit in progress.
|
|
46
|
+
*/
|
|
47
|
+
addNumberInput(spec: NumberInputSpec): void;
|
|
48
|
+
/** Replace the whole input set. */
|
|
49
|
+
setNumberInputs(specs: readonly NumberInputSpec[]): void;
|
|
50
|
+
removeNumberInput(id: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Patch one input's spec (label, position, range, step, enabled, ...). The
|
|
53
|
+
* committed value is kept (re-snapped to the new range) unless the patch
|
|
54
|
+
* sets `value`.
|
|
55
|
+
*/
|
|
56
|
+
updateNumberInput(id: string, patch: Partial<Omit<NumberInputSpec, 'id'>>): void;
|
|
57
|
+
setEnabled(id: string, enabled: boolean): void;
|
|
58
|
+
/** Set an input's value from code (clamped and snapped). Fires neither callback. */
|
|
59
|
+
setValue(id: string, value: number): void;
|
|
60
|
+
/** The committed value. */
|
|
61
|
+
getValue(id: string): number | undefined;
|
|
62
|
+
/** The text the field shows: the edit in progress, or the formatted value. */
|
|
63
|
+
getText(id: string): string | undefined;
|
|
64
|
+
/** The ids of all inputs, in draw order. */
|
|
65
|
+
get ids(): string[];
|
|
66
|
+
/** An input's boxes in spec units (canvas pixels, or CSS pixels with `cssUnits`). */
|
|
67
|
+
getLayout(id: string): NumberInputLayout | null;
|
|
68
|
+
/** The input with keyboard focus (being edited), if any. */
|
|
69
|
+
get focusedInput(): string | null;
|
|
70
|
+
/**
|
|
71
|
+
* Give an input keyboard focus with its whole number selected, or null to
|
|
72
|
+
* clear. Leaving an input commits its edit. Disabled inputs refuse.
|
|
73
|
+
*/
|
|
74
|
+
focus(id: string | null): void;
|
|
75
|
+
hitTest(x: number, y: number): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* On the spinner: steps and commits. On the text: focuses (selecting all),
|
|
78
|
+
* or places the caret when already focused, and starts a drag selection.
|
|
79
|
+
*/
|
|
80
|
+
pointerDown(x: number, y: number): boolean;
|
|
81
|
+
pointerMove(x: number, y: number): boolean;
|
|
82
|
+
pointerUp(x: number, y: number): boolean;
|
|
83
|
+
pointerCancel(): void;
|
|
84
|
+
/**
|
|
85
|
+
* Editing keys change the text; Enter commits; Escape reverts; ArrowUp and
|
|
86
|
+
* ArrowDown step (Shift: ten steps), as do PageUp and PageDown.
|
|
87
|
+
*/
|
|
88
|
+
/** Enter commits the field and, in a dialog, submits it. */
|
|
89
|
+
submitsForm(e: UIKitKeyEvent): boolean;
|
|
90
|
+
keyDown(e: UIKitKeyEvent): boolean;
|
|
91
|
+
/** The wheel over a focused field steps it (the page scrolls elsewhere). */
|
|
92
|
+
wheel(x: number, y: number, _deltaX: number, deltaY: number): boolean;
|
|
93
|
+
/** Commits the edit in progress and drops focus. */
|
|
94
|
+
blur(): void;
|
|
95
|
+
bindLayer(layer: UIKitRedrawSource): void;
|
|
96
|
+
drawOverlay(frame: UIKitOverlayFrame): void;
|
|
97
|
+
/** Release GPU resources on both backends. */
|
|
98
|
+
destroy(): void;
|
|
99
|
+
private invalidate;
|
|
100
|
+
private display;
|
|
101
|
+
/** The edit state for a freshly focused (or reverted) input: all selected. */
|
|
102
|
+
private freshEdit;
|
|
103
|
+
/** The typed text as a snapped value, or NaN while it is not a number. */
|
|
104
|
+
private typedValue;
|
|
105
|
+
/** Fire `onInput` when the typed number differs from the last one reported. */
|
|
106
|
+
private reportInput;
|
|
107
|
+
/**
|
|
108
|
+
* Commit the edit: a valid number becomes the value (firing `onChange` if
|
|
109
|
+
* it changed), invalid text reverts. The text is re-formatted and, when the
|
|
110
|
+
* input stays focused, selected again.
|
|
111
|
+
*/
|
|
112
|
+
private commit;
|
|
113
|
+
/** Step from the typed number (or the value when the text is invalid) and commit. */
|
|
114
|
+
private step;
|
|
115
|
+
/** The caret for a drag: a pointer left of the shown text pulls one hidden glyph into view. */
|
|
116
|
+
private dragCaretAt;
|
|
117
|
+
private caretAt;
|
|
118
|
+
/**
|
|
119
|
+
* Settle the scroll position for the next frame: the stored first glyph,
|
|
120
|
+
* moved the least distance that keeps the caret in view. Every caret move
|
|
121
|
+
* (key edit, press, drag) settles, so `firstGlyph` is always the window
|
|
122
|
+
* the next frame shows and pointer math can read it as is; `rebuild` settles
|
|
123
|
+
* again for caret moves that bypass this (focus, step, commit).
|
|
124
|
+
*/
|
|
125
|
+
private firstShown;
|
|
126
|
+
private scaled;
|
|
127
|
+
private layoutOf;
|
|
128
|
+
/** The topmost (last added) enabled input whose field is under a canvas point. */
|
|
129
|
+
private hitEntry;
|
|
130
|
+
private rebuild;
|
|
131
|
+
}
|