Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Improve relationship creation tool (#53)
* Improve relationship creation tool - Adapt edge creation tool to desired behavior - Only perform selection when select tool is active (default) - Select edge after it has been created * Moved example files into subfolders for readability. --------- Co-authored-by: Harmen Wessels <97173058+harmen-xb@users.noreply.github.com>
- Loading branch information
1 parent
7514d76
commit d6e9ef8
Showing
21 changed files
with
337 additions
and
9 deletions.
There are no files selected for viewing
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
14 changes: 14 additions & 0 deletions
14
...es/glsp-client/src/browser/system-diagram/edge-creation-tool/edge-creation-tool-module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/******************************************************************************** | ||
* Copyright (c) 2024 CrossBreeze. | ||
********************************************************************************/ | ||
|
||
import { EdgeCreationTool, FeatureModule, edgeCreationToolModule, viewportModule } from '@eclipse-glsp/client'; | ||
import { SystemEdgeCreationTool } from './system-edge-creation-tool'; | ||
|
||
export const systemEdgeCreationToolModule = new FeatureModule( | ||
(bind, unbind, isBound, rebind) => { | ||
const context = { bind, unbind, isBound, rebind }; | ||
context.rebind(EdgeCreationTool).to(SystemEdgeCreationTool).inSingletonScope(); | ||
}, | ||
{ requires: [edgeCreationToolModule, viewportModule] } | ||
); |
227 changes: 227 additions & 0 deletions
227
...es/glsp-client/src/browser/system-diagram/edge-creation-tool/system-edge-creation-tool.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
/******************************************************************************** | ||
* Copyright (c) 2024 CrossBreeze. | ||
********************************************************************************/ | ||
|
||
import { | ||
Action, | ||
Connectable, | ||
CreateEdgeOperation, | ||
CursorCSS, | ||
Disposable, | ||
DragAwareMouseListener, | ||
EdgeCreationTool, | ||
EnableDefaultToolsAction, | ||
FeedbackEdgeEndMovingMouseListener, | ||
GEdge, | ||
GLSPActionDispatcher, | ||
GModelElement, | ||
HoverFeedbackAction, | ||
ITypeHintProvider, | ||
ModifyCSSFeedbackAction, | ||
Point, | ||
TriggerEdgeCreationAction, | ||
cursorFeedbackAction, | ||
findParentByFeature, | ||
isConnectable, | ||
isCtrlOrCmd | ||
} from '@eclipse-glsp/client'; | ||
import { | ||
DrawFeedbackEdgeAction, | ||
RemoveFeedbackEdgeAction | ||
} from '@eclipse-glsp/client/lib/features/tools/edge-creation/dangling-edge-feedback'; | ||
import { injectable } from '@theia/core/shared/inversify'; | ||
|
||
const CSS_EDGE_CREATION = 'edge-creation'; | ||
|
||
@injectable() | ||
export class SystemEdgeCreationTool extends EdgeCreationTool { | ||
override doEnable(): void { | ||
const mouseMovingFeedback = new FeedbackEdgeEndMovingMouseListener(this.anchorRegistry, this.feedbackDispatcher); | ||
const listener = new SystemEdgeCreationToolMouseListener(this.triggerAction, this.actionDispatcher, this.typeHintProvider, this); | ||
this.toDisposeOnDisable.push( | ||
listener, | ||
mouseMovingFeedback, | ||
this.mouseTool.registerListener(listener), | ||
this.mouseTool.registerListener(mouseMovingFeedback), | ||
this.registerFeedback([], this, [ | ||
RemoveFeedbackEdgeAction.create(), | ||
cursorFeedbackAction(), | ||
ModifyCSSFeedbackAction.create({ remove: [CSS_EDGE_CREATION] }) | ||
]) | ||
); | ||
} | ||
} | ||
|
||
export interface ConnectionContext { | ||
element?: GModelElement & Connectable; | ||
canConnect?: boolean; | ||
} | ||
|
||
export interface DragConnectionContext { | ||
element: GModelElement & Connectable; | ||
dragStart: Point; | ||
} | ||
|
||
export class SystemEdgeCreationToolMouseListener extends DragAwareMouseListener implements Disposable { | ||
protected source?: string; | ||
protected target?: string; | ||
protected proxyEdge: GEdge; | ||
|
||
protected dragContext?: DragConnectionContext; | ||
protected mouseMoveFeedback?: Disposable; | ||
protected sourceFeedback?: Disposable; | ||
|
||
constructor( | ||
protected triggerAction: TriggerEdgeCreationAction, | ||
protected actionDispatcher: GLSPActionDispatcher, | ||
protected typeHintProvider: ITypeHintProvider, | ||
protected tool: EdgeCreationTool | ||
) { | ||
super(); | ||
this.proxyEdge = new GEdge(); | ||
this.proxyEdge.type = triggerAction.elementTypeId; | ||
} | ||
|
||
protected isSourceSelected(): boolean { | ||
return this.source !== undefined; | ||
} | ||
|
||
protected isTargetSelected(): boolean { | ||
return this.target !== undefined; | ||
} | ||
|
||
override mouseDown(target: GModelElement, event: MouseEvent): Action[] { | ||
const result = super.mouseDown(target, event); | ||
if (event.button === 0 && !this.isSourceSelected()) { | ||
// update the current target | ||
const context = this.calculateContext(target, event); | ||
if (context.element && context.canConnect) { | ||
this.dragContext = { element: context.element, dragStart: { x: event.clientX, y: event.clientY } }; | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
override mouseMove(target: GModelElement, event: MouseEvent): Action[] { | ||
const result = super.mouseMove(target, event); | ||
if (this.isMouseDrag && this.dragContext && !this.isSourceSelected()) { | ||
const dragDistance = Point.maxDistance(this.dragContext.dragStart, { x: event.clientX, y: event.clientY }); | ||
if (dragDistance > 3) { | ||
// assign source if possible | ||
this.source = this.dragContext.element.id; | ||
this.tool.registerFeedback([ | ||
DrawFeedbackEdgeAction.create({ elementTypeId: this.triggerAction.elementTypeId, sourceId: this.source }) | ||
]); | ||
this.dragContext = undefined; | ||
} | ||
} | ||
this.updateFeedback(target, event); | ||
return result; | ||
} | ||
|
||
override draggingMouseUp(target: GModelElement, event: MouseEvent): Action[] { | ||
const result = super.draggingMouseUp(target, event); | ||
if (this.isSourceSelected()) { | ||
const context = this.calculateContext(target, event); | ||
if (context.element && context.canConnect) { | ||
this.target = context.element.id; | ||
result.push( | ||
CreateEdgeOperation.create({ | ||
elementTypeId: this.triggerAction.elementTypeId, | ||
sourceElementId: this.source!, | ||
targetElementId: this.target, | ||
args: this.triggerAction.args | ||
}) | ||
); | ||
if (!isCtrlOrCmd(event)) { | ||
result.push(EnableDefaultToolsAction.create()); | ||
} | ||
} | ||
} | ||
this.reinitialize(); | ||
return result; | ||
} | ||
|
||
override nonDraggingMouseUp(_element: GModelElement, event: MouseEvent): Action[] { | ||
this.reinitialize(); | ||
return [EnableDefaultToolsAction.create()]; | ||
} | ||
|
||
protected canConnect(element: GModelElement | undefined, role: 'source' | 'target'): boolean { | ||
return ( | ||
!!element && | ||
!!isConnectable(element) && | ||
element.canConnect(this.proxyEdge, role) && | ||
(role !== 'target' || this.source !== element?.id) | ||
); | ||
} | ||
|
||
protected updateFeedback(target: GModelElement, event: MouseEvent): void { | ||
const context = this.calculateContext(target, event); | ||
|
||
// source element feedback | ||
if (this.isSourceSelected()) { | ||
this.sourceFeedback = this.tool.registerFeedback( | ||
[HoverFeedbackAction.create({ mouseoverElement: this.source!, mouseIsOver: true })], | ||
this.proxyEdge, | ||
[HoverFeedbackAction.create({ mouseoverElement: this.source!, mouseIsOver: false })] | ||
); | ||
} | ||
|
||
// cursor feedback | ||
if (!context.element || context.element?.id === this.source) { | ||
// by default we want to use the edge creation CSS when the tool is active | ||
this.registerFeedback( | ||
[ModifyCSSFeedbackAction.create({ add: [CSS_EDGE_CREATION] })], | ||
[ModifyCSSFeedbackAction.create({ remove: [CSS_EDGE_CREATION] })] | ||
); | ||
return; | ||
} | ||
|
||
if (!context.canConnect) { | ||
this.registerFeedback([cursorFeedbackAction(CursorCSS.OPERATION_NOT_ALLOWED)], [cursorFeedbackAction()]); | ||
return; | ||
} | ||
|
||
const cursorCss = this.isSourceSelected() ? CursorCSS.EDGE_CREATION_TARGET : CursorCSS.EDGE_CREATION_SOURCE; | ||
this.registerFeedback( | ||
[cursorFeedbackAction(cursorCss), HoverFeedbackAction.create({ mouseoverElement: context.element.id, mouseIsOver: true })], | ||
[cursorFeedbackAction(), HoverFeedbackAction.create({ mouseoverElement: context.element.id, mouseIsOver: false })] | ||
); | ||
} | ||
|
||
protected registerFeedback(feedbackActions: Action[], cleanupActions?: Action[]): Disposable { | ||
this.mouseMoveFeedback?.dispose(); | ||
this.mouseMoveFeedback = this.tool.registerFeedback(feedbackActions, this, cleanupActions); | ||
return this.mouseMoveFeedback; | ||
} | ||
|
||
protected calculateContext(target: GModelElement, event: MouseEvent, previousContext?: ConnectionContext): ConnectionContext { | ||
const context: ConnectionContext = {}; | ||
context.element = findParentByFeature(target, isConnectable); | ||
if (previousContext && previousContext.element === context.element) { | ||
return previousContext; | ||
} | ||
if (!this.isSourceSelected()) { | ||
context.canConnect = this.canConnect(context.element, 'source'); | ||
} else if (!this.isTargetSelected()) { | ||
context.canConnect = this.canConnect(context.element, 'target'); | ||
} else { | ||
context.canConnect = false; | ||
} | ||
return context; | ||
} | ||
|
||
protected reinitialize(): void { | ||
this.source = undefined; | ||
this.target = undefined; | ||
this.tool.registerFeedback([RemoveFeedbackEdgeAction.create()]); | ||
this.dragContext = undefined; | ||
this.mouseMoveFeedback?.dispose(); | ||
this.sourceFeedback?.dispose(); | ||
} | ||
|
||
dispose(): void { | ||
this.reinitialize(); | ||
} | ||
} |
23 changes: 23 additions & 0 deletions
23
packages/glsp-client/src/browser/system-diagram/select-tool/select-tool-module.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/******************************************************************************** | ||
* Copyright (c) 2024 CrossBreeze. | ||
********************************************************************************/ | ||
import { | ||
FeatureModule, | ||
RankedSelectMouseListener, | ||
SelectAllCommand, | ||
SelectCommand, | ||
SelectFeedbackCommand, | ||
TYPES, | ||
bindAsService, | ||
configureCommand | ||
} from '@eclipse-glsp/client'; | ||
import { SystemSelectTool } from './select-tool'; | ||
|
||
export const systemSelectModule = new FeatureModule((bind, _unbind, isBound) => { | ||
const context = { bind, isBound }; | ||
configureCommand(context, SelectCommand); | ||
configureCommand(context, SelectAllCommand); | ||
configureCommand(context, SelectFeedbackCommand); | ||
bindAsService(context, TYPES.IDefaultTool, SystemSelectTool); | ||
bind(RankedSelectMouseListener).toSelf().inSingletonScope(); | ||
}); |
25 changes: 25 additions & 0 deletions
25
packages/glsp-client/src/browser/system-diagram/select-tool/select-tool.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
/******************************************************************************** | ||
* Copyright (c) 2024 CrossBreeze. | ||
********************************************************************************/ | ||
|
||
import { GLSPMouseTool, RankedSelectMouseListener, Tool } from '@eclipse-glsp/client'; | ||
import { inject, injectable } from '@theia/core/shared/inversify'; | ||
|
||
@injectable() | ||
export class SystemSelectTool implements Tool { | ||
static ID = 'tool_system_select'; | ||
|
||
id = SystemSelectTool.ID; | ||
isEditTool = false; | ||
|
||
@inject(GLSPMouseTool) protected mouseTool: GLSPMouseTool; | ||
@inject(RankedSelectMouseListener) protected listener: RankedSelectMouseListener; | ||
|
||
enable(): void { | ||
this.mouseTool.registerListener(this.listener); | ||
} | ||
|
||
disable(): void { | ||
this.mouseTool.deregister(this.listener); | ||
} | ||
} |
Oops, something went wrong.