Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Adapt edge creation tool to desired behavior - Only perform selection when select tool is active (default) - Select edge after it has been created
- Loading branch information
1 parent
7514d76
commit 9aae9d8
Showing
9 changed files
with
337 additions
and
9 deletions.
There are no files selected for viewing
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.