Merge branch 'jogging' into 'v3'

Generalised Jogging

Closes #680

See merge request openflexure/openflexure-microscope-server!476
This commit is contained in:
Joe Knapper 2026-02-18 16:20:42 +00:00
commit 3f7de554d9
10 changed files with 906 additions and 206 deletions

View file

@ -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.

View file

@ -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;