Skip to content

Commit 639a707

Browse files
committed
box selection
1 parent 01b7ed2 commit 639a707

File tree

4 files changed

+167
-27
lines changed

4 files changed

+167
-27
lines changed

src/Editor.ts

Lines changed: 132 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ export class Editor {
4646

4747
readonly scene:ThreeScene;
4848

49+
/**
50+
* Start of a box selection. In canva's coordinate.
51+
*/
52+
private boxSelectionStart?:Vector2Like;
53+
54+
/**
55+
* Nodes selected...
56+
*/
57+
private selectedNodes:Node[]=[]
58+
4959
/**
5060
* An object that will intercept and handle the mouse events.
5161
*/
@@ -141,6 +151,12 @@ export class Editor {
141151
const mousePos = this.getMousePos( event);
142152
const canvasPos = this.getCanvasMousePosition(mousePos);
143153

154+
// if( this.boxSelectionStart )
155+
// {
156+
// this.selectNodesInsideBoxSelection(this.boxSelectionStart, canvasPos);
157+
// return;
158+
// }
159+
144160
let sx = ( mousePos.x - this.mouse.x ) ;
145161
let sy = ( mousePos.y - this.mouse.y ) ;
146162

@@ -161,8 +177,21 @@ export class Editor {
161177
}
162178
else if( this.focusedChild )
163179
{
164-
this.focusedChild.x += sx;
165-
this.focusedChild.y += sy;
180+
//this.focusedChild.x += sx;
181+
//this.focusedChild.y += sy;
182+
183+
// move the selection
184+
this.selectedNodes.forEach(node=>{
185+
node.x += sx;
186+
node.y += sy;
187+
})
188+
}
189+
else if( this.boxSelectionStart )
190+
{
191+
//
192+
// update box selection
193+
//
194+
this.selectNodesInsideBoxSelection(this.boxSelectionStart, canvasPos);
166195
}
167196
else
168197
{
@@ -189,11 +218,12 @@ export class Editor {
189218
// main mouse button to move stuff arround...
190219
else if( ev.button==0 )
191220
{
192-
const cursor = this.getCanvasMousePosition(this.mouse );
193-
221+
const cursor = this.getCanvasMousePosition(this.mouse );
194222

195223
if( this.overlay )
196224
{
225+
this.clearBoxSelection();
226+
197227
if( this.clickElementAt( this.mouse, this.overlay.overlayBody ) )
198228
{
199229
return;
@@ -248,7 +278,10 @@ export class Editor {
248278

249279
});
250280

251-
if( this.selectedOutlet ) continue;
281+
if( this.selectedOutlet ) {
282+
this.clearBoxSelection();
283+
break;
284+
}
252285
//#endregion
253286

254287
//#region Click on element...
@@ -268,14 +301,17 @@ export class Editor {
268301
// default: mouse down on the node window.
269302
//
270303
if( cursor.x>obj.x && cursor.x<obj.x+obj.width(this.ctx) && cursor.y>obj.y && cursor.y<obj.y+obj.height(this.ctx) )
271-
{
304+
{
272305
// default... will make the object move...
273306
this.focusedChild = obj;
274307
this.bingToTop(obj);
308+
309+
310+
275311
break; //<-- to avoid processing childrens under us....
276-
}
312+
}
277313

278-
};
314+
};
279315

280316
//
281317
// if we clicked an outlet, we need to know which of the available outlets are valid to be connected to...
@@ -295,6 +331,31 @@ export class Editor {
295331
});
296332
}
297333

334+
//
335+
// if we selected a child...
336+
//
337+
else
338+
{
339+
if( this.focusedChild )
340+
{
341+
if( !this.selectedNodes.includes(this.focusedChild) )
342+
{
343+
this.clearBoxSelection()
344+
this.selectedNodes.push(this.focusedChild);
345+
}
346+
347+
return;
348+
}
349+
else
350+
{
351+
this.clearBoxSelection();
352+
}
353+
354+
this.boxSelectionStart = cursor;
355+
356+
console.log("START SELECTION")
357+
}
358+
298359
}
299360
});
300361
//#endregion
@@ -304,6 +365,8 @@ export class Editor {
304365

305366
const cursor = this.getCanvasMousePosition( this.mouse );
306367

368+
this.boxSelectionStart = undefined;
369+
307370
if( this.eventsHandler )
308371
{
309372
this.eventsHandler.onMouseUp();
@@ -686,6 +749,64 @@ export class Editor {
686749

687750
}
688751

752+
protected drawBoxSelection( ctx:CanvasRenderingContext2D ) {
753+
if(!this.boxSelectionStart) return;
754+
755+
const cursor = this.getCanvasMousePosition(this.mouse );
756+
757+
ctx.save();
758+
ctx.beginPath()
759+
760+
ctx.rect(this.boxSelectionStart.x,this.boxSelectionStart.y,
761+
cursor.x-this.boxSelectionStart.x,
762+
cursor.y-this.boxSelectionStart.y
763+
);
764+
765+
ctx.lineWidth = 2
766+
ctx.setLineDash([3, 3]);
767+
ctx.strokeStyle = Theme.config.selectionBoxColor;
768+
ctx.stroke();
769+
ctx.restore()
770+
}
771+
772+
protected clearBoxSelection() {
773+
this.selectedNodes.length=0;
774+
}
775+
776+
/**
777+
* Select the nodes inside the box...
778+
* @param poin1 In canva's coordinate
779+
* @param point2 In canva's coordinate
780+
*/
781+
protected selectNodesInsideBoxSelection( point1:Vector2Like, point2:Vector2Like )
782+
{
783+
// reset
784+
this.selectedNodes.length = 0;
785+
786+
const left = Math.min(point1.x, point2.x);
787+
const right = Math.max(point1.x, point2.x);
788+
const top = Math.min(point1.y, point2.y);
789+
const bottom = Math.max(point1.y, point2.y);
790+
791+
const ctx = this._ctx;
792+
793+
this.objs.forEach( node => {
794+
795+
const objLeft = node.x;
796+
const objRight = node.x + node.width(ctx);
797+
const objTop = node.y;
798+
const objBottom = node.y + node.height(ctx);
799+
800+
const overlaps = (left <= objRight &&
801+
right >= objLeft &&
802+
top <= objBottom &&
803+
bottom >= objTop);
804+
805+
if( overlaps ) this.selectedNodes.push(node);
806+
807+
});
808+
}
809+
689810
start() {
690811

691812
this.scene.render()
@@ -704,13 +825,15 @@ export class Editor {
704825

705826
ctx.save();
706827

828+
obj.selected = this.selectedNodes.includes(obj)
707829
obj.draw(ctx);
708830

709831
ctx.restore();
710832

711833
});
712834

713-
this.drawAvailableConnectionPipes(ctx);
835+
this.drawAvailableConnectionPipes(ctx);
836+
this.drawBoxSelection(ctx);
714837

715838
ctx.save()
716839
this.overlay?.renderOverlay(ctx);

src/colors/Theme.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ export class Theme {
1515
readonly nodeRowHeight = 20;
1616
readonly nodeBorderRadius = 5;
1717

18+
readonly selectionBoxColor = "white";
19+
1820
readonly fontFamily:string = "Arial"; //https://developer.mozilla.org/en-US/docs/Web/CSS/font-family
1921

2022
readonly nodeWinBgColor : FillStyle = "#303030";

src/nodes/Node.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ export class Node extends LayoutElement {
1010

1111
editor!:Editor;
1212
canBeDeleted = true;
13+
14+
/**
15+
* Mostly used to visually show a sign that this node is selected
16+
*/
17+
selected = false;
1318

1419
x = 0
1520
y = 0
@@ -74,8 +79,8 @@ export class Node extends LayoutElement {
7479
//ctx.shadowColor = 'transparent'; //Or, you can reset all shadow properties.
7580
this.boxShadow(ctx, 0);
7681

77-
ctx.strokeStyle = 'black';
78-
ctx.lineWidth = 0.5;
82+
ctx.strokeStyle = this.selected? Theme.config.selectionBoxColor : 'black';
83+
ctx.lineWidth = this.selected? 1 : 0.5;
7984
ctx.stroke();
8085
}
8186

src/util/onDoubleClick.ts

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
1-
export function onDoubleClick( elem:HTMLElement , onClick:(ev:MouseEvent)=>void )
2-
{
1+
export function onDoubleClick(elem: HTMLElement, onClick: (ev: MouseEvent) => void) {
32
let clickCount = 0;
43
let clickTimer = 0;
5-
4+
let clickX = 0;
5+
let clickY = 0;
6+
67
elem.addEventListener('click', (event) => {
7-
clickCount++;
8-
9-
if (clickCount === 1) {
10-
clickTimer = setTimeout(() => {
11-
// Single click action
12-
clickCount = 0;
13-
}, 300); // Adjust the delay as needed (milliseconds)
14-
} else if (clickCount === 2) {
15-
clearTimeout(clickTimer);
16-
// Double click action
17-
onClick(event)
18-
clickCount = 0;
19-
}
8+
9+
if (event.clientX == clickX && event.clientY == clickY) {
10+
clickCount++;
11+
}
12+
else {
13+
clickCount = 1;
14+
}
15+
16+
clickX = event.clientX;
17+
clickY = event.clientY;
18+
19+
if (clickCount === 1) {
20+
clickTimer = setTimeout(() => {
21+
// Single click action
22+
clickCount = 0;
23+
}, 300); // Adjust the delay as needed (milliseconds)
24+
} else if (clickCount === 2) {
25+
clearTimeout(clickTimer);
26+
// Double click action
27+
onClick(event)
28+
clickCount = 0;
29+
}
2030
});
2131
}

0 commit comments

Comments
 (0)