@react-x11/components 0.4.0 → 0.5.0
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 +125 -4
- package/dist/charts/components.d.ts +3 -1
- package/dist/charts/components.d.ts.map +1 -1
- package/dist/charts/components.js +8 -4
- package/dist/charts/components.js.map +1 -1
- package/dist/charts/node.d.ts +13 -6
- package/dist/charts/node.d.ts.map +1 -1
- package/dist/charts/node.js +75 -24
- package/dist/charts/node.js.map +1 -1
- package/dist/charts/render.d.ts +4 -0
- package/dist/charts/render.d.ts.map +1 -1
- package/dist/charts/render.js +11 -6
- package/dist/charts/render.js.map +1 -1
- package/dist/code-editor/node.d.ts +47 -16
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +121 -51
- package/dist/code-editor/node.js.map +1 -1
- package/dist/flow/draw.d.ts +19 -1
- package/dist/flow/draw.d.ts.map +1 -1
- package/dist/flow/draw.js +64 -35
- package/dist/flow/draw.js.map +1 -1
- package/dist/flow/index.d.ts.map +1 -1
- package/dist/flow/index.js +24 -8
- package/dist/flow/index.js.map +1 -1
- package/dist/flow/model.d.ts +4 -2
- package/dist/flow/model.d.ts.map +1 -1
- package/dist/flow/model.js +4 -2
- package/dist/flow/model.js.map +1 -1
- package/dist/flow/node.d.ts +40 -9
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +142 -53
- package/dist/flow/node.js.map +1 -1
- package/dist/flow/types.d.ts +46 -21
- package/dist/flow/types.d.ts.map +1 -1
- package/dist/formula/index.d.ts +4 -2
- package/dist/formula/index.d.ts.map +1 -1
- package/dist/formula/index.js.map +1 -1
- package/dist/formula/node.d.ts +5 -1
- package/dist/formula/node.d.ts.map +1 -1
- package/dist/formula/node.js +19 -3
- package/dist/formula/node.js.map +1 -1
- package/dist/html/controls.d.ts +4 -1
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +11 -5
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +18 -8
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/parse.d.ts +15 -10
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +41 -16
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +3 -0
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +3 -1
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/values.d.ts +14 -1
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +11 -8
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +1 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +12 -5
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +7 -4
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +22 -1
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +22 -8
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +97 -15
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +3 -0
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +2 -2
- package/dist/html/paint.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -1
- package/dist/maps/clip.d.ts +43 -0
- package/dist/maps/clip.d.ts.map +1 -0
- package/dist/maps/clip.js +171 -0
- package/dist/maps/clip.js.map +1 -0
- package/dist/maps/gzip.d.ts +15 -0
- package/dist/maps/gzip.d.ts.map +1 -0
- package/dist/maps/gzip.js +56 -0
- package/dist/maps/gzip.js.map +1 -0
- package/dist/maps/index.d.ts +55 -0
- package/dist/maps/index.d.ts.map +1 -0
- package/dist/maps/index.js +141 -0
- package/dist/maps/index.js.map +1 -0
- package/dist/maps/labels.d.ts +123 -0
- package/dist/maps/labels.d.ts.map +1 -0
- package/dist/maps/labels.js +323 -0
- package/dist/maps/labels.js.map +1 -0
- package/dist/maps/mvt.d.ts +244 -0
- package/dist/maps/mvt.d.ts.map +1 -0
- package/dist/maps/mvt.js +820 -0
- package/dist/maps/mvt.js.map +1 -0
- package/dist/maps/node.d.ts +278 -0
- package/dist/maps/node.d.ts.map +1 -0
- package/dist/maps/node.js +1459 -0
- package/dist/maps/node.js.map +1 -0
- package/dist/maps/overlay.d.ts +157 -0
- package/dist/maps/overlay.d.ts.map +1 -0
- package/dist/maps/overlay.js +472 -0
- package/dist/maps/overlay.js.map +1 -0
- package/dist/maps/paint.d.ts +226 -0
- package/dist/maps/paint.d.ts.map +1 -0
- package/dist/maps/paint.js +705 -0
- package/dist/maps/paint.js.map +1 -0
- package/dist/maps/proj.d.ts +281 -0
- package/dist/maps/proj.d.ts.map +1 -0
- package/dist/maps/proj.js +451 -0
- package/dist/maps/proj.js.map +1 -0
- package/dist/maps/sources.d.ts +256 -0
- package/dist/maps/sources.d.ts.map +1 -0
- package/dist/maps/sources.js +210 -0
- package/dist/maps/sources.js.map +1 -0
- package/dist/maps/style.d.ts +141 -0
- package/dist/maps/style.d.ts.map +1 -0
- package/dist/maps/style.js +147 -0
- package/dist/maps/style.js.map +1 -0
- package/dist/maps/styles.d.ts +82 -0
- package/dist/maps/styles.d.ts.map +1 -0
- package/dist/maps/styles.js +898 -0
- package/dist/maps/styles.js.map +1 -0
- package/dist/maps/tiles.d.ts +283 -0
- package/dist/maps/tiles.d.ts.map +1 -0
- package/dist/maps/tiles.js +469 -0
- package/dist/maps/tiles.js.map +1 -0
- package/dist/maps/types.d.ts +244 -0
- package/dist/maps/types.d.ts.map +1 -0
- package/dist/maps/types.js +2 -0
- package/dist/maps/types.js.map +1 -0
- package/dist/reorder/hx.d.ts +18 -0
- package/dist/reorder/hx.d.ts.map +1 -0
- package/dist/reorder/hx.js +10 -0
- package/dist/reorder/hx.js.map +1 -0
- package/dist/reorder/index.d.ts +314 -0
- package/dist/reorder/index.d.ts.map +1 -0
- package/dist/reorder/index.js +1539 -0
- package/dist/reorder/index.js.map +1 -0
- package/dist/reorder/model.d.ts +107 -0
- package/dist/reorder/model.d.ts.map +1 -0
- package/dist/reorder/model.js +162 -0
- package/dist/reorder/model.js.map +1 -0
- package/dist/richtext/node.d.ts +15 -5
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +40 -11
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +35 -13
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +83 -27
- package/dist/richtext/runs.js.map +1 -1
- package/dist/tabs/index.d.ts +17 -0
- package/dist/tabs/index.d.ts.map +1 -1
- package/dist/tabs/index.js +849 -186
- package/dist/tabs/index.js.map +1 -1
- package/dist/terminal/vt/fonts.d.ts +45 -15
- package/dist/terminal/vt/fonts.d.ts.map +1 -1
- package/dist/terminal/vt/fonts.js +89 -19
- package/dist/terminal/vt/fonts.js.map +1 -1
- package/dist/terminal/vt/node.d.ts +42 -0
- package/dist/terminal/vt/node.d.ts.map +1 -1
- package/dist/terminal/vt/node.js +95 -11
- package/dist/terminal/vt/node.js.map +1 -1
- package/dist/terminal/vt/renderer.d.ts.map +1 -1
- package/dist/terminal/vt/renderer.js +13 -3
- package/dist/terminal/vt/renderer.js.map +1 -1
- package/package.json +13 -3
- package/src/charts/components.ts +14 -7
- package/src/charts/node.ts +81 -26
- package/src/charts/render.ts +16 -6
- package/src/code-editor/node.ts +147 -53
- package/src/flow/draw.ts +104 -35
- package/src/flow/index.ts +33 -13
- package/src/flow/model.ts +4 -2
- package/src/flow/node.ts +158 -59
- package/src/flow/types.ts +46 -21
- package/src/formula/index.ts +4 -2
- package/src/formula/node.ts +24 -5
- package/src/html/controls.ts +4 -1
- package/src/html/css/cascade.ts +18 -7
- package/src/html/css/parse.ts +46 -21
- package/src/html/css/style.ts +6 -1
- package/src/html/css/values.ts +25 -9
- package/src/html/index.ts +1 -0
- package/src/html/layout/boxes.ts +19 -9
- package/src/html/layout/inline.ts +23 -1
- package/src/html/node.ts +110 -17
- package/src/html/paint.ts +17 -2
- package/src/index.ts +98 -0
- package/src/maps/clip.ts +184 -0
- package/src/maps/gzip.ts +66 -0
- package/src/maps/index.ts +316 -0
- package/src/maps/labels.ts +442 -0
- package/src/maps/mvt.ts +857 -0
- package/src/maps/node.ts +1819 -0
- package/src/maps/overlay.ts +639 -0
- package/src/maps/paint.ts +978 -0
- package/src/maps/proj.ts +641 -0
- package/src/maps/sources.ts +425 -0
- package/src/maps/style.ts +309 -0
- package/src/maps/styles.ts +1040 -0
- package/src/maps/tiles.ts +676 -0
- package/src/maps/types.ts +260 -0
- package/src/reorder/hx.ts +31 -0
- package/src/reorder/index.ts +2223 -0
- package/src/reorder/model.ts +232 -0
- package/src/richtext/node.ts +49 -14
- package/src/richtext/runs.ts +115 -30
- package/src/tabs/index.ts +1040 -204
- package/src/terminal/vt/fonts.ts +128 -36
- package/src/terminal/vt/node.ts +105 -12
- package/src/terminal/vt/renderer.ts +13 -3
package/src/code-editor/node.ts
CHANGED
|
@@ -17,6 +17,21 @@
|
|
|
17
17
|
// `onKeyDown` handler that consumes the keys the popup owns (arrows,
|
|
18
18
|
// Return, Tab-accept, Escape-close) and, by consuming them, keeps this
|
|
19
19
|
// node's default action from also acting on them.
|
|
20
|
+
//
|
|
21
|
+
// **Units.** Everything this node measures is a device-pixel quantity:
|
|
22
|
+
// `this.style` and `this.abs` arrive already multiplied by the display
|
|
23
|
+
// scale (react-x11's docs/scale.md), so the layouts `app.fonts` shapes at
|
|
24
|
+
// that `fontSize`, the line height and column width read off them, the
|
|
25
|
+
// scroll offsets and everything painted are all on the panel's grid. The
|
|
26
|
+
// pointer is the exception — a synthetic event's `x`/`y` are logical — so
|
|
27
|
+
// it is converted on the way in (`_devicePoint`), and the geometry the
|
|
28
|
+
// handle answers is converted on the way out (`caretRect`, `metrics`,
|
|
29
|
+
// `measureText`, `scrollBy`), because a `<popup anchor>` and a style length
|
|
30
|
+
// are logical. The paint constants below never pass through a style, so
|
|
31
|
+
// each use multiplies by `_scale`. At 1x every conversion is the identity,
|
|
32
|
+
// which is how a press that handed `ev.x` to a hit test measured in glyph
|
|
33
|
+
// metrics passed every test and then landed at half the pointer's distance
|
|
34
|
+
// the day a native backend reported a retina panel.
|
|
20
35
|
import { CARET_BLINK_MS, Node } from 'react-x11/node';
|
|
21
36
|
import type {
|
|
22
37
|
Context2D,
|
|
@@ -82,6 +97,9 @@ export const ELEMENT = 'codeeditor';
|
|
|
82
97
|
|
|
83
98
|
const UNDO_LIMIT = 200;
|
|
84
99
|
const DEFAULT_ROWS = 6;
|
|
100
|
+
// Logical pixels, like every length an app writes. None of these pass
|
|
101
|
+
// through a style, so each use multiplies by `_scale` — the way core's own
|
|
102
|
+
// caret width does (see "Units" above).
|
|
85
103
|
const PREFERRED_WIDTH = 360;
|
|
86
104
|
const GUTTER_PAD = 8; // each side of the line numbers
|
|
87
105
|
const CARET_MARGIN = 24; // keep this many px visible beside the caret
|
|
@@ -119,7 +137,10 @@ export interface CodeEditorHandle {
|
|
|
119
137
|
toggleLineComment(): void;
|
|
120
138
|
copySelection(selection?: string): void;
|
|
121
139
|
pasteFrom(selection?: string): void;
|
|
140
|
+
/** Logical pixels, like a style length; true when the offset moved. */
|
|
122
141
|
scrollBy(dx: number, dy: number): boolean;
|
|
142
|
+
/** The caret in the editor's own coordinates, logical pixels — what a
|
|
143
|
+
* `<popup anchor={{ at }}>` takes. */
|
|
123
144
|
caretRect(): { x: number; y: number; height: number };
|
|
124
145
|
focus(): void;
|
|
125
146
|
blur(): void;
|
|
@@ -274,11 +295,17 @@ export interface EditorKeyEvent {
|
|
|
274
295
|
}
|
|
275
296
|
|
|
276
297
|
export interface EditorMouseEvent {
|
|
298
|
+
/** Window-relative, in logical pixels — what a synthetic event carries. */
|
|
277
299
|
x: number;
|
|
278
300
|
y: number;
|
|
279
301
|
button?: number;
|
|
280
302
|
detail?: number;
|
|
281
303
|
shiftKey: boolean;
|
|
304
|
+
/** The X event underneath, when there is one. Its `x`/`y` are the device
|
|
305
|
+
* pixels `abs` is in, and the editor reads those when present — core's
|
|
306
|
+
* own idiom for its fields. A caller synthesizing an event leaves it out,
|
|
307
|
+
* and the logical `x`/`y` are multiplied by the display scale instead. */
|
|
308
|
+
nativeEvent?: unknown;
|
|
282
309
|
capturePointer?(): void;
|
|
283
310
|
releasePointer?(): void;
|
|
284
311
|
preventDefault?(): void;
|
|
@@ -360,8 +387,10 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
360
387
|
*/
|
|
361
388
|
override measureContent({ width }: MeasureConstraints): MeasuredSize {
|
|
362
389
|
const rows = Math.max(1, Number(this.props.rows ?? DEFAULT_ROWS));
|
|
390
|
+
// Device pixels, like the constraints: the line height already is, and
|
|
391
|
+
// the preferred width is a constant that never passed through a style.
|
|
363
392
|
return {
|
|
364
|
-
width: Math.min(PREFERRED_WIDTH, width),
|
|
393
|
+
width: Math.min(PREFERRED_WIDTH * this._scale, width),
|
|
365
394
|
height: Math.ceil(this._lineHeight() * rows),
|
|
366
395
|
};
|
|
367
396
|
}
|
|
@@ -451,6 +480,33 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
451
480
|
|
|
452
481
|
// --- metrics & style -----------------------------------------------------
|
|
453
482
|
|
|
483
|
+
/**
|
|
484
|
+
* Device pixels per logical pixel — the display scale this editor's
|
|
485
|
+
* window resolved to (react-x11's docs/scale.md): `1` on an ordinary
|
|
486
|
+
* display, `2` on a retina panel, fractional on a desktop set to 1.5.
|
|
487
|
+
* Resolved once by `createRoot` and constant for the node's life; `1`
|
|
488
|
+
* when the node reports nothing usable, the guard the terminal keeps.
|
|
489
|
+
*/
|
|
490
|
+
private get _scale(): number {
|
|
491
|
+
return this.scale > 0 ? this.scale : 1;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* A pointer event's position in device pixels — the unit `abs`, the
|
|
496
|
+
* content rect and every glyph metric here are in. The X event underneath
|
|
497
|
+
* carries it already; a caller that synthesized the event without one
|
|
498
|
+
* wrote logical coordinates, which multiply.
|
|
499
|
+
*/
|
|
500
|
+
private _devicePoint(ev: EditorMouseEvent): { x: number; y: number } {
|
|
501
|
+
const native = ev.nativeEvent as
|
|
502
|
+
{ x?: unknown; y?: unknown } | null | undefined;
|
|
503
|
+
const s = this._scale;
|
|
504
|
+
return {
|
|
505
|
+
x: typeof native?.x === 'number' ? native.x : ev.x * s,
|
|
506
|
+
y: typeof native?.y === 'number' ? native.y : ev.y * s,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
|
|
454
510
|
private _textStyle(): Record<string, unknown> {
|
|
455
511
|
const s = this.style as Record<string, unknown>;
|
|
456
512
|
return {
|
|
@@ -501,9 +557,9 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
501
557
|
|
|
502
558
|
/**
|
|
503
559
|
* The content box: `abs` inset by border and padding, computed from the
|
|
504
|
-
* flattened style
|
|
505
|
-
* `contentBox()`; a public
|
|
506
|
-
* filed as react-x11#254.
|
|
560
|
+
* flattened style — device pixels, since both of those are. [react-x11
|
|
561
|
+
* gap] built-in fields read an internal `contentBox()`; a public
|
|
562
|
+
* equivalent would remove this arithmetic — filed as react-x11#254.
|
|
507
563
|
*/
|
|
508
564
|
private _contentRect(): {
|
|
509
565
|
x: number;
|
|
@@ -534,7 +590,7 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
534
590
|
if (!this.props.lineNumbers) return 0;
|
|
535
591
|
this._metrics();
|
|
536
592
|
const digits = Math.max(2, String(this._lines.length).length);
|
|
537
|
-
return Math.ceil(digits * this._charW) + GUTTER_PAD * 2;
|
|
593
|
+
return Math.ceil(digits * this._charW) + GUTTER_PAD * this._scale * 2;
|
|
538
594
|
}
|
|
539
595
|
|
|
540
596
|
private _tabSize(): number {
|
|
@@ -634,7 +690,7 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
634
690
|
return entry;
|
|
635
691
|
}
|
|
636
692
|
|
|
637
|
-
/** Caret x (
|
|
693
|
+
/** Caret x (device pixels from the text origin) for a position. */
|
|
638
694
|
private _caretX(pos: Position): number {
|
|
639
695
|
const entry = this._lineEntry(pos.line);
|
|
640
696
|
if (!entry.layout) return 0;
|
|
@@ -642,7 +698,7 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
642
698
|
return entry.layout.caretPosition(utf16ToCp(entry.display, disp)).x;
|
|
643
699
|
}
|
|
644
700
|
|
|
645
|
-
/** Window coordinates → document position. */
|
|
701
|
+
/** Window coordinates, device pixels → document position. */
|
|
646
702
|
private _posAt(x: number, y: number): Position {
|
|
647
703
|
this._metrics();
|
|
648
704
|
const content = this._contentRect();
|
|
@@ -661,7 +717,9 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
661
717
|
return clampPos(this._lines, { line, ch: entry.map.toRaw(disp) });
|
|
662
718
|
}
|
|
663
719
|
|
|
664
|
-
/** Font facts the completion popup sizes itself with
|
|
720
|
+
/** Font facts the completion popup sizes itself with — logical pixels,
|
|
721
|
+
* because they become style lengths (`height`, `fontSize`) on its rows.
|
|
722
|
+
* The face was shaped at the device size, so a 2x panel divides. */
|
|
665
723
|
metrics(): {
|
|
666
724
|
lineHeight: number;
|
|
667
725
|
charWidth: number;
|
|
@@ -670,37 +728,47 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
670
728
|
} {
|
|
671
729
|
this._metrics();
|
|
672
730
|
const s = this._textStyle();
|
|
731
|
+
const scale = this._scale;
|
|
673
732
|
return {
|
|
674
|
-
lineHeight: this._lineH,
|
|
675
|
-
charWidth: this._charW,
|
|
733
|
+
lineHeight: this._lineH / scale,
|
|
734
|
+
charWidth: this._charW / scale,
|
|
676
735
|
family: String(s.family),
|
|
677
|
-
size: Number(s.size),
|
|
736
|
+
size: Number(s.size) / scale,
|
|
678
737
|
};
|
|
679
738
|
}
|
|
680
739
|
|
|
681
|
-
/** Text width in this editor's font. The built-in
|
|
682
|
-
* longer needs it — a `<popup anchor>` measures its
|
|
683
|
-
* laying out its own overlay against the text still
|
|
740
|
+
/** Text width in this editor's font, in logical pixels. The built-in
|
|
741
|
+
* completion popup no longer needs it — a `<popup anchor>` measures its
|
|
742
|
+
* own rows — but an app laying out its own overlay against the text still
|
|
743
|
+
* does, and an overlay is laid out in style lengths. */
|
|
684
744
|
measureText(text: string): number {
|
|
685
745
|
const fonts = this._fonts();
|
|
686
|
-
|
|
687
|
-
|
|
746
|
+
const width = fonts
|
|
747
|
+
? fonts.layout(text, this._textStyle()).width
|
|
748
|
+
: text.length * this._charW;
|
|
749
|
+
return width / this._scale;
|
|
688
750
|
}
|
|
689
751
|
|
|
690
|
-
/** Caret geometry relative to this node's origin — node coordinates,
|
|
691
|
-
* is exactly what a `<popup anchor={{at}}>` takes,
|
|
692
|
-
* hands this straight through and nothing here
|
|
693
|
-
* editor sits on screen. */
|
|
752
|
+
/** Caret geometry relative to this node's origin — node coordinates, in
|
|
753
|
+
* logical pixels, which is exactly what a `<popup anchor={{at}}>` takes,
|
|
754
|
+
* so the completion popup hands this straight through and nothing here
|
|
755
|
+
* has to know where the editor sits on screen, or on what panel. */
|
|
694
756
|
caretRect(): { x: number; y: number; height: number } {
|
|
695
757
|
this._syncProps();
|
|
696
758
|
this._metrics();
|
|
697
759
|
const content = this._contentRect();
|
|
698
760
|
const textX = content.x + this._gutterWidth() - this.abs.x;
|
|
761
|
+
// everything above is device; the anchor is logical, so divide once
|
|
762
|
+
const s = this._scale;
|
|
699
763
|
return {
|
|
700
|
-
x: textX + this._caretX(this._caret) - this._scrollX,
|
|
764
|
+
x: (textX + this._caretX(this._caret) - this._scrollX) / s,
|
|
701
765
|
y:
|
|
702
|
-
content.y -
|
|
703
|
-
|
|
766
|
+
(content.y -
|
|
767
|
+
this.abs.y +
|
|
768
|
+
this._caret.line * this._lineH -
|
|
769
|
+
this._scrollY) /
|
|
770
|
+
s,
|
|
771
|
+
height: this._lineH / s,
|
|
704
772
|
};
|
|
705
773
|
}
|
|
706
774
|
|
|
@@ -717,18 +785,26 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
717
785
|
private _maxScrollX(): number {
|
|
718
786
|
const content = this._contentRect();
|
|
719
787
|
const textW = content.width - this._gutterWidth();
|
|
720
|
-
return Math.max(0, this._widest + CARET_MARGIN - textW);
|
|
788
|
+
return Math.max(0, this._widest + CARET_MARGIN * this._scale - textW);
|
|
721
789
|
}
|
|
722
790
|
|
|
723
|
-
/** Wheel notches (deltaX/deltaY arrive as ±1 per notch) →
|
|
791
|
+
/** Wheel notches (deltaX/deltaY arrive as ±1 per notch) → three lines a
|
|
792
|
+
* notch, in the device pixels the offsets are kept in. */
|
|
724
793
|
handleWheel(ev: { deltaX: number; deltaY: number }): boolean {
|
|
725
794
|
const step = this._lineHeight() * 3;
|
|
726
|
-
return this.
|
|
795
|
+
return this._scrollByDevice(ev.deltaX * step, ev.deltaY * step);
|
|
727
796
|
}
|
|
728
797
|
|
|
729
|
-
/**
|
|
730
|
-
*
|
|
798
|
+
/** Logical pixels, like core's own `scrollBy`: application code writes
|
|
799
|
+
* the unit it writes its styles in. Returns true when the offset moved
|
|
800
|
+
* (the component consumes the wheel event exactly then, so an
|
|
801
|
+
* unscrollable editor lets the page scroll). */
|
|
731
802
|
scrollBy(dx: number, dy: number): boolean {
|
|
803
|
+
const s = this._scale;
|
|
804
|
+
return this._scrollByDevice(dx * s, dy * s);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
private _scrollByDevice(dx: number, dy: number): boolean {
|
|
732
808
|
const nx = Math.max(0, Math.min(this._scrollX + dx, this._maxScrollX()));
|
|
733
809
|
const ny = Math.max(0, Math.min(this._scrollY + dy, this._maxScrollY()));
|
|
734
810
|
if (nx === this._scrollX && ny === this._scrollY) return false;
|
|
@@ -749,11 +825,12 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
749
825
|
}
|
|
750
826
|
const textW = content.width - this._gutterWidth();
|
|
751
827
|
const x = this._caretX(this._caret);
|
|
752
|
-
|
|
753
|
-
|
|
828
|
+
const margin = CARET_MARGIN * this._scale;
|
|
829
|
+
if (x < this._scrollX + margin) {
|
|
830
|
+
this._scrollX = Math.max(0, x - margin);
|
|
754
831
|
}
|
|
755
|
-
if (x > this._scrollX + textW -
|
|
756
|
-
this._scrollX = x - textW +
|
|
832
|
+
if (x > this._scrollX + textW - margin) {
|
|
833
|
+
this._scrollX = x - textW + margin;
|
|
757
834
|
}
|
|
758
835
|
this._scrollY = Math.max(0, Math.min(this._scrollY, this._maxScrollY()));
|
|
759
836
|
this._scrollX = Math.max(0, this._scrollX);
|
|
@@ -1422,7 +1499,8 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
1422
1499
|
handleMouseDown(ev: EditorMouseEvent): boolean {
|
|
1423
1500
|
this._syncProps();
|
|
1424
1501
|
this.breakUndoRun();
|
|
1425
|
-
const
|
|
1502
|
+
const at = this._devicePoint(ev);
|
|
1503
|
+
const pos = this._posAt(at.x, at.y);
|
|
1426
1504
|
if (ev.button === 3) {
|
|
1427
1505
|
const [a, b] = this._selectionRange();
|
|
1428
1506
|
if (comparePos(pos, a) < 0 || comparePos(pos, b) > 0) {
|
|
@@ -1455,7 +1533,8 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
1455
1533
|
|
|
1456
1534
|
handleMouseMove(ev: EditorMouseEvent): boolean {
|
|
1457
1535
|
if (!this._drag) return false;
|
|
1458
|
-
const
|
|
1536
|
+
const at = this._devicePoint(ev);
|
|
1537
|
+
const pos = this._posAt(at.x, at.y);
|
|
1459
1538
|
if (this._drag === 'char') this.moveCaret(pos, true);
|
|
1460
1539
|
else if (this._drag === 'word')
|
|
1461
1540
|
this._selectWordRange(this._dragOrigin, pos);
|
|
@@ -1598,6 +1677,9 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
1598
1677
|
const lineH = this._lineH;
|
|
1599
1678
|
const gutterW = this._gutterWidth();
|
|
1600
1679
|
const textX = content.x + gutterW - this._scrollX;
|
|
1680
|
+
// the context draws in device pixels, like everything measured above;
|
|
1681
|
+
// the hairlines, insets and minimums below are logical constants
|
|
1682
|
+
const s = this._scale;
|
|
1601
1683
|
const props = this.props as unknown as CodeEditorProps;
|
|
1602
1684
|
const styles = this._textStyle();
|
|
1603
1685
|
const baseColor = styles.color as string;
|
|
@@ -1685,7 +1767,12 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
1685
1767
|
i === b.line
|
|
1686
1768
|
? this._caretX({ line: i, ch: b.ch })
|
|
1687
1769
|
: (entry.layout?.width ?? 0) + this._charW * 0.5; // the newline
|
|
1688
|
-
c.fillRect(
|
|
1770
|
+
c.fillRect(
|
|
1771
|
+
textX + startX,
|
|
1772
|
+
lineY(i),
|
|
1773
|
+
Math.max(2 * s, endX - startX),
|
|
1774
|
+
lineH,
|
|
1775
|
+
);
|
|
1689
1776
|
}
|
|
1690
1777
|
}
|
|
1691
1778
|
|
|
@@ -1700,7 +1787,7 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
1700
1787
|
if (pos.line < first || pos.line > last) continue;
|
|
1701
1788
|
const x0 = this._caretX(pos);
|
|
1702
1789
|
const x1 = this._caretX({ line: pos.line, ch: pos.ch + 1 });
|
|
1703
|
-
c.fillRect(textX + x0, lineY(pos.line), Math.max(
|
|
1790
|
+
c.fillRect(textX + x0, lineY(pos.line), Math.max(s, x1 - x0), lineH);
|
|
1704
1791
|
}
|
|
1705
1792
|
}
|
|
1706
1793
|
}
|
|
@@ -1763,7 +1850,7 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
1763
1850
|
lineY(i) +
|
|
1764
1851
|
(lineH - (entry.layout?.height || lineH)) / 2 +
|
|
1765
1852
|
(entry.layout?.height || lineH);
|
|
1766
|
-
const y = Math.min(lineY(i) + lineH - 2, glyphBottom -
|
|
1853
|
+
const y = Math.min(lineY(i) + lineH - 2 * s, glyphBottom - s);
|
|
1767
1854
|
const width = Math.max(this._charW * 0.6, x1 - x0);
|
|
1768
1855
|
if (
|
|
1769
1856
|
typeof c.beginPath === 'function' &&
|
|
@@ -1772,34 +1859,35 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
1772
1859
|
typeof c.stroke === 'function'
|
|
1773
1860
|
) {
|
|
1774
1861
|
c.beginPath();
|
|
1775
|
-
|
|
1862
|
+
// a hairline zig-zag, 4px a step and 2px tall, on the panel's grid
|
|
1863
|
+
const step = 4 * s;
|
|
1776
1864
|
let up = true;
|
|
1777
1865
|
for (let x = 0; x <= width; x += step) {
|
|
1778
1866
|
const px = textX + x0 + x;
|
|
1779
|
-
const py = y + (up ? 0 : 2);
|
|
1867
|
+
const py = y + (up ? 0 : 2 * s);
|
|
1780
1868
|
if (x === 0) c.moveTo(px, py);
|
|
1781
1869
|
else c.lineTo(px, py);
|
|
1782
1870
|
up = !up;
|
|
1783
1871
|
}
|
|
1784
1872
|
c.strokeStyle = color;
|
|
1785
|
-
c.lineWidth =
|
|
1873
|
+
c.lineWidth = s;
|
|
1786
1874
|
c.stroke();
|
|
1787
1875
|
} else {
|
|
1788
1876
|
c.fillStyle = color;
|
|
1789
|
-
c.fillRect(textX + x0, y +
|
|
1877
|
+
c.fillRect(textX + x0, y + s, width, s);
|
|
1790
1878
|
}
|
|
1791
1879
|
}
|
|
1792
1880
|
}
|
|
1793
1881
|
|
|
1794
|
-
// caret —
|
|
1882
|
+
// caret — 2 logical px, in the (theme-resolved) text colour
|
|
1795
1883
|
if (this._focused && this._caretOn) {
|
|
1796
1884
|
const x = this._caretX(this._caret);
|
|
1797
1885
|
c.fillStyle = this._resolveColor(props.caretColor) ?? baseColor;
|
|
1798
1886
|
c.fillRect(
|
|
1799
1887
|
Math.round(textX + x),
|
|
1800
|
-
lineY(this._caret.line) +
|
|
1801
|
-
2,
|
|
1802
|
-
lineH - 2,
|
|
1888
|
+
lineY(this._caret.line) + s,
|
|
1889
|
+
2 * s,
|
|
1890
|
+
lineH - 2 * s,
|
|
1803
1891
|
);
|
|
1804
1892
|
}
|
|
1805
1893
|
|
|
@@ -1826,40 +1914,46 @@ export class CodeEditorNode extends Node implements CodeEditorHandle {
|
|
|
1826
1914
|
);
|
|
1827
1915
|
layout.draw(
|
|
1828
1916
|
ctx,
|
|
1829
|
-
content.x + gutterW - GUTTER_PAD - layout.width,
|
|
1917
|
+
content.x + gutterW - GUTTER_PAD * s - layout.width,
|
|
1830
1918
|
lineY(i) + (lineH - layout.height) / 2,
|
|
1831
1919
|
);
|
|
1832
1920
|
}
|
|
1833
1921
|
}
|
|
1834
1922
|
}
|
|
1835
1923
|
|
|
1836
|
-
// scroll thumbs — minimal, panel-style
|
|
1924
|
+
// scroll thumbs — minimal, panel-style: a 3px bar inset 4px, never
|
|
1925
|
+
// shorter than 24px, every one of those logical
|
|
1837
1926
|
const maxY = this._maxScrollY();
|
|
1838
1927
|
if (maxY > 0) {
|
|
1839
1928
|
const trackH = content.height;
|
|
1840
1929
|
const thumbH = Math.max(
|
|
1841
|
-
24,
|
|
1930
|
+
24 * s,
|
|
1842
1931
|
(trackH * trackH) / (this._lines.length * lineH),
|
|
1843
1932
|
);
|
|
1844
1933
|
const yOff = (this._scrollY / maxY) * (trackH - thumbH);
|
|
1845
1934
|
c.fillStyle = 'rgba(128,128,128,0.35)';
|
|
1846
|
-
c.fillRect(
|
|
1935
|
+
c.fillRect(
|
|
1936
|
+
content.x + content.width - 4 * s,
|
|
1937
|
+
content.y + yOff,
|
|
1938
|
+
3 * s,
|
|
1939
|
+
thumbH,
|
|
1940
|
+
);
|
|
1847
1941
|
}
|
|
1848
1942
|
const maxX = this._maxScrollX();
|
|
1849
1943
|
if (maxX > 0) {
|
|
1850
1944
|
const textW = content.width - gutterW;
|
|
1851
1945
|
const trackW = textW;
|
|
1852
1946
|
const thumbW = Math.max(
|
|
1853
|
-
24,
|
|
1854
|
-
(trackW * trackW) / (this._widest + CARET_MARGIN),
|
|
1947
|
+
24 * s,
|
|
1948
|
+
(trackW * trackW) / (this._widest + CARET_MARGIN * s),
|
|
1855
1949
|
);
|
|
1856
1950
|
const xOff = (this._scrollX / maxX) * (trackW - thumbW);
|
|
1857
1951
|
c.fillStyle = 'rgba(128,128,128,0.35)';
|
|
1858
1952
|
c.fillRect(
|
|
1859
1953
|
content.x + gutterW + xOff,
|
|
1860
|
-
content.y + content.height - 4,
|
|
1954
|
+
content.y + content.height - 4 * s,
|
|
1861
1955
|
thumbW,
|
|
1862
|
-
3,
|
|
1956
|
+
3 * s,
|
|
1863
1957
|
);
|
|
1864
1958
|
}
|
|
1865
1959
|
|