Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/changelog.d/186.added.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Remote rendering 4.3a: forward mouse and wheel input to server
1 change: 1 addition & 0 deletions src/ansys/visor/viewer/renderer/server_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def __init__(
self._camera_publisher = camera_publisher
self._bounding_box_visible = False
super().__init__()
self._orientation_widget.animate_off()

# ------------------------------------------------------------------
# Server-only surface
Expand Down
4 changes: 4 additions & 0 deletions src/ansys/visor/viewer/vtk/widgets/visor_orientation.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ def is_on(self):
"""Return whether the widget is on or not."""
return self.__widget.GetEnabled() == 1

def animate_off(self) -> None:
"""Snap the camera on an axis click instead of animating it over interactor timer events."""
self.__widget.AnimateOff()

def register_with_local_view(self, local_view: LocalView):
"""Register the widget with the local view."""
self.__widget_wasm_id = local_view.register_vtk_object(self.__widget)
Expand Down
24 changes: 24 additions & 0 deletions src/ansys/visor/visor-client/src/input/ClickTracker.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/** Recognises a click: a press and a release at the same client position with no leave or blur between them. */
export class ClickTracker {
/** Record a press at (clientX, clientY). */
press(clientX: number, clientY: number): void {
this.#pressX = clientX;
this.#pressY = clientY;
}

/** Forget any press; called on mouseout and blur. */
reset(): void {
this.#pressX = null;
this.#pressY = null;
}

/** True if a release at (clientX, clientY) completes a click; always forgets the press. */
release(clientX: number, clientY: number): boolean {
const isClick = this.#pressX === clientX && this.#pressY === clientY;
this.reset();
return isClick;
}

#pressX: number | null = null;
#pressY: number | null = null;
}
46 changes: 46 additions & 0 deletions src/ansys/visor/visor-client/src/input/MouseButtonState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/** VTK button a gesture drives: 0 left (rotate), 1 middle (pan), 2 right (zoom). */
export type VtkButton = 0 | 1 | 2;

/** Which physical buttons are held, and the one VTK button they drive together. */
export class MouseButtonState {
/** Record that DOM button (0 left, 1 middle, 2 right) went down or up; other values are ignored. */
setButton(button: number, down: boolean): void {
if (button === 0) {
this.#left = down;
} else if (button === 1) {
this.#middle = down;
} else if (button === 2) {
this.#right = down;
}
}

/** Whether DOM button (0 left, 1 middle, 2 right) is held; false for any other value. */
isDown(button: number): boolean {
if (button === 0) {
return this.#left;
} else if (button === 1) {
return this.#middle;
} else if (button === 2) {
return this.#right;
}
return false;
}

/** The VTK button the held buttons drive: left+right 1, right 2, left 0, middle 0, none null. */
effectiveButton(): VtkButton | null {
if (this.#left && this.#right) {
return 1;
} else if (this.#right) {
return 2;
} else if (this.#left) {
return 0;
} else if (this.#middle) {
return 0;
}
return null;
}

#left = false;
#middle = false;
#right = false;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { MouseButtonState } from '../input/MouseButtonState';

/**
* The held DOM buttons map to the one VTK button they drive together: a
* middle press drives rotate (0), and pan (1) is the left and right chord.
*
* Expected values are hand-written literals.
*/

/** A state with each DOM button in `pressed` held, in order. */
function holding(...pressed: number[]): MouseButtonState {
const state = new MouseButtonState();
for (const button of pressed) {
state.setButton(button, true);
}
return state;
}

describe('MouseButtonState', () => {
test('left alone drives 0', () => {
expect(holding(0).effectiveButton()).toBe(0);
});

test('right alone drives 2', () => {
expect(holding(2).effectiveButton()).toBe(2);
});

test('left and right drive 1', () => {
expect(holding(0, 2).effectiveButton()).toBe(1);
});

test('middle alone drives 0', () => {
expect(holding(1).effectiveButton()).toBe(0);
});

test('left and middle drive 0', () => {
expect(holding(0, 1).effectiveButton()).toBe(0);
});

test('nothing held drives null', () => {
expect(holding().effectiveButton()).toBeNull();
});

test('releasing every held button drives null', () => {
const state = holding(0, 1, 2);

state.setButton(0, false);
state.setButton(1, false);
state.setButton(2, false);

expect(state.effectiveButton()).toBeNull();
});

test('isDown reports only the button held', () => {
const state = holding(1);

expect([state.isDown(0), state.isDown(1), state.isDown(2)]).toEqual([false, true, false]);
});
});
Loading
Loading