Skip to content

Commit

Permalink
Introduce moving and zooming of the viewport and elements via the key…
Browse files Browse the repository at this point in the history
…board (#241)

- Introduce tool and handler for moving viewport and elements via keyboard shortcut
- Introduce tool and handler for zooming viewport and elements via keyboard shortcut
- Provide tool for an improved implementation for deselecting elements

Part of eclipse-glsp/glsp#1029
  • Loading branch information
aylin-sarioglu committed Jun 6, 2023
1 parent 4e10035 commit c54a4b8
Show file tree
Hide file tree
Showing 9 changed files with 756 additions and 4 deletions.
8 changes: 6 additions & 2 deletions packages/client/src/features/accessibility/di.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,16 @@
********************************************************************************/

import { ContainerModule } from 'inversify';
import { configureMoveZoom } from './move-zoom/di.config';
import { configureSearchPaletteModule } from './search/di.config';
import { configureViewKeyTools } from './view-key-tools/di.config';

/**
* Enables the accessibility tools for a keyboard-only-usage
*/
export const glspAccessibilityModule = new ContainerModule((bind, _unbind, isBound, rebind) => {
const context = { bind, isBound, rebind };
export const glspAccessibilityModule = new ContainerModule((bind, unbind, isBound, rebind) => {
const context = { bind, unbind, isBound, rebind };
configureViewKeyTools(context);
configureMoveZoom(context);
configureSearchPaletteModule(context);
});
43 changes: 43 additions & 0 deletions packages/client/src/features/accessibility/move-zoom/di.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/********************************************************************************
* Copyright (c) 2023 Business Informatics Group (TU Wien) and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { BindingContext } from '@eclipse-glsp/protocol';
import { ContainerModule } from 'inversify';
import { configureActionHandler } from 'sprotty';
import { MoveElementAction, MoveElementHandler, MoveViewportAction, MoveViewportHandler } from './move-handler';
import { ZoomElementAction, ZoomElementHandler, ZoomViewportAction, ZoomViewportHandler } from './zoom-handler';

/**
* Handles move and zoom actions.
*/
export const glspMoveZoomModule = new ContainerModule((bind, unbind, isBound, rebind) => {
const context = { bind, unbind, isBound, rebind };
configureMoveZoom(context);
});

export function configureMoveZoom(context: BindingContext): void {
context.bind(MoveViewportHandler).toSelf().inSingletonScope();
context.bind(MoveElementHandler).toSelf().inSingletonScope();

context.bind(ZoomViewportHandler).toSelf().inSingletonScope();
context.bind(ZoomElementHandler).toSelf().inSingletonScope();

configureActionHandler(context, MoveViewportAction.KIND, MoveViewportHandler);
configureActionHandler(context, MoveElementAction.KIND, MoveElementHandler);

configureActionHandler(context, ZoomViewportAction.KIND, ZoomViewportHandler);
configureActionHandler(context, ZoomElementAction.KIND, ZoomElementHandler);
}
198 changes: 198 additions & 0 deletions packages/client/src/features/accessibility/move-zoom/move-handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/********************************************************************************
* Copyright (c) 2023 Business Informatics Group (TU Wien) and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { Action, ChangeBoundsOperation, Point, SetViewportAction, Viewport } from '@eclipse-glsp/protocol';
import { inject, injectable } from 'inversify';
import { throttle } from 'lodash';
import { findParentByFeature, IActionDispatcher, IActionHandler, ICommand, isViewport, SModelRoot } from 'sprotty';
import { EditorContextService } from '../../../base/editor-context-service';
import { TYPES } from '../../../base/types';
import { getElements, isSelectableAndBoundsAware, SelectableBoundsAware } from '../../../utils/smodel-util';

/**
* Action for triggering moving of the viewport.
*/
export interface MoveViewportAction extends Action {
kind: typeof MoveViewportAction.KIND;
/**
* used to specify the amount to be moved in the x-axis
*/
moveX: number;
/**
* used to specify the amount to be moved in the y-axis
*/
moveY: number;
}

export namespace MoveViewportAction {
export const KIND = 'moveViewportAction';

export function is(object: any): object is MoveViewportAction {
return Action.hasKind(object, KIND);
}

export function create(moveX: number, moveY: number): MoveViewportAction {
return { kind: KIND, moveX, moveY };
}
}

/**
* Action for triggering moving of elements.
*/
export interface MoveElementAction extends Action {
kind: typeof MoveElementAction.KIND;
/**
* used to specify the elements to be zoomed in/out
*/
elementIds: string[];
/**
* used to specify the amount to be moved in the x-axis
*/
moveX: number;
/**
* used to specify the amount to be moved in the y-axis
*/
moveY: number;
}

export namespace MoveElementAction {
export const KIND = 'moveElementAction';

export function is(object: any): object is MoveElementAction {
return Action.hasKind(object, KIND);
}

export function create(elementIds: string[], moveX: number, moveY: number): MoveElementAction {
return { kind: KIND, elementIds, moveX, moveY };
}
}

/* The MoveViewportHandler class is an implementation of the IActionHandler interface that handles
moving of the viewport. */
@injectable()
export class MoveViewportHandler implements IActionHandler {
@inject(EditorContextService)
protected editorContextService: EditorContextService;

@inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher;
protected readonly throttledHandleViewportMove = throttle((action: MoveViewportAction) => this.handleMoveViewport(action), 150);

handle(action: Action): void | Action | ICommand {
if (MoveViewportAction.is(action)) {
this.throttledHandleViewportMove(action);
}
}

handleMoveViewport(action: MoveViewportAction): void {
const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport);
if (!viewport) {
return;
}
this.dispatcher.dispatch(this.moveViewport(viewport, action.moveX, action.moveY));
}

protected moveViewport(viewport: SModelRoot & Viewport, offsetX: number, offSetY: number): SetViewportAction {
const newViewport: Viewport = {
scroll: {
x: viewport.scroll.x + offsetX,
y: viewport.scroll.y + offSetY
},
zoom: viewport.zoom
};

return SetViewportAction.create(viewport.id, newViewport, { animate: true });
}
}

/* The MoveElementHandler class is an implementation of the IActionHandler interface that handles
moving elements. */
@injectable()
export class MoveElementHandler implements IActionHandler {
@inject(EditorContextService)
protected editorContextService: EditorContextService;
@inject(TYPES.IActionDispatcher) protected dispatcher: IActionDispatcher;
protected readonly throttledHandleElementMove = throttle((action: MoveElementAction) => this.handleMoveElement(action), 150);

handle(action: Action): void | Action | ICommand {
if (MoveElementAction.is(action)) {
this.throttledHandleElementMove(action);
}
}

handleMoveElement(action: MoveElementAction): void {
const viewport = findParentByFeature(this.editorContextService.modelRoot, isViewport);
if (!viewport) {
return;
}

const elements = getElements(this.editorContextService.modelRoot.index, action.elementIds, isSelectableAndBoundsAware);

this.dispatcher.dispatchAll(this.move(viewport, elements, action.moveX, action.moveY));
}

protected getBounds(element: SelectableBoundsAware, offSetX: number, offSetY: number): Point {
return { x: element.bounds.x + offSetX, y: element.bounds.y + offSetY };
}

protected adaptViewport(
viewport: SModelRoot & Viewport,
newPoint: Point,
moveX: number,
moveY: number
): MoveViewportAction | undefined {
if (
newPoint.x < viewport.scroll.x ||
newPoint.x > viewport.scroll.x + viewport.canvasBounds.width ||
newPoint.y < viewport.scroll.y ||
newPoint.y > viewport.scroll.y + viewport.canvasBounds.height
) {
return MoveViewportAction.create(moveX, moveY);
}
return;
}

protected moveElement(element: SelectableBoundsAware, offSetX: number, offSetY: number): ChangeBoundsOperation {
return ChangeBoundsOperation.create([
{
elementId: element.id,
newSize: {
width: element.bounds.width,
height: element.bounds.height
},
newPosition: {
x: element.bounds.x + offSetX,
y: element.bounds.y + offSetY
}
}
]);
}

protected move(viewport: SModelRoot & Viewport, selectedElements: SelectableBoundsAware[], deltaX: number, deltaY: number): Action[] {
const results: Action[] = [];

if (selectedElements.length !== 0) {
selectedElements.forEach(currentElement => {
results.push(this.moveElement(currentElement, deltaX, deltaY));
const newPosition = this.getBounds(currentElement, deltaX, deltaY);
const viewportAction = this.adaptViewport(viewport, newPosition, deltaX, deltaY);
if (viewportAction) {
results.push(viewportAction);
}
});
}
return results;
}
}

0 comments on commit c54a4b8

Please sign in to comment.