Skip to content

Commit a8a6f18

Browse files
committed
double click to show node selection menu
1 parent d4fd934 commit a8a6f18

File tree

5 files changed

+220
-0
lines changed

5 files changed

+220
-0
lines changed

src/Editor.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { calculateDirectionAlignment } from "./util/isPointingAt";
1111
import { isMouseHandler } from "./events/isMouseHandler";
1212
import { IOverlayRenderer } from "./layout/IOverlayRenderer";
1313
import { LayoutElement } from "./layout/LayoutElement";
14+
import { onDoubleClick } from "./util/onDoubleClick";
15+
import { createNewNode } from "./ui/NodeSelectionModal";
1416

1517
type MouseInfo = {
1618
clientX:number, clientY:number
@@ -26,6 +28,9 @@ type OuletCandidate = {
2628
alignmentScore:number
2729
}
2830

31+
/**
32+
* I'm the editor. I show the nodes, and run the ThreeJs background scene.
33+
*/
2934
export class Editor {
3035
private _ctx:CanvasRenderingContext2D;
3136
private mouseDrag = false;
@@ -73,6 +78,9 @@ export class Editor {
7378

7479
// fix aspect ratio...
7580
this.ctx.scale(1, this.aspectCorrection) ;
81+
82+
// Double Click
83+
onDoubleClick(canvas, ev=>this.showNodeCreationMenu(ev));
7684

7785
//#region MOUSE WHEEL
7886
const mouseWheelCaptured = (obj:LayoutElement, globalMouse:Vector2Like, delta:number) => obj.traverse( elem=>{
@@ -691,4 +699,17 @@ export class Editor {
691699

692700
requestAnimationFrame(()=>this.start());
693701
}
702+
703+
protected showNodeCreationMenu( ev:MouseEvent ) {
704+
const at = this.getCanvasMousePosition(this.mouse ); ;
705+
706+
createNewNode( ev.clientX, ev.clientY, newNode => {
707+
708+
newNode.x = at.x;
709+
newNode.y = at.y;
710+
711+
this.add( newNode );
712+
713+
});
714+
}
694715
}

src/EditorNodes.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { FillStyle, Theme } from "./colors/Theme";
2+
import { UVNode } from "./nodes/attribute/UVNode";
3+
import { MeshStandardNode } from "./nodes/shader/MeshStandardNode";
4+
import { ImageTextureNode } from "./nodes/texture/ImageTextureNode";
5+
import { WinNode } from "./nodes/WinNode";
6+
7+
// Define the type for class constructors that extend BaseType
8+
type Constructor<T extends WinNode> = new (...args: any[]) => T;
9+
10+
export type NodeGroupType = {
11+
group:string
12+
color:string
13+
nodes:{ TypeClass:Constructor<WinNode>, name:string }[]
14+
}
15+
16+
export const NodeTypes : NodeGroupType[] = [
17+
{
18+
group:"Attribute",
19+
color:Theme.config.groupAttribute as string,
20+
nodes:[
21+
{ TypeClass:UVNode, name:"UV" }
22+
]
23+
},
24+
{
25+
group:"Shader",
26+
color:Theme.config.groupShader as string,
27+
nodes:[
28+
{ TypeClass:MeshStandardNode, name:"Mesh Standard"}
29+
]
30+
},
31+
{
32+
group:"Texture",
33+
color:Theme.config.groupTexture as string,
34+
nodes:[
35+
{ TypeClass:ImageTextureNode, name:"Image Texture"}
36+
]
37+
}
38+
]
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
.root {
2+
max-height: 300px;
3+
width: 300px;
4+
background-color: black;
5+
position: fixed;
6+
overflow-y: auto;
7+
z-index: 9;
8+
padding: 10px;
9+
border-radius: 5px;
10+
}
11+
12+
.root ul {
13+
margin: 0px;
14+
padding: 0px;
15+
list-style-type: none;
16+
}
17+
.root li {
18+
cursor: pointer;
19+
padding: 2px;
20+
}
21+
.root li:hover {
22+
background-color: rgba(255,255,255,0.1);
23+
24+
25+
}
26+
27+
.groupTitle {
28+
padding: 3px 10px;
29+
font-size: 0.8em;
30+
display: inline-block;
31+
border-radius: 5px;
32+
}
33+
34+
.root > input[type="text"] {
35+
width: 100%;
36+
box-sizing: border-box;
37+
margin-bottom: 10px;
38+
}
39+
40+
.hide {
41+
display: none;
42+
}

src/ui/NodeSelectionModal.ts

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { NodeTypes } from "../EditorNodes";
2+
import { WinNode } from "../nodes/WinNode";
3+
import styles from "./NodeSelectionModal.module.css";
4+
5+
type NodeHandler = (node:WinNode)=>void
6+
7+
class NodeSelector {
8+
private div:HTMLDivElement;
9+
private input:HTMLInputElement;
10+
private _onCreated?:NodeHandler;
11+
12+
constructor() {
13+
this.div = document.createElement("div");
14+
this.div.classList.add( styles.root, styles.hide );
15+
document.body.appendChild( this.div );
16+
17+
this.input = document.createElement("input");
18+
this.input.type= "text";
19+
this.input.placeholder="Find node by name...";
20+
21+
this.div.appendChild( this.input );
22+
23+
window.addEventListener("mousedown", ev=>{
24+
25+
if( this.visible ) this.hide()
26+
27+
});
28+
29+
this.div.addEventListener("mousedown", ev=>{
30+
ev.stopImmediatePropagation()
31+
})
32+
33+
// render types!
34+
//#region Node Types
35+
NodeTypes.forEach( groupType=>{
36+
37+
const ul = document.createElement("ul");
38+
const groupDiv = this.div.appendChild( document.createElement("div") );
39+
const title = groupDiv.appendChild( document.createElement("div"));
40+
41+
title.innerText = groupType.group;
42+
title.style.backgroundColor = groupType.color;
43+
title.classList.add( styles.groupTitle )
44+
45+
groupType.nodes.forEach( node => {
46+
47+
const li = ul.appendChild( document.createElement("li"));
48+
49+
li.innerText = "→" + node.name;
50+
li.addEventListener("click", ev=>this.addNewNode(new node.TypeClass))
51+
52+
});
53+
54+
groupDiv.classList.add( styles.group );
55+
groupDiv.appendChild(ul)
56+
57+
58+
59+
});
60+
//#endregion
61+
}
62+
63+
get visible() {
64+
return !this.div.classList.contains(styles.hide)
65+
}
66+
67+
private addNewNode( node:WinNode )
68+
{
69+
this._onCreated?.(node);
70+
this.hide();
71+
72+
}
73+
74+
hide() {
75+
this.div.classList.add(styles.hide);
76+
this._onCreated = undefined;
77+
}
78+
79+
show( posX:number, porY:number, onCreated:NodeHandler ) {
80+
this.div.classList.remove(styles.hide);
81+
this.div.style.left = `${posX-20}px`;
82+
this.div.style.top = `${porY-20}px`;
83+
84+
this.input.value="";
85+
this.input.focus()
86+
87+
this._onCreated = onCreated;
88+
}
89+
}
90+
91+
let modal:NodeSelector;
92+
93+
export function createNewNode( showModalAtX:number, showModalAtY:number, onCreated:NodeHandler )
94+
{
95+
if(!modal) modal = new NodeSelector();
96+
97+
modal.show(showModalAtX, showModalAtY, onCreated);
98+
}

src/util/onDoubleClick.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
export function onDoubleClick( elem:HTMLElement , onClick:(ev:MouseEvent)=>void )
2+
{
3+
let clickCount = 0;
4+
let clickTimer = 0;
5+
6+
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+
}
20+
});
21+
}

0 commit comments

Comments
 (0)