Add way to set spinner step without changing HTTP API

This commit is contained in:
Julian Stirling 2026-02-19 13:34:31 +00:00
parent cd14f470f6
commit e66fe5a35a
5 changed files with 50 additions and 11 deletions

View file

@ -173,13 +173,13 @@ class RectGridWorkflow(ScanWorkflow[SettingModelType], Generic[SettingModelType]
# Redefine _csm Thing Slot, as CSM is required for any RectGridWorkflow # Redefine _csm Thing Slot, as CSM is required for any RectGridWorkflow
_csm: CameraStageMapper = lt.thing_slot() _csm: CameraStageMapper = lt.thing_slot()
overlap: float = lt.setting(default=0.45, ge=0.1, le=0.7, multiple_of=0.05) overlap: float = lt.setting(default=0.45, ge=0.1, le=0.7)
"""The fraction that adjacent images should overlap in x and y. """The fraction that adjacent images should overlap in x and y.
This must be between 0.1 and 0.7. This must be between 0.1 and 0.7.
""" """
autofocus_dz: int = lt.setting(default=1000, ge=400, le=3000, multiple_of=200) autofocus_dz: int = lt.setting(default=1000, ge=400, le=3000)
"""The z distance to perform an autofocus in steps. """The z distance to perform an autofocus in steps.
Must be greater than or equal to 400, and less than or equal to 3000. Must be greater than or equal to 400, and less than or equal to 3000.
@ -290,7 +290,7 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
This uses the settings from the ``BackgroundDetectThing``. This uses the settings from the ``BackgroundDetectThing``.
""" """
max_range: int = lt.setting(default=45000, multiple_of=1000) max_range: int = lt.setting(default=45000)
"""The maximum distance in steps from the centre of the scan.""" """The maximum distance in steps from the centre of the scan."""
# Stacking settings # Stacking settings
@ -311,7 +311,7 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
Defaults to 9 which balances reliability and speed. Defaults to 9 which balances reliability and speed.
""" """
stack_dz: int = lt.setting(default=50, ge=20, le=400, multiple_of=10) stack_dz: int = lt.setting(default=50, ge=20, le=400)
"""Distance in steps between images in a z-stack. """Distance in steps between images in a z-stack.
Suggested values: Suggested values:
@ -538,7 +538,9 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
def settings_ui(self) -> list[PropertyControl]: def settings_ui(self) -> list[PropertyControl]:
"""A list of PropertyControl objects to create the settings in the scan tab.""" """A list of PropertyControl objects to create the settings in the scan tab."""
return [ return [
property_control_for(self, "overlap", label="Image Overlap (0.1-0.7)"), property_control_for(
self, "overlap", label="Image Overlap (0.1-0.7)", step=0.05
),
property_control_for( property_control_for(
self, "stack_images_to_save", label="Images in Stack to Save" self, "stack_images_to_save", label="Images in Stack to Save"
), ),
@ -547,9 +549,13 @@ class HistoScanWorkflow(RectGridWorkflow[HistoScanSettingsModel]):
"stack_min_images_to_test", "stack_min_images_to_test",
label="Minimum number of images to test for focus", label="Minimum number of images to test for focus",
), ),
property_control_for(self, "stack_dz", label="Stack dz (steps)"), property_control_for(self, "stack_dz", label="Stack dz (steps)", step=5),
property_control_for(self, "autofocus_dz", label="Autofocus Range (steps)"), property_control_for(
property_control_for(self, "max_range", label="Maximum Distance (steps)"), self, "autofocus_dz", label="Autofocus Range (steps)", step=200
),
property_control_for(
self, "max_range", label="Maximum Distance (steps)", step=1000
),
property_control_for( property_control_for(
self, "skip_background", label="Detect and Skip Empty Fields " self, "skip_background", label="Detect and Skip Empty Fields "
), ),
@ -662,7 +668,9 @@ class RegularGridWorkflow(RectGridWorkflow[RegularGridSettingsModel]):
def settings_ui(self) -> list[PropertyControl]: def settings_ui(self) -> list[PropertyControl]:
"""A list of PropertyControl objects to create the settings in the scan tab.""" """A list of PropertyControl objects to create the settings in the scan tab."""
return [ return [
property_control_for(self, "overlap", label="Image Overlap (0.1-0.7)"), property_control_for(
self, "overlap", label="Image Overlap (0.1-0.7)", step=0.05
),
property_control_for(self, "x_count", label="Number of columns"), property_control_for(self, "x_count", label="Number of columns"),
property_control_for(self, "y_count", label="Number of rows"), property_control_for(self, "y_count", label="Number of rows"),
property_control_for(self, "autofocus_dz", label="Autofocus Range (steps)"), property_control_for(self, "autofocus_dz", label="Autofocus Range (steps)"),

View file

@ -97,6 +97,16 @@ class PropertyControl(BaseModel):
when selected. when selected.
""" """
step: Optional[int | float] = None
"""The step size for a numerical input.
If the property is not numeric this will be ignored.
If this is left as None, then the UI will try to use the ``multipleOf`` field in
the property dataSchema. If ``multipleOf`` is not set then the browser default is
used.
"""
def property_control_for( def property_control_for(
thing: lt.Thing, property_name: str, **kwargs: Any thing: lt.Thing, property_name: str, **kwargs: Any

View file

@ -27,7 +27,7 @@
type="number" type="number"
:min="minimum" :min="minimum"
:max="maximum" :max="maximum"
:step="step" :step="spinner_step"
@input="grabFocus" @input="grabFocus"
@focusout="focusOut" @focusout="focusOut"
@keydown="keyDown" @keydown="keyDown"
@ -152,6 +152,11 @@ export default {
default: null, default: null,
required: false, required: false,
}, },
step: {
type: Number,
default: null,
required: false,
},
}, },
emits: ["requestUpdate", "sendValue", "animationShown"], emits: ["requestUpdate", "sendValue", "animationShown"],
@ -253,8 +258,17 @@ export default {
if (this.dataType !== "number") return undefined; if (this.dataType !== "number") return undefined;
return this.dataSchema.minimum; return this.dataSchema.minimum;
}, },
step() { /**
* The step size for numerical spinners.
*
* If not a number this is `undefined`. If `step` is set as a prop then it is used
* otherwise `multipleOf` from the property `dataSchema` is used. If `multipleOf`
* is not set in the `dataSchema` JS will return `undefined` and the browser will
* use its default value.
*/
spinner_step() {
if (this.dataType !== "number") return undefined; if (this.dataType !== "number") return undefined;
if (this.step !== null) return this.step;
return this.dataSchema.multipleOf; return this.dataSchema.multipleOf;
}, },
}, },

View file

@ -5,6 +5,7 @@
:label="label" :label="label"
:animate="animate" :animate="animate"
:options="options" :options="options"
:step="step"
@request-update="readProperty" @request-update="readProperty"
@send-value="writeProperty" @send-value="writeProperty"
@animation-shown="resetAnimate" @animation-shown="resetAnimate"
@ -50,6 +51,11 @@ export default {
default: null, default: null,
required: false, required: false,
}, },
step: {
type: Number,
default: null,
required: false,
},
}, },
data() { data() {

View file

@ -7,6 +7,7 @@
:read-back="propertyData.read_back" :read-back="propertyData.read_back"
:read-back-delay="propertyData.read_back_delay" :read-back-delay="propertyData.read_back_delay"
:options="propertyData.options" :options="propertyData.options"
:step="propertyData.step"
/> />
</template> </template>