@design-core/core 0.0.6 → 0.0.7
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/.eslintrc.json +4 -0
- package/TODO.md +49 -6
- package/core/core/core.js +0 -3
- package/core/dimensions/alignedDimension.js +5 -4
- package/core/dimensions/angularDimension.js +6 -5
- package/core/dimensions/baseCircularDimension.js +3 -2
- package/core/dimensions/baseDimension.js +59 -93
- package/core/dimensions/baseLinearDimension.js +6 -5
- package/core/dimensions/diametricDimension.js +4 -3
- package/core/dimensions/dimension.js +21 -14
- package/core/dimensions/radialDimension.js +4 -3
- package/core/dimensions/rotatedDimension.js +12 -11
- package/core/entities/arc.js +112 -145
- package/core/entities/arctext.js +212 -174
- package/core/entities/basePolyline.js +30 -282
- package/core/entities/circle.js +77 -99
- package/core/entities/entity.js +114 -93
- package/core/entities/hatch.js +322 -249
- package/core/entities/insert.js +66 -81
- package/core/entities/line.js +21 -142
- package/core/entities/lwpolyline.js +16 -11
- package/core/entities/polyline.js +4 -4
- package/core/entities/rectangle.js +3 -1
- package/core/entities/solid.js +6 -15
- package/core/entities/text.js +188 -199
- package/core/lib/auxiliary/rubberBand.js +32 -0
- package/core/lib/auxiliary/selectionWindow.js +63 -0
- package/core/lib/auxiliary/snapPoint.js +113 -0
- package/core/lib/auxiliary/trackingLine.js +76 -0
- package/core/lib/canvas.js +228 -166
- package/core/lib/clipboard.js +7 -1
- package/core/lib/colour.js +16 -0
- package/core/lib/commandLine.js +1 -6
- package/core/lib/commandManager.js +18 -8
- package/core/lib/constants.js +28 -0
- package/core/lib/dxf/dxf.js +4 -3
- package/core/lib/entityManager.js +18 -11
- package/core/lib/inputManager.js +35 -11
- package/core/lib/intersect.js +25 -0
- package/core/lib/patterns.js +8 -1
- package/core/lib/plotOptions.js +67 -0
- package/core/lib/polylineUtils.js +75 -0
- package/core/lib/renderers/cairoRenderer.js +371 -0
- package/core/lib/renderers/canvasRenderer.js +315 -0
- package/core/lib/renderers/pdfRenderer.js +366 -0
- package/core/lib/renderers/rendererBase.js +284 -0
- package/core/lib/renderers/svgRenderer.js +343 -0
- package/core/lib/scene.js +27 -5
- package/core/lib/selectionManager.js +2 -2
- package/core/lib/settings.js +4 -2
- package/core/lib/snapping.js +4 -191
- package/core/lib/stateManager.js +30 -1
- package/core/lib/utils.js +21 -2
- package/core/properties/entityProperties.js +104 -0
- package/core/properties/property.js +106 -2
- package/core/properties/propertyManager.js +29 -13
- package/core/tables/block.js +4 -37
- package/core/tables/endblk.js +1 -1
- package/core/tables/layer.js +8 -0
- package/core/tables/styleManagerBase.js +3 -2
- package/core/tables/tableManagerBase.js +8 -3
- package/core/tools/chamfer.js +111 -137
- package/core/tools/chamferFilletBase.js +135 -34
- package/core/tools/copy.js +3 -3
- package/core/tools/explode.js +2 -2
- package/core/tools/extend.js +132 -19
- package/core/tools/fillet.js +86 -183
- package/core/tools/matchProp.js +2 -11
- package/core/tools/mirror.js +7 -6
- package/core/tools/move.js +3 -3
- package/core/tools/offset.js +3 -5
- package/core/tools/pasteclip.js +1 -1
- package/core/tools/rotate.js +6 -3
- package/core/tools/scale.js +1 -1
- package/core/tools/trim.js +265 -23
- package/core/tools/zoom.js +1 -1
- package/jest.config.js +6 -0
- package/package.json +5 -3
- package/test/dimensions/alignedDimension.test.js +14 -7
- package/test/dimensions/angularDimension.test.js +6 -6
- package/test/dimensions/baseDimension.test.js +69 -8
- package/test/dimensions/baseLinearDimension.test.js +62 -0
- package/test/dimensions/diametricDimension.test.js +6 -6
- package/test/dimensions/radialDimension.test.js +6 -6
- package/test/dimensions/rotatedDimension.test.js +14 -7
- package/test/entities/arc.test.js +80 -83
- package/test/entities/arctext.test.js +180 -53
- package/test/entities/basePolyline.test.js +68 -2
- package/test/entities/circle.test.js +57 -85
- package/test/entities/entity.test.js +42 -8
- package/test/entities/hatch.test.js +543 -70
- package/test/entities/insert.test.js +96 -1
- package/test/entities/line.test.js +15 -41
- package/test/entities/lwpolyline.test.js +104 -0
- package/test/entities/polyline.test.js +11 -0
- package/test/entities/rectangle.test.js +11 -0
- package/test/entities/solid.test.js +1 -1
- package/test/entities/text.test.js +294 -69
- package/test/integration/exportIntegration.reference.dxf +9 -9
- package/test/integration/exportIntegration.test.js +2 -2
- package/test/integration/intersectionIntegration.test.js +4 -5
- package/test/integration/pdfRendererIntegration.reference.pdf +17709 -0
- package/test/integration/pdfRendererIntegration.test.js +61 -0
- package/test/integration/svgRendererIntegration.reference.svg +3343 -0
- package/test/integration/svgRendererIntegration.test.js +61 -0
- package/test/integration/userInputIntegration.reference.dxf +1165 -105
- package/test/integration/userInputIntegration.test.js +239 -132
- package/test/lib/canvas.test.js +742 -123
- package/test/lib/constants.test.js +27 -0
- package/test/lib/entityManager.test.js +4 -4
- package/test/lib/inputManager.test.js +80 -19
- package/test/lib/intersect.test.js +83 -0
- package/test/lib/plotOptions.test.js +179 -0
- package/test/lib/rubberBand.test.js +84 -0
- package/test/lib/settings.test.js +5 -6
- package/test/lib/snapping.test.js +71 -129
- package/test/lib/stateManager.test.js +7 -7
- package/test/lib/utils.test.js +36 -2
- package/test/properties/entityProperties.test.js +132 -0
- package/test/properties/property.test.js +53 -0
- package/test/properties/propertyManager.test.js +3 -3
- package/test/renderers/canvasRenderer.test.js +317 -0
- package/test/renderers/pdfRenderer.test.js +271 -0
- package/test/renderers/rendererBase.test.js +213 -0
- package/test/renderers/svgRenderer.test.js +326 -0
- package/test/tables/block.test.js +31 -1
- package/test/tables/endblk.test.js +2 -2
- package/test/tables/layer.test.js +30 -0
- package/test/test-helpers/hatchGridRenderer.js +180 -0
- package/test/test-helpers/test-helpers.js +152 -0
- package/test/tools/chamfer.test.js +227 -5
- package/test/tools/chamferFilletBase.test.js +98 -9
- package/test/tools/copy.test.js +2 -2
- package/test/tools/explode.test.js +3 -3
- package/test/tools/extend.test.js +331 -0
- package/test/tools/fillet.test.js +181 -0
- package/test/tools/matchProp.test.js +5 -5
- package/test/tools/mirror.test.js +13 -13
- package/test/tools/offset.test.js +170 -3
- package/test/tools/pasteclip.test.js +5 -5
- package/test/tools/rotate.test.js +32 -2
- package/test/tools/scale.test.js +5 -5
- package/test/tools/trim.test.js +526 -0
- package/test/tools/zoom.test.js +7 -7
- package/core/lib/selectionWindow.js +0 -83
package/test/tools/trim.test.js
CHANGED
|
@@ -3,6 +3,7 @@ import { Point } from '../../core/entities/point.js';
|
|
|
3
3
|
import { Trim } from '../../core/tools/trim.js';
|
|
4
4
|
import { SingleSelection } from '../../core/lib/selectionManager.js';
|
|
5
5
|
import { Strings } from '../../core/lib/strings.js';
|
|
6
|
+
import { DesignCore } from '../../core/designCore.js';
|
|
6
7
|
import { expect, jest } from '@jest/globals';
|
|
7
8
|
import { withMockInput } from '../test-helpers/test-helpers.js';
|
|
8
9
|
|
|
@@ -431,6 +432,102 @@ test('Trim.preview does not throw', () => {
|
|
|
431
432
|
expect(() => trim.preview()).not.toThrow();
|
|
432
433
|
});
|
|
433
434
|
|
|
435
|
+
test('Trim.preview returns early when no boundary items set', () => {
|
|
436
|
+
core.scene.clear();
|
|
437
|
+
core.scene.addItem('Line', { points: [new Point(0, -10), new Point(0, 10)] });
|
|
438
|
+
DesignCore.Scene.previewEntities.clear();
|
|
439
|
+
|
|
440
|
+
const trim = new Trim();
|
|
441
|
+
// selectedBoundaryItems is empty by default
|
|
442
|
+
trim.preview();
|
|
443
|
+
|
|
444
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
test('Trim.preview returns early when findClosestItem returns undefined', () => {
|
|
448
|
+
core.scene.clear();
|
|
449
|
+
core.scene.addItem('Line', { points: [new Point(0, -10), new Point(0, 10)] });
|
|
450
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(10, 0)] });
|
|
451
|
+
DesignCore.Scene.previewEntities.clear();
|
|
452
|
+
|
|
453
|
+
const trim = new Trim();
|
|
454
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(0)];
|
|
455
|
+
|
|
456
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(undefined);
|
|
457
|
+
trim.preview();
|
|
458
|
+
findSpy.mockRestore();
|
|
459
|
+
|
|
460
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
test('Trim.preview returns early when hovered entity does not implement trim', () => {
|
|
464
|
+
// Text does not override Entity.trim() — preview must not emit a notification.
|
|
465
|
+
core.scene.clear();
|
|
466
|
+
core.scene.addItem('Line', { points: [new Point(0, -10), new Point(0, 10)] }); // boundary
|
|
467
|
+
core.scene.addItem('Text', { points: [new Point(5, 0)], string: 'hello' }); // unsupported
|
|
468
|
+
DesignCore.Scene.previewEntities.clear();
|
|
469
|
+
|
|
470
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
471
|
+
const trim = new Trim();
|
|
472
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(0)];
|
|
473
|
+
|
|
474
|
+
const savedPointOnScene1 = DesignCore.Mouse.pointOnScene;
|
|
475
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(1);
|
|
476
|
+
DesignCore.Mouse.pointOnScene = () => new Point(5, 0);
|
|
477
|
+
trim.preview();
|
|
478
|
+
findSpy.mockRestore();
|
|
479
|
+
notifySpy.mockRestore();
|
|
480
|
+
DesignCore.Mouse.pointOnScene = savedPointOnScene1;
|
|
481
|
+
|
|
482
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
483
|
+
expect(notifySpy).not.toHaveBeenCalled();
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
test('Trim.preview returns early when hovered entity has no intersections with boundary', () => {
|
|
487
|
+
// Two parallel horizontal lines — no intersection with the vertical boundary
|
|
488
|
+
core.scene.clear();
|
|
489
|
+
core.scene.addItem('Line', { points: [new Point(0, -10), new Point(0, 10)] }); // boundary (vertical)
|
|
490
|
+
core.scene.addItem('Line', { points: [new Point(-10, 5), new Point(10, 5)] }); // hovered (horizontal, no intersection with boundary)
|
|
491
|
+
DesignCore.Scene.previewEntities.clear();
|
|
492
|
+
|
|
493
|
+
// Boundary is entity 0; hovered is entity 1 — they are parallel on the y-axis, so no intersection
|
|
494
|
+
core.scene.addItem('Line', { points: [new Point(-10, 20), new Point(10, 20)] }); // extra line, no intersection
|
|
495
|
+
|
|
496
|
+
const trim = new Trim();
|
|
497
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(2)]; // horizontal boundary — parallel to hovered
|
|
498
|
+
|
|
499
|
+
const savedPointOnScene2 = DesignCore.Mouse.pointOnScene;
|
|
500
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(1);
|
|
501
|
+
DesignCore.Mouse.pointOnScene = () => new Point(0, 5);
|
|
502
|
+
trim.preview();
|
|
503
|
+
findSpy.mockRestore();
|
|
504
|
+
DesignCore.Mouse.pointOnScene = savedPointOnScene2;
|
|
505
|
+
|
|
506
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
test('Trim.preview populates previewEntities when entity can be trimmed', () => {
|
|
510
|
+
// Vertical boundary at x=0; horizontal line from (-10,0) to (10,0) is the hovered entity.
|
|
511
|
+
// Mouse at (5,0) → trims the right half, leaving the left half as a preview.
|
|
512
|
+
core.scene.clear();
|
|
513
|
+
core.scene.addItem('Line', { points: [new Point(0, -10), new Point(0, 10)] }); // boundary
|
|
514
|
+
core.scene.addItem('Line', { points: [new Point(-10, 0), new Point(10, 0)] }); // hovered
|
|
515
|
+
DesignCore.Scene.previewEntities.clear();
|
|
516
|
+
|
|
517
|
+
const trim = new Trim();
|
|
518
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(0)];
|
|
519
|
+
|
|
520
|
+
const savedPointOnScene3 = DesignCore.Mouse.pointOnScene;
|
|
521
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(1);
|
|
522
|
+
DesignCore.Mouse.pointOnScene = () => new Point(5, 0);
|
|
523
|
+
trim.preview();
|
|
524
|
+
findSpy.mockRestore();
|
|
525
|
+
DesignCore.Mouse.pointOnScene = savedPointOnScene3;
|
|
526
|
+
|
|
527
|
+
// dulled original + at least one trimmed survivor
|
|
528
|
+
expect(DesignCore.Scene.previewEntities.count()).toBeGreaterThanOrEqual(2);
|
|
529
|
+
});
|
|
530
|
+
|
|
434
531
|
test('Trim.execute returns early when boundary input is undefined', async () => {
|
|
435
532
|
core.scene.clear();
|
|
436
533
|
core.scene.selectionManager.reset();
|
|
@@ -503,6 +600,58 @@ test('Trim.action does nothing when selectedBoundaryItems is empty', () => {
|
|
|
503
600
|
expect(trim.selectedItem).toBeNull();
|
|
504
601
|
});
|
|
505
602
|
|
|
603
|
+
test('Trim.action notifies NOTRIM when selected entity lacks fromPolylinePoints', () => {
|
|
604
|
+
// Use a stub entity that has toPolylinePoints but no fromPolylinePoints.
|
|
605
|
+
// action() must notify and not throw.
|
|
606
|
+
core.scene.clear();
|
|
607
|
+
core.scene.addItem('Line', { points: [new Point(0, -10), new Point(0, 10)] }); // boundary
|
|
608
|
+
|
|
609
|
+
const trim = new Trim();
|
|
610
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(0)];
|
|
611
|
+
trim.selectedItem = {
|
|
612
|
+
type: 'FakeEntity',
|
|
613
|
+
toPolylinePoints: () => [new Point(-5, 0), new Point(5, 0)],
|
|
614
|
+
// deliberately no fromPolylinePoints
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
const notifySpy = jest.spyOn(core, 'notify').mockImplementation(() => {});
|
|
618
|
+
trim.action();
|
|
619
|
+
|
|
620
|
+
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Message.NOTRIM));
|
|
621
|
+
expect(trim.selectedItem).toBeNull();
|
|
622
|
+
notifySpy.mockRestore();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
test('Trim.preview silently skips entity that lacks fromPolylinePoints', () => {
|
|
626
|
+
// Stub entity implements toPolylinePoints but not fromPolylinePoints.
|
|
627
|
+
// preview() must return early without calling notify.
|
|
628
|
+
core.scene.clear();
|
|
629
|
+
core.scene.addItem('Line', { points: [new Point(0, -10), new Point(0, 10)] }); // boundary
|
|
630
|
+
DesignCore.Scene.previewEntities.clear();
|
|
631
|
+
|
|
632
|
+
const stubEntity = {
|
|
633
|
+
type: 'FakeEntity',
|
|
634
|
+
toPolylinePoints: () => [new Point(-5, 0), new Point(5, 0)],
|
|
635
|
+
// deliberately no fromPolylinePoints
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
const notifySpy = jest.spyOn(core, 'notify');
|
|
639
|
+
const trim = new Trim();
|
|
640
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(0)];
|
|
641
|
+
|
|
642
|
+
const savedPointOnScene = DesignCore.Mouse.pointOnScene;
|
|
643
|
+
const findSpy = jest.spyOn(core.scene.selectionManager, 'findClosestItem').mockReturnValue(0);
|
|
644
|
+
jest.spyOn(core.scene.entities, 'get').mockReturnValue(stubEntity);
|
|
645
|
+
DesignCore.Mouse.pointOnScene = () => new Point(0, 0);
|
|
646
|
+
trim.preview();
|
|
647
|
+
findSpy.mockRestore();
|
|
648
|
+
jest.restoreAllMocks();
|
|
649
|
+
DesignCore.Mouse.pointOnScene = savedPointOnScene;
|
|
650
|
+
|
|
651
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(0);
|
|
652
|
+
expect(notifySpy).not.toHaveBeenCalled();
|
|
653
|
+
});
|
|
654
|
+
|
|
506
655
|
test('Trim.action notifies when no intersection found (parallel lines)', () => {
|
|
507
656
|
core.scene.clear();
|
|
508
657
|
// Two parallel vertical lines - no intersection
|
|
@@ -536,3 +685,380 @@ test('Trim.action notifies when boundary item equals selected item', () => {
|
|
|
536
685
|
expect(notifySpy).toHaveBeenCalledWith(expect.stringContaining(Strings.Message.NOTRIM));
|
|
537
686
|
notifySpy.mockRestore();
|
|
538
687
|
});
|
|
688
|
+
|
|
689
|
+
test('Trim.action trims an arc', () => {
|
|
690
|
+
// Arc: center (0,0), start (10,0), end (-10,0) — CCW upper semicircle through (0,10)
|
|
691
|
+
// Boundary: vertical line at x=0, intersects arc at (0,10)
|
|
692
|
+
// Mouse at (7,7) — in the right half, selects (10,0)→(0,10) segment for removal
|
|
693
|
+
// Expected: original arc removed, shorter arc from (0,10) to (-10,0) added
|
|
694
|
+
|
|
695
|
+
const trim = new Trim();
|
|
696
|
+
core.scene.clear();
|
|
697
|
+
|
|
698
|
+
core.scene.addItem('Arc', { points: [new Point(0, 0), new Point(10, 0), new Point(-10, 0)] });
|
|
699
|
+
core.scene.addItem('Line', { points: [new Point(0, -20), new Point(0, 20)] });
|
|
700
|
+
|
|
701
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1)];
|
|
702
|
+
trim.selectedItem = core.scene.entities.get(0);
|
|
703
|
+
core.mouse.setPosFromScenePoint(new Point(7, 7));
|
|
704
|
+
trim.action();
|
|
705
|
+
|
|
706
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
707
|
+
|
|
708
|
+
let arc;
|
|
709
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
710
|
+
if (core.scene.entities.get(i).type === 'Arc') arc = core.scene.entities.get(i);
|
|
711
|
+
}
|
|
712
|
+
expect(arc).toBeDefined();
|
|
713
|
+
// Remaining arc runs from the intersection (0,10) to the original end (-10,0)
|
|
714
|
+
expect(arc.points[1].x).toBeCloseTo(0);
|
|
715
|
+
expect(arc.points[1].y).toBeCloseTo(10);
|
|
716
|
+
expect(arc.points[2].x).toBeCloseTo(-10);
|
|
717
|
+
expect(arc.points[2].y).toBeCloseTo(0);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
test('Trim.action trims a circle', () => {
|
|
721
|
+
// Circle: center (0,0), radius 10
|
|
722
|
+
// Boundary: vertical line at x=0, intersects circle at (0,10) and (0,-10)
|
|
723
|
+
// Mouse at (8,0) — on the right side
|
|
724
|
+
// Expected: original circle removed, arc covering the right side added
|
|
725
|
+
|
|
726
|
+
const trim = new Trim();
|
|
727
|
+
core.scene.clear();
|
|
728
|
+
|
|
729
|
+
core.scene.addItem('Circle', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
730
|
+
core.scene.addItem('Line', { points: [new Point(0, -20), new Point(0, 20)] });
|
|
731
|
+
|
|
732
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1)];
|
|
733
|
+
trim.selectedItem = core.scene.entities.get(0);
|
|
734
|
+
core.mouse.setPosFromScenePoint(new Point(8, 0));
|
|
735
|
+
trim.action();
|
|
736
|
+
|
|
737
|
+
// circle removed, arc added, boundary line remains
|
|
738
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
739
|
+
|
|
740
|
+
let arc;
|
|
741
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
742
|
+
if (core.scene.entities.get(i).type === 'Arc') arc = core.scene.entities.get(i);
|
|
743
|
+
}
|
|
744
|
+
expect(arc).toBeDefined();
|
|
745
|
+
// Arc center should match the original circle center
|
|
746
|
+
expect(arc.points[0].x).toBeCloseTo(0, 10);
|
|
747
|
+
expect(arc.points[0].y).toBeCloseTo(0, 10);
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
test('Trim.action trims a closed polyline - produces open polyline with closed flag cleared', () => {
|
|
751
|
+
// Closed square: (0,0)→(100,0)→(100,100)→(0,100) with flag bit 1
|
|
752
|
+
// Boundary: vertical line at x=50
|
|
753
|
+
// Mouse at (75,50) — right of centre, selects the right side for removal
|
|
754
|
+
// Expected: one open polyline (50,100)→(0,100)→(0,0)→(50,0), flag bit 1 cleared
|
|
755
|
+
|
|
756
|
+
const trim = new Trim();
|
|
757
|
+
core.scene.clear();
|
|
758
|
+
|
|
759
|
+
// Five points where first === last → constructor pops last and sets flag=1
|
|
760
|
+
core.scene.addItem('Lwpolyline', {
|
|
761
|
+
points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100), new Point(0, 0)],
|
|
762
|
+
});
|
|
763
|
+
core.scene.addItem('Line', { points: [new Point(50, -10), new Point(50, 110)] });
|
|
764
|
+
|
|
765
|
+
const polyline = core.scene.entities.get(0);
|
|
766
|
+
expect(polyline.flags.hasFlag(1)).toBe(true); // confirm closed before trim
|
|
767
|
+
|
|
768
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1)];
|
|
769
|
+
trim.selectedItem = polyline;
|
|
770
|
+
core.mouse.setPosFromScenePoint(new Point(75, 50));
|
|
771
|
+
trim.action();
|
|
772
|
+
|
|
773
|
+
// Original closed polyline removed, one open polyline added, boundary line remains
|
|
774
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
775
|
+
|
|
776
|
+
let trimmed;
|
|
777
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
778
|
+
if (core.scene.entities.get(i).type === 'Polyline') trimmed = core.scene.entities.get(i);
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
expect(trimmed).toBeDefined();
|
|
782
|
+
expect(trimmed.flags.hasFlag(1)).toBe(false); // closed flag removed
|
|
783
|
+
expect(trimmed.points.length).toBe(4);
|
|
784
|
+
// Joined path runs from one intersection, around the left side, to the other
|
|
785
|
+
expect(trimmed.points[0].x).toBeCloseTo(50);
|
|
786
|
+
expect(trimmed.points[3].x).toBeCloseTo(50);
|
|
787
|
+
});
|
|
788
|
+
|
|
789
|
+
test('Trim.action trims middle of a line - circle boundary creates two intersections on the same segment', () => {
|
|
790
|
+
// A circle boundary intersects a single-segment line at two points, both on
|
|
791
|
+
// segment index 1. The sort comparator falls through to the
|
|
792
|
+
// positionAlongSegment comparison (line 179 of trim.js) because the segment
|
|
793
|
+
// indices are equal.
|
|
794
|
+
// Circle: center (50,0) radius 20 → intersects the x-axis at (30,0) and (70,0)
|
|
795
|
+
// Line: (0,0) → (100,0), mouse at (50,0) — inside the circle
|
|
796
|
+
// Expected: original line removed, two shorter lines (0→30) and (70→100) added
|
|
797
|
+
|
|
798
|
+
const trim = new Trim();
|
|
799
|
+
core.scene.clear();
|
|
800
|
+
|
|
801
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(100, 0)] });
|
|
802
|
+
core.scene.addItem('Circle', { points: [new Point(50, 0), new Point(70, 0)] }); // radius 20
|
|
803
|
+
|
|
804
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1)];
|
|
805
|
+
trim.selectedItem = core.scene.entities.get(0);
|
|
806
|
+
core.mouse.setPosFromScenePoint(new Point(50, 0));
|
|
807
|
+
trim.action();
|
|
808
|
+
|
|
809
|
+
// original line removed; two shorter lines added; circle remains
|
|
810
|
+
expect(core.scene.entities.count()).toBe(3);
|
|
811
|
+
|
|
812
|
+
const lines = [];
|
|
813
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
814
|
+
if (core.scene.entities.get(i).type === 'Line') lines.push(core.scene.entities.get(i));
|
|
815
|
+
}
|
|
816
|
+
expect(lines.length).toBe(2);
|
|
817
|
+
lines.sort((a, b) => a.points[0].x - b.points[0].x);
|
|
818
|
+
|
|
819
|
+
expect(lines[0].points[0].x).toBeCloseTo(0);
|
|
820
|
+
expect(lines[0].points[1].x).toBeCloseTo(30);
|
|
821
|
+
expect(lines[1].points[0].x).toBeCloseTo(70);
|
|
822
|
+
expect(lines[1].points[1].x).toBeCloseTo(100);
|
|
823
|
+
});
|
|
824
|
+
|
|
825
|
+
test('Trim.execute catches and logs errors thrown during input', async () => {
|
|
826
|
+
// Force an error inside execute() to cover the catch block (line 69 of trim.js)
|
|
827
|
+
core.scene.clear();
|
|
828
|
+
core.scene.selectionManager.reset();
|
|
829
|
+
|
|
830
|
+
const requestInputSpy = jest.spyOn(DesignCore.Scene.inputManager, 'requestInput')
|
|
831
|
+
.mockRejectedValue(new Error('mocked error'));
|
|
832
|
+
|
|
833
|
+
const trim = new Trim();
|
|
834
|
+
// execute() should resolve without re-throwing because the catch block handles it
|
|
835
|
+
await expect(trim.execute()).resolves.toBeUndefined();
|
|
836
|
+
|
|
837
|
+
requestInputSpy.mockRestore();
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
test('Trim.action trims a circle near the seam (right side, mouse at angle 0)', () => {
|
|
841
|
+
// Circle: center (0,0), radius 10.
|
|
842
|
+
// Circle.toPolylinePoints() always places the seam at angle 0 → vertex (10,0).
|
|
843
|
+
// The old code failed when the mouse was near that seam because both intersections
|
|
844
|
+
// appeared "after" the mouse in linear polyline order, leaving trimBefore=null.
|
|
845
|
+
//
|
|
846
|
+
// Boundary: vertical line at x=0, intersects at (0,10) [seg 2] and (0,-10) [seg 1].
|
|
847
|
+
// Mouse at (8,0) — on the RIGHT side, which is the forward arc from (0,-10) → (10,0) → (0,10)
|
|
848
|
+
// (crosses the seam vertex in the polyline chain but is a valid arc region).
|
|
849
|
+
// Expected: arc from (10,0) → (0,-10) covering the left half, trimming the seam-crossing right arc.
|
|
850
|
+
|
|
851
|
+
const trim = new Trim();
|
|
852
|
+
core.scene.clear();
|
|
853
|
+
|
|
854
|
+
core.scene.addItem('Circle', { points: [new Point(0, 0), new Point(10, 0)] });
|
|
855
|
+
core.scene.addItem('Line', { points: [new Point(0, -20), new Point(0, 20)] });
|
|
856
|
+
|
|
857
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1)];
|
|
858
|
+
trim.selectedItem = core.scene.entities.get(0);
|
|
859
|
+
core.mouse.setPosFromScenePoint(new Point(8, 0));
|
|
860
|
+
trim.action();
|
|
861
|
+
|
|
862
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
863
|
+
|
|
864
|
+
let arc;
|
|
865
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
866
|
+
if (core.scene.entities.get(i).type === 'Arc') arc = core.scene.entities.get(i);
|
|
867
|
+
}
|
|
868
|
+
expect(arc).toBeDefined();
|
|
869
|
+
// Remaining arc is the left half: from (0,10) CCW through (-10,0) to (0,-10)
|
|
870
|
+
expect(arc.points[0].x).toBeCloseTo(0); // center
|
|
871
|
+
expect(arc.points[0].y).toBeCloseTo(0);
|
|
872
|
+
expect(arc.points[1].x).toBeCloseTo(0); // start at (0,10)
|
|
873
|
+
expect(arc.points[1].y).toBeCloseTo(10);
|
|
874
|
+
expect(arc.points[2].x).toBeCloseTo(0); // end at (0,-10)
|
|
875
|
+
expect(arc.points[2].y).toBeCloseTo(-10);
|
|
876
|
+
});
|
|
877
|
+
|
|
878
|
+
test('Trim.action trims a closed polyline near the seam vertex', () => {
|
|
879
|
+
// Closed square drawn starting from (0,0): seam is at vertex (0,0).
|
|
880
|
+
// Boundary: horizontal line at y=50, intersects:
|
|
881
|
+
// left edge (closing segment (0,100)→(0,0)→...) at (0,50) — segment 4 (closing seg)
|
|
882
|
+
// right edge (100,0)→(100,100) at (100,50) — segment 2
|
|
883
|
+
//
|
|
884
|
+
// Mouse at (50,25) — in the BOTTOM half, which is the forward arc from intA=(100,50)
|
|
885
|
+
// through the bottom vertices to intB=(0,50). This forward arc crosses the seam vertex.
|
|
886
|
+
//
|
|
887
|
+
// Expected: open polyline (100,50)→(100,0)→(0,0)→(0,50), flag bit 1 cleared.
|
|
888
|
+
|
|
889
|
+
const trim = new Trim();
|
|
890
|
+
core.scene.clear();
|
|
891
|
+
|
|
892
|
+
core.scene.addItem('Lwpolyline', {
|
|
893
|
+
points: [new Point(0, 0), new Point(100, 0), new Point(100, 100), new Point(0, 100), new Point(0, 0)],
|
|
894
|
+
});
|
|
895
|
+
core.scene.addItem('Line', { points: [new Point(-10, 50), new Point(110, 50)] });
|
|
896
|
+
|
|
897
|
+
const polyline = core.scene.entities.get(0);
|
|
898
|
+
expect(polyline.flags.hasFlag(1)).toBe(true);
|
|
899
|
+
|
|
900
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1)];
|
|
901
|
+
trim.selectedItem = polyline;
|
|
902
|
+
// Mouse in the BOTTOM half — forward arc between the two intersections, crosses the seam
|
|
903
|
+
core.mouse.setPosFromScenePoint(new Point(50, 25));
|
|
904
|
+
trim.action();
|
|
905
|
+
|
|
906
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
907
|
+
|
|
908
|
+
let trimmed;
|
|
909
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
910
|
+
if (core.scene.entities.get(i).type === 'Polyline') trimmed = core.scene.entities.get(i);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
expect(trimmed).toBeDefined();
|
|
914
|
+
expect(trimmed.flags.hasFlag(1)).toBe(false);
|
|
915
|
+
expect(trimmed.points.length).toBe(4);
|
|
916
|
+
// Forward arc: right edge from y=50 down to corner, across bottom, left edge up to y=50
|
|
917
|
+
expect(trimmed.points[0].x).toBeCloseTo(100);
|
|
918
|
+
expect(trimmed.points[0].y).toBeCloseTo(50);
|
|
919
|
+
expect(trimmed.points[3].x).toBeCloseTo(0);
|
|
920
|
+
expect(trimmed.points[3].y).toBeCloseTo(50);
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
test('Trim.action trims an open polyline with two consecutive arc segments', () => {
|
|
924
|
+
// Polyline: (0,0,b=1) -> arc1 -> (100,0,b=1) -> arc2 -> (200,0)
|
|
925
|
+
// arc1 center (50,0) radius 50, through (50,-50)
|
|
926
|
+
// arc2 center (150,0) radius 50, through (150,-50)
|
|
927
|
+
// Boundary: vertical line at x=150, intersection at (150,-50) on arc2.
|
|
928
|
+
// Mouse at (120,-40): on arc2 BEFORE the intersection (in arc-travel order).
|
|
929
|
+
// Expected: keep right portion = (150,-50) -> (200,0); discard left side.
|
|
930
|
+
|
|
931
|
+
const trim = new Trim();
|
|
932
|
+
core.scene.clear();
|
|
933
|
+
|
|
934
|
+
core.scene.addItem('Lwpolyline', { points: [new Point(0, 0, 1), new Point(100, 0, 1), new Point(200, 0)] });
|
|
935
|
+
core.scene.addItem('Line', { points: [new Point(150, -100), new Point(150, 100)] });
|
|
936
|
+
|
|
937
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1)];
|
|
938
|
+
trim.selectedItem = core.scene.entities.get(0);
|
|
939
|
+
core.mouse.setPosFromScenePoint(new Point(120, -40));
|
|
940
|
+
trim.action();
|
|
941
|
+
|
|
942
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
943
|
+
|
|
944
|
+
let result;
|
|
945
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
946
|
+
if (core.scene.entities.get(i).type === 'Polyline') result = core.scene.entities.get(i);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
expect(result).toBeDefined();
|
|
950
|
+
expect(result.points.length).toBe(2);
|
|
951
|
+
expect(result.points[0].x).toBeCloseTo(150);
|
|
952
|
+
expect(result.points[0].y).toBeCloseTo(-50);
|
|
953
|
+
expect(result.points[1].x).toBeCloseTo(200);
|
|
954
|
+
expect(result.points[1].y).toBeCloseTo(0);
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
test('Trim.action trims an arc from the start (portionOne same-segment bulge)', () => {
|
|
958
|
+
// Arc: center (0,0), start (10,0), end (-10,0) — CCW upper semicircle through (0,10).
|
|
959
|
+
// Boundary: vertical line at x=0, intersection at (0,10).
|
|
960
|
+
// Mouse at (-7,7) — LEFT of intersection, so trimBefore=(0,10), trimAfter=null.
|
|
961
|
+
// portionOne = startLoc(seg1,(10,0)) → trimBefore(seg1,(0,10)) — same-segment arc split.
|
|
962
|
+
// Expected: arc from (10,0) to (0,10), covering the right quarter.
|
|
963
|
+
|
|
964
|
+
const trim = new Trim();
|
|
965
|
+
core.scene.clear();
|
|
966
|
+
|
|
967
|
+
core.scene.addItem('Arc', { points: [new Point(0, 0), new Point(10, 0), new Point(-10, 0)] });
|
|
968
|
+
core.scene.addItem('Line', { points: [new Point(0, -20), new Point(0, 20)] });
|
|
969
|
+
|
|
970
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1)];
|
|
971
|
+
trim.selectedItem = core.scene.entities.get(0);
|
|
972
|
+
core.mouse.setPosFromScenePoint(new Point(-7, 7));
|
|
973
|
+
trim.action();
|
|
974
|
+
|
|
975
|
+
expect(core.scene.entities.count()).toBe(2);
|
|
976
|
+
|
|
977
|
+
let arc;
|
|
978
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
979
|
+
if (core.scene.entities.get(i).type === 'Arc') arc = core.scene.entities.get(i);
|
|
980
|
+
}
|
|
981
|
+
expect(arc).toBeDefined();
|
|
982
|
+
// Kept portion runs from original start (10,0) to intersection (0,10)
|
|
983
|
+
expect(arc.points[1].x).toBeCloseTo(10);
|
|
984
|
+
expect(arc.points[1].y).toBeCloseTo(0);
|
|
985
|
+
expect(arc.points[2].x).toBeCloseTo(0);
|
|
986
|
+
expect(arc.points[2].y).toBeCloseTo(10);
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
test('Trim.action trims the middle of an arc (two intersections on same arc segment)', () => {
|
|
990
|
+
// Arc: center (0,0), start (10,0), end (-10,0) — CCW upper semicircle through (0,10).
|
|
991
|
+
// Two boundaries: vertical line at x=-5 and x=5.
|
|
992
|
+
// Intersections at (5,√75)≈(5,8.66) and (-5,√75)≈(-5,8.66), both on segment 1.
|
|
993
|
+
// Mouse at (0,9) — between both intersections at the top.
|
|
994
|
+
// Expected: two arcs: (10,0)→(5,8.66) and (-5,8.66)→(-10,0).
|
|
995
|
+
|
|
996
|
+
const trim = new Trim();
|
|
997
|
+
core.scene.clear();
|
|
998
|
+
|
|
999
|
+
core.scene.addItem('Arc', { points: [new Point(0, 0), new Point(10, 0), new Point(-10, 0)] });
|
|
1000
|
+
core.scene.addItem('Line', { points: [new Point(5, -20), new Point(5, 20)] });
|
|
1001
|
+
core.scene.addItem('Line', { points: [new Point(-5, -20), new Point(-5, 20)] });
|
|
1002
|
+
|
|
1003
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1), core.scene.entities.get(2)];
|
|
1004
|
+
trim.selectedItem = core.scene.entities.get(0);
|
|
1005
|
+
core.mouse.setPosFromScenePoint(new Point(0, 9));
|
|
1006
|
+
trim.action();
|
|
1007
|
+
|
|
1008
|
+
// Original arc removed, two new arcs added, two boundary lines remain
|
|
1009
|
+
expect(core.scene.entities.count()).toBe(4);
|
|
1010
|
+
|
|
1011
|
+
const arcs = [];
|
|
1012
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
1013
|
+
if (core.scene.entities.get(i).type === 'Arc') arcs.push(core.scene.entities.get(i));
|
|
1014
|
+
}
|
|
1015
|
+
expect(arcs.length).toBe(2);
|
|
1016
|
+
arcs.sort((a, b) => a.points[1].x - b.points[1].x); // sort by start x
|
|
1017
|
+
|
|
1018
|
+
// Left arc: start near (-5,8.66), end at (-10,0)
|
|
1019
|
+
expect(arcs[0].points[1].x).toBeCloseTo(-5);
|
|
1020
|
+
expect(arcs[0].points[1].y).toBeCloseTo(Math.sqrt(75));
|
|
1021
|
+
expect(arcs[0].points[2].x).toBeCloseTo(-10);
|
|
1022
|
+
expect(arcs[0].points[2].y).toBeCloseTo(0);
|
|
1023
|
+
|
|
1024
|
+
// Right arc: start at (10,0), end near (5,8.66)
|
|
1025
|
+
expect(arcs[1].points[1].x).toBeCloseTo(10);
|
|
1026
|
+
expect(arcs[1].points[1].y).toBeCloseTo(0);
|
|
1027
|
+
expect(arcs[1].points[2].x).toBeCloseTo(5);
|
|
1028
|
+
expect(arcs[1].points[2].y).toBeCloseTo(Math.sqrt(75));
|
|
1029
|
+
});
|
|
1030
|
+
|
|
1031
|
+
test('Trim.action trims with multiple boundary items', () => {
|
|
1032
|
+
// Line: (0,0)→(200,0). Two separate boundary lines at x=50 and x=150.
|
|
1033
|
+
// Mouse at (100,0) — between both boundaries.
|
|
1034
|
+
// Expected: two lines: (0,0)→(50,0) and (150,0)→(200,0).
|
|
1035
|
+
|
|
1036
|
+
const trim = new Trim();
|
|
1037
|
+
core.scene.clear();
|
|
1038
|
+
|
|
1039
|
+
core.scene.addItem('Line', { points: [new Point(0, 0), new Point(200, 0)] });
|
|
1040
|
+
core.scene.addItem('Line', { points: [new Point(50, -20), new Point(50, 20)] });
|
|
1041
|
+
core.scene.addItem('Line', { points: [new Point(150, -20), new Point(150, 20)] });
|
|
1042
|
+
|
|
1043
|
+
trim.selectedBoundaryItems = [core.scene.entities.get(1), core.scene.entities.get(2)];
|
|
1044
|
+
trim.selectedItem = core.scene.entities.get(0);
|
|
1045
|
+
core.mouse.setPosFromScenePoint(new Point(100, 0));
|
|
1046
|
+
trim.action();
|
|
1047
|
+
|
|
1048
|
+
// Original line removed, two new lines added, two boundary lines remain
|
|
1049
|
+
expect(core.scene.entities.count()).toBe(4);
|
|
1050
|
+
|
|
1051
|
+
const lines = [];
|
|
1052
|
+
for (let i = 0; i < core.scene.entities.count(); i++) {
|
|
1053
|
+
const e = core.scene.entities.get(i);
|
|
1054
|
+
// Identify result lines by both points being at y=0 (boundary lines are vertical)
|
|
1055
|
+
if (e.type === 'Line' && e.points[0].y === 0 && e.points[1].y === 0) lines.push(e);
|
|
1056
|
+
}
|
|
1057
|
+
expect(lines.length).toBe(2);
|
|
1058
|
+
lines.sort((a, b) => a.points[0].x - b.points[0].x);
|
|
1059
|
+
|
|
1060
|
+
expect(lines[0].points[0].x).toBeCloseTo(0);
|
|
1061
|
+
expect(lines[0].points[1].x).toBeCloseTo(50);
|
|
1062
|
+
expect(lines[1].points[0].x).toBeCloseTo(150);
|
|
1063
|
+
expect(lines[1].points[1].x).toBeCloseTo(200);
|
|
1064
|
+
});
|
package/test/tools/zoom.test.js
CHANGED
|
@@ -109,15 +109,15 @@ test('Test Zoom.preview - Window mode with one point', () => {
|
|
|
109
109
|
// Mock mouse position
|
|
110
110
|
core.mouse.mouseMoved(50, 50);
|
|
111
111
|
|
|
112
|
-
const initialTempItemsCount = DesignCore.Scene.
|
|
112
|
+
const initialTempItemsCount = DesignCore.Scene.previewEntities.count();
|
|
113
113
|
|
|
114
114
|
zoom.preview();
|
|
115
115
|
|
|
116
116
|
// Should have added a polyline (rectangle preview)
|
|
117
|
-
expect(DesignCore.Scene.
|
|
117
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(initialTempItemsCount + 1);
|
|
118
118
|
|
|
119
119
|
// Verify it's a polyline
|
|
120
|
-
const tempItem = DesignCore.Scene.
|
|
120
|
+
const tempItem = DesignCore.Scene.previewEntities.get(DesignCore.Scene.previewEntities.count() - 1);
|
|
121
121
|
expect(tempItem.type).toBe('Polyline');
|
|
122
122
|
expect(tempItem.points.length).toBe(4); // Rectangle has 4 points (closed flag set)
|
|
123
123
|
});
|
|
@@ -126,12 +126,12 @@ test('Test Zoom.preview - Window mode with no points (should not preview)', () =
|
|
|
126
126
|
const zoom = new Zoom();
|
|
127
127
|
zoom.mode = 'Window';
|
|
128
128
|
|
|
129
|
-
const initialTempItemsCount = DesignCore.Scene.
|
|
129
|
+
const initialTempItemsCount = DesignCore.Scene.previewEntities.count();
|
|
130
130
|
|
|
131
131
|
zoom.preview();
|
|
132
132
|
|
|
133
133
|
// Should not add anything
|
|
134
|
-
expect(DesignCore.Scene.
|
|
134
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(initialTempItemsCount);
|
|
135
135
|
});
|
|
136
136
|
|
|
137
137
|
test('Test Zoom.preview - All mode (should not preview)', () => {
|
|
@@ -139,12 +139,12 @@ test('Test Zoom.preview - All mode (should not preview)', () => {
|
|
|
139
139
|
zoom.mode = 'All';
|
|
140
140
|
zoom.points.push(new Point(10, 10));
|
|
141
141
|
|
|
142
|
-
const initialTempItemsCount = DesignCore.Scene.
|
|
142
|
+
const initialTempItemsCount = DesignCore.Scene.previewEntities.count();
|
|
143
143
|
|
|
144
144
|
zoom.preview();
|
|
145
145
|
|
|
146
146
|
// Should not add anything for All/Extents modes
|
|
147
|
-
expect(DesignCore.Scene.
|
|
147
|
+
expect(DesignCore.Scene.previewEntities.count()).toBe(initialTempItemsCount);
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
test('Test Canvas.zoomToWindow - valid window', () => {
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import { DesignCore } from '../designCore.js';
|
|
2
|
-
import { Point } from '../entities/point.js';
|
|
3
|
-
import { Colours } from './colours.js';
|
|
4
|
-
|
|
5
|
-
/** SelectionWindow Class */
|
|
6
|
-
export class SelectionWindow {
|
|
7
|
-
/**
|
|
8
|
-
* Create a SelectionWindow
|
|
9
|
-
* @param {Object} data
|
|
10
|
-
*/
|
|
11
|
-
constructor(data) {
|
|
12
|
-
this.colour = DesignCore.Settings.selectionWindow;
|
|
13
|
-
this.lineWidth = 1;
|
|
14
|
-
this.dashPattern = [];
|
|
15
|
-
|
|
16
|
-
if (data) {
|
|
17
|
-
if (data.hasOwnProperty('points')) {
|
|
18
|
-
if (data.points[1].y > data.points[0].y) {
|
|
19
|
-
this.colour = DesignCore.Settings.selectionWindow;
|
|
20
|
-
this.dashPattern = [5];
|
|
21
|
-
} else {
|
|
22
|
-
this.colour = DesignCore.Settings.crossingWindow;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
this.points = [];
|
|
26
|
-
const point1 = new Point(data.points[0].x, data.points[0].y);
|
|
27
|
-
const point2 = new Point(data.points[1].x, data.points[1].y);
|
|
28
|
-
|
|
29
|
-
this.points.push(point1);
|
|
30
|
-
this.points.push(point2);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Draw the entity
|
|
37
|
-
* @param {Object} ctx - context
|
|
38
|
-
* @param {number} scale
|
|
39
|
-
*/
|
|
40
|
-
draw(ctx, scale) {
|
|
41
|
-
const colour = this.colour;
|
|
42
|
-
ctx.fillStyle = colour;
|
|
43
|
-
|
|
44
|
-
const scaledPattern = this.dashPattern.map((x) => x / scale);
|
|
45
|
-
|
|
46
|
-
try { // HTML Canvas
|
|
47
|
-
ctx.strokeStyle = Colours.rgbToString(colour);
|
|
48
|
-
ctx.fillStyle = Colours.rgbToString(colour);
|
|
49
|
-
ctx.lineWidth = this.lineWidth / scale;
|
|
50
|
-
ctx.beginPath();
|
|
51
|
-
ctx.globalAlpha = 0.2;
|
|
52
|
-
const width = this.points[1].x - this.points[0].x;
|
|
53
|
-
const height = this.points[1].y - this.points[0].y;
|
|
54
|
-
ctx.fillRect(this.points[0].x, this.points[0].y, width, height);
|
|
55
|
-
ctx.globalAlpha = 1.0;
|
|
56
|
-
this.drawRect(ctx);
|
|
57
|
-
ctx.setLineDash(scaledPattern);
|
|
58
|
-
ctx.stroke();
|
|
59
|
-
} catch { // Cairo
|
|
60
|
-
ctx.setLineWidth(this.lineWidth / scale);
|
|
61
|
-
const rgbColour = Colours.rgbToScaledRGB(colour);
|
|
62
|
-
ctx.setSourceRGBA(rgbColour.r, rgbColour.g, rgbColour.b, 0.2);
|
|
63
|
-
this.drawRect(ctx);
|
|
64
|
-
ctx.fillPreserve();
|
|
65
|
-
ctx.setSourceRGB(rgbColour.r, rgbColour.g, rgbColour.b);
|
|
66
|
-
|
|
67
|
-
ctx.setDash(scaledPattern, 1);
|
|
68
|
-
ctx.stroke();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Draw a rectangle
|
|
74
|
-
* @param {Object} ctx
|
|
75
|
-
*/
|
|
76
|
-
drawRect(ctx) {
|
|
77
|
-
ctx.moveTo(this.points[0].x, this.points[0].y);
|
|
78
|
-
ctx.lineTo(this.points[1].x, this.points[0].y);
|
|
79
|
-
ctx.lineTo(this.points[1].x, this.points[1].y);
|
|
80
|
-
ctx.lineTo(this.points[0].x, this.points[1].y);
|
|
81
|
-
ctx.lineTo(this.points[0].x, this.points[0].y);
|
|
82
|
-
}
|
|
83
|
-
}
|