Merge branch 'jogging' into 'v3'
Generalised Jogging Closes #680 See merge request openflexure/openflexure-microscope-server!476
This commit is contained in:
commit
3f7de554d9
10 changed files with 906 additions and 206 deletions
|
|
@ -29,6 +29,8 @@ import loadingContent from "./components/loadingContent.vue";
|
|||
import Mousetrap from "mousetrap";
|
||||
import { eventBus } from "./eventBus.js";
|
||||
|
||||
const move_keys = ["up", "down", "left", "right", "pageup", "pagedown"];
|
||||
|
||||
Mousetrap.prototype.stopCallback = function (e, element) {
|
||||
// if the element has the class "mousetrap" then no need to stop
|
||||
if ((" " + element.className + " ").indexOf(" Mousetrap ") > -1) {
|
||||
|
|
@ -65,6 +67,10 @@ export default {
|
|||
keyboardManual: [],
|
||||
systemDark: undefined,
|
||||
themeObserver: undefined,
|
||||
keysDown: new Set(),
|
||||
lastJogTime: 0,
|
||||
jogDistance: 600,
|
||||
jogTime: 300,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -130,34 +136,31 @@ export default {
|
|||
this.toggleModalElement(this.$refs["keyboardManualModal"]); // Calls the mixin
|
||||
});
|
||||
|
||||
// Arrow keys
|
||||
Mousetrap.bind(
|
||||
["up", "down", "left", "right"],
|
||||
(event) => {
|
||||
this.arrowKeysDown[event.keyCode] = true; //Add key to array
|
||||
this.navigateKeyHandler();
|
||||
move_keys,
|
||||
(event, key) => {
|
||||
event.preventDefault();
|
||||
this.keysDown.add(key);
|
||||
this.updateJogFromKeys();
|
||||
},
|
||||
"keydown",
|
||||
);
|
||||
|
||||
Mousetrap.bind(
|
||||
["up", "down", "left", "right"],
|
||||
(event) => {
|
||||
delete this.arrowKeysDown[event.keyCode]; //Remove key from array
|
||||
move_keys,
|
||||
(event, key) => {
|
||||
event.preventDefault();
|
||||
this.keysDown.delete(key);
|
||||
this.updateJogFromKeys();
|
||||
},
|
||||
"keyup",
|
||||
);
|
||||
|
||||
this.keyboardManual.push({
|
||||
shortcut: "←↑→↓",
|
||||
description: "Move the microscope stage",
|
||||
});
|
||||
|
||||
// Focus keys
|
||||
Mousetrap.bind("pageup", () => {
|
||||
eventBus.emit("globalMoveStepEvent", { x: 0, y: 0, z: 1 });
|
||||
});
|
||||
Mousetrap.bind("pagedown", () => {
|
||||
eventBus.emit("globalMoveStepEvent", { x: 0, y: 0, z: -1 });
|
||||
});
|
||||
this.keyboardManual.push({
|
||||
shortcut: "pgup / pgdn",
|
||||
description: "Move the microscope focus",
|
||||
|
|
@ -240,47 +243,85 @@ export default {
|
|||
eventBus.emit("globalTogglePreview", false);
|
||||
},
|
||||
|
||||
// Handle global mouse wheel events to be associated with navigation
|
||||
/**
|
||||
* Handle global mouse wheel events to be associated with navigation
|
||||
*/
|
||||
wheelMonitor: function (event) {
|
||||
// Only capture scroll if the event target's parent contains the "scrollTarget" class
|
||||
if (
|
||||
event.target.parentNode.classList.contains("scrollTarget") ||
|
||||
event.target.classList.contains("scrollTarget")
|
||||
) {
|
||||
const z_steps = event.deltaY / 100;
|
||||
const z_rel = event.deltaY / 100;
|
||||
// Emit a signal to move, acted on by panelControl.vue
|
||||
eventBus.emit("globalMoveStepEvent", {
|
||||
x_steps: 0,
|
||||
y_steps: 0,
|
||||
z_steps: z_steps,
|
||||
absolute: false,
|
||||
});
|
||||
const navigationStepSize = this.$store.state.navigationStepSize;
|
||||
const z = z_rel * navigationStepSize.z;
|
||||
// Don't use `jog() due to variable size of jogs here and the rate limiting in
|
||||
// `jog()`. No need to invert on z, as navigationInvert.z isn't exposed.
|
||||
this.invokeAction("stage", "jog", { x: 0, y: 0, z: z });
|
||||
eventBus.emit("globalUpdatePositionEvent");
|
||||
}
|
||||
},
|
||||
|
||||
navigateKeyHandler: function () {
|
||||
// Calculate movement array
|
||||
var x_rel = 0;
|
||||
var y_rel = 0;
|
||||
// 37 corresponds to the left key
|
||||
if (37 in this.arrowKeysDown) {
|
||||
x_rel = x_rel - 1;
|
||||
/**
|
||||
* Jog for key-presses.
|
||||
*
|
||||
* This is a similar to the function in stageControlButtons.vue however it uses
|
||||
* uses the key repeat to fire in case a key up is missed. It debounces any
|
||||
* request to jog that is too recent after the last jog.
|
||||
*/
|
||||
jog(x, y, z) {
|
||||
// Manually debounce extra requests from keyboard repeat rate.
|
||||
// This is used rather than an interval in case of missing a repeat.
|
||||
const now = Date.now();
|
||||
const navigationInvert = this.$store.state.navigationInvert;
|
||||
if (now - this.lastJogTime < this.jogTime) {
|
||||
return;
|
||||
}
|
||||
// 39 corresponds to the right key
|
||||
if (39 in this.arrowKeysDown) {
|
||||
x_rel = x_rel + 1;
|
||||
this.lastJogTime = now;
|
||||
|
||||
this.invokeAction("stage", "jog", {
|
||||
x: x * this.jogDistance * (navigationInvert.x ? -1 : 1),
|
||||
y: y * this.jogDistance * (navigationInvert.y ? -1 : 1),
|
||||
z: z * this.jogDistance,
|
||||
});
|
||||
eventBus.emit("globalUpdatePositionEvent");
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop jogging on key-up
|
||||
*
|
||||
* This is also similar to the function in stageControlButtons.vue. It handles
|
||||
* stopping jogging and resetting the `lastJogTime` so there is no delay when
|
||||
* starting a new jog after an old jog finished.
|
||||
*/
|
||||
jogStop() {
|
||||
this.invokeAction("stage", "jog", { stop: true });
|
||||
this.lastJogTime = 0;
|
||||
setTimeout(() => {
|
||||
eventBus.emit("globalUpdatePositionEvent");
|
||||
}, 100);
|
||||
},
|
||||
|
||||
/**
|
||||
* Track which keys are still down on keypress (or key repeat).
|
||||
*/
|
||||
updateJogFromKeys() {
|
||||
let x = 0,
|
||||
y = 0,
|
||||
z = 0;
|
||||
if (this.keysDown.has("left")) x -= 1;
|
||||
if (this.keysDown.has("right")) x += 1;
|
||||
if (this.keysDown.has("up")) y += 1;
|
||||
if (this.keysDown.has("down")) y -= 1;
|
||||
if (this.keysDown.has("pageup")) z += 1;
|
||||
if (this.keysDown.has("pagedown")) z -= 1;
|
||||
|
||||
if (x || y || z) {
|
||||
this.jog(x, y, z);
|
||||
} else {
|
||||
this.jogStop();
|
||||
}
|
||||
// 38 corresponds to the up key
|
||||
if (38 in this.arrowKeysDown) {
|
||||
y_rel = y_rel + 1;
|
||||
}
|
||||
// 40 corresponds to the down key
|
||||
if (40 in this.arrowKeysDown) {
|
||||
y_rel = y_rel - 1;
|
||||
}
|
||||
// Make a position request
|
||||
// Emit a signal to move, acted on by panelControl.vue
|
||||
eventBus.emit("globalMoveStepEvent", { x: x_rel, y: y_rel, z: 0 });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -3,58 +3,21 @@
|
|||
<p>Use the buttons below to bring the sample into focus.</p>
|
||||
<p>You may also adjust the z position with <b>page up</b> and <b>page down</b>.</p>
|
||||
<template #below-stream>
|
||||
<div class="action-button-container">
|
||||
<action-button
|
||||
class="moveZ"
|
||||
thing="stage"
|
||||
action="move_relative"
|
||||
:button-primary="false"
|
||||
:submit-data="{ x: 0, y: 0, z: -100 }"
|
||||
:submit-label="' - '"
|
||||
:can-terminate="false"
|
||||
/>
|
||||
<action-button
|
||||
class="moveZ"
|
||||
thing="stage"
|
||||
action="move_relative"
|
||||
:button-primary="false"
|
||||
:submit-data="{ x: 0, y: 0, z: 100 }"
|
||||
:submit-label="'+'"
|
||||
:can-terminate="false"
|
||||
/>
|
||||
</div>
|
||||
<stageControlButtons :show-dpad="false" />
|
||||
</template>
|
||||
</stepTemplateWithStream>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import stepTemplateWithStream from "../stepTemplateWithStream.vue";
|
||||
import ActionButton from "../../../labThingsComponents/actionButton.vue";
|
||||
|
||||
import stageControlButtons from "@/components/tabContentComponents/controlComponents/stageControlButtons.vue";
|
||||
export default {
|
||||
name: "CameraMainCalibrationStep",
|
||||
|
||||
components: {
|
||||
stepTemplateWithStream,
|
||||
ActionButton,
|
||||
stageControlButtons,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped>
|
||||
.action-button-container {
|
||||
display: flex;
|
||||
flex-direction: row; /* Stack vertically */
|
||||
justify-content: center; /* Left align */
|
||||
gap: 8px; /* Small space between buttons */
|
||||
margin-top: 4px; /* Gap from image */
|
||||
}
|
||||
.moveZ :deep(.uk-button.uk-width-1-1) {
|
||||
line-height: 50px;
|
||||
font-size: 50px !important;
|
||||
height: 60px;
|
||||
padding-bottom: 46px;
|
||||
margin: 0; /* Remove default margin */
|
||||
width: 120px;
|
||||
min-width: 80px;
|
||||
}
|
||||
</style>
|
||||
<style scoped></style>
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ export default {
|
|||
return {
|
||||
setPosition: null,
|
||||
moveLock: false,
|
||||
jogging: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -92,8 +93,7 @@ export default {
|
|||
eventBus.on("globalUpdatePositionEvent", this.updatePosition);
|
||||
// A global signal listener to perform a move action in pixels
|
||||
eventBus.on("globalMoveInImageCoordinatesEvent", this.onMoveImage);
|
||||
// A global signal listener to perform a move in multiples of a step size
|
||||
eventBus.on("globalMoveStepEvent", this.onMoveStep);
|
||||
|
||||
// Update the current position in text boxes
|
||||
await this.updatePosition();
|
||||
},
|
||||
|
|
@ -103,7 +103,6 @@ export default {
|
|||
eventBus.off("globalMoveEvent", this.move);
|
||||
eventBus.off("globalUpdatePositionEvent", this.updatePosition);
|
||||
eventBus.off("globalMoveInImageCoordinatesEvent", this.onMoveImage);
|
||||
eventBus.off("globalMoveStepEvent", this.onMoveStep);
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
|
@ -115,22 +114,12 @@ export default {
|
|||
this.moveInImageCoordinatesRequest(payload.x, payload.y, payload.absolute);
|
||||
},
|
||||
|
||||
onMoveStep(payload) {
|
||||
const { x: x_steps, y: y_steps, z: z_steps } = payload;
|
||||
const navigationStepSize = this.$store.state.navigationStepSize;
|
||||
const navigationInvert = this.$store.state.navigationInvert;
|
||||
const x = x_steps * navigationStepSize.x * (navigationInvert.x ? -1 : 1);
|
||||
const y = y_steps * navigationStepSize.y * (navigationInvert.y ? -1 : 1);
|
||||
const z = z_steps * navigationStepSize.z * (navigationInvert.z ? -1 : 1);
|
||||
const movePayload = { x, y, z, absolute: false };
|
||||
eventBus.emit("globalMoveEvent", movePayload);
|
||||
},
|
||||
|
||||
async move(payload) {
|
||||
const { x, y, z, absolute } = payload;
|
||||
// Move the stage, by updating the controls and starting a move task
|
||||
// This is equivalent to clicking the "move" button.
|
||||
if (this.moveLock) return; // Discard move requests if we're already moving
|
||||
if (this.jogging) return; // Discard move requests if a jog is in progress
|
||||
// NB moveLock is just boolean flag - it's not as safe as a "proper" lock.
|
||||
this.moveLock = true; // This will also be set by the task submitter, but
|
||||
// setting it here avoids multiple moves being requested simultaneously.
|
||||
|
|
|
|||
|
|
@ -1,34 +1,75 @@
|
|||
<template>
|
||||
<div class="uk-flex uk-flex-center uk-flex-middle uk-margin">
|
||||
<div class="dpad-grid">
|
||||
<button id="up-button" class="uk-button uk-button-primary dpad-btn" @click="move(0, 1, 0)">
|
||||
<div
|
||||
class="dpad-grid"
|
||||
:class="{
|
||||
'dpad-only': showDpad && !showFocusControls,
|
||||
'focus-only': !showDpad && showFocusControls,
|
||||
'both-controls': showDpad && showFocusControls,
|
||||
}"
|
||||
>
|
||||
<button
|
||||
v-if="showDpad"
|
||||
id="up-button"
|
||||
class="uk-button uk-button-primary dpad-btn"
|
||||
@pointerdown="jog($event, 0, 1, 0)"
|
||||
@pointerup="jogStop()"
|
||||
@mouseLeave="jogStop()"
|
||||
>
|
||||
<span class="material-symbols-outlined sync-icon"> arrow_upward </span>
|
||||
</button>
|
||||
|
||||
<button id="left-button" class="uk-button uk-button-primary dpad-btn" @click="move(-1, 0, 0)">
|
||||
<button
|
||||
v-if="showDpad"
|
||||
id="left-button"
|
||||
class="uk-button uk-button-primary dpad-btn"
|
||||
@pointerdown="jog($event, -1, 0, 0)"
|
||||
@pointerup="jogStop()"
|
||||
@pointercancel="jogStop()"
|
||||
>
|
||||
<span class="material-symbols-outlined sync-icon"> arrow_back </span>
|
||||
</button>
|
||||
|
||||
<button id="right-button" class="uk-button uk-button-primary dpad-btn" @click="move(1, 0, 0)">
|
||||
<button
|
||||
v-if="showDpad"
|
||||
id="right-button"
|
||||
class="uk-button uk-button-primary dpad-btn"
|
||||
@pointerdown="jog($event, 1, 0, 0)"
|
||||
@pointerup="jogStop()"
|
||||
@pointercancel="jogStop()"
|
||||
>
|
||||
<span class="material-symbols-outlined sync-icon"> arrow_forward </span>
|
||||
</button>
|
||||
|
||||
<button id="down-button" class="uk-button uk-button-primary dpad-btn" @click="move(0, -1, 0)">
|
||||
<button
|
||||
v-if="showDpad"
|
||||
id="down-button"
|
||||
class="uk-button uk-button-primary dpad-btn"
|
||||
@pointerdown="jog($event, 0, -1, 0)"
|
||||
@pointerup="jogStop()"
|
||||
@pointercancel="jogStop()"
|
||||
>
|
||||
<span class="material-symbols-outlined sync-icon"> arrow_downward </span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="showFocusControls"
|
||||
id="focus-out-button"
|
||||
class="uk-button uk-button-primary dpad-btn"
|
||||
@click="move(0, 0, -1)"
|
||||
@pointerdown="jog($event, 0, 0, -1)"
|
||||
@pointerup="jogStop()"
|
||||
@pointercancel="jogStop()"
|
||||
>
|
||||
<span class="material-symbols-outlined sync-icon"> remove </span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="showFocusControls"
|
||||
id="focus-in-button"
|
||||
class="uk-button uk-button-primary dpad-btn"
|
||||
@click="move(0, 0, 1)"
|
||||
@pointerdown="jog($event, 0, 0, 1)"
|
||||
@pointerup="jogStop()"
|
||||
@pointercancel="jogStop()"
|
||||
>
|
||||
<span class="material-symbols-outlined sync-icon"> add </span>
|
||||
</button>
|
||||
|
|
@ -37,13 +78,68 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { eventBus } from "../../../eventBus.js";
|
||||
import { eventBus } from "@/eventBus.js";
|
||||
|
||||
export default {
|
||||
name: "StageControlButtons",
|
||||
props: {
|
||||
showDpad: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
showFocusControls: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
data: () => ({
|
||||
jogIntervalId: null,
|
||||
jogDistance: 600,
|
||||
jogTime: 300,
|
||||
}),
|
||||
methods: {
|
||||
move(x, y, z) {
|
||||
eventBus.emit("globalMoveStepEvent", { x, y, z, absolute: false });
|
||||
/**
|
||||
* Jog d-pad and focus buttons.
|
||||
*
|
||||
* This is a similar to the function in App.vue, however it uses an Interval rather
|
||||
* than the one in App.vue that uses key repeats.
|
||||
*/
|
||||
jog(keyevent, x, y, z) {
|
||||
// Only respond to primary button (left mouse / primary touch)
|
||||
if (keyevent.button !== 0) return;
|
||||
|
||||
if (this.jogIntervalId) {
|
||||
clearInterval(this.jogIntervalId);
|
||||
}
|
||||
|
||||
// Designate this element to get the pointers next pointerup event wherever that
|
||||
// pointer is.
|
||||
keyevent.target.setPointerCapture(keyevent.pointerId);
|
||||
|
||||
const navigationInvert = this.$store.state.navigationInvert;
|
||||
let invokeJog = () =>
|
||||
this.invokeAction("stage", "jog", {
|
||||
x: x * this.jogDistance * (navigationInvert.x ? -1 : 1),
|
||||
y: y * this.jogDistance * (navigationInvert.y ? -1 : 1),
|
||||
z: z * this.jogDistance,
|
||||
});
|
||||
invokeJog();
|
||||
this.jogIntervalId = setInterval(invokeJog, this.jogTime);
|
||||
},
|
||||
/**
|
||||
* Stop jogging from d-pad and focus buttons.
|
||||
*
|
||||
* This is a similar to the function in App.vue, but it is designed to clear the
|
||||
* interval used with the d-pad and focus buttons.
|
||||
*/
|
||||
jogStop() {
|
||||
if (this.jogIntervalId) {
|
||||
clearInterval(this.jogIntervalId);
|
||||
}
|
||||
this.invokeAction("stage", "jog", { stop: true });
|
||||
setTimeout(() => {
|
||||
eventBus.emit("globalUpdatePositionEvent");
|
||||
}, 100);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
@ -53,12 +149,23 @@ export default {
|
|||
.dpad-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 40px);
|
||||
grid-template-rows: 40px 40px 40px 20px 40px;
|
||||
gap: 1px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.both-controls {
|
||||
grid-template-rows: 40px 40px 40px 20px 40px;
|
||||
}
|
||||
|
||||
.dpad-only {
|
||||
grid-template-rows: 40px 40px 40px;
|
||||
}
|
||||
|
||||
.focus-only {
|
||||
grid-template-rows: 40px;
|
||||
}
|
||||
|
||||
/* Place buttons within grid */
|
||||
.dpad-grid #up-button {
|
||||
grid-column: 2;
|
||||
|
|
@ -76,15 +183,25 @@ export default {
|
|||
grid-column: 2;
|
||||
grid-row: 3;
|
||||
}
|
||||
.dpad-grid #focus-out-button {
|
||||
|
||||
.both-controls #focus-out-button {
|
||||
grid-column: 1;
|
||||
grid-row: 5;
|
||||
}
|
||||
.dpad-grid #focus-in-button {
|
||||
.both-controls #focus-in-button {
|
||||
grid-column: 3;
|
||||
grid-row: 5;
|
||||
}
|
||||
|
||||
.focus-only #focus-out-button {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
.focus-only #focus-in-button {
|
||||
grid-column: 3;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.dpad-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue