Skip to content

Commit

Permalink
Input-widgets: Dial: Improve UX on knob dragging and value insertion
Browse files Browse the repository at this point in the history
Signed-off-by: Arturo Manzoli <[email protected]>
  • Loading branch information
ArturoManzoli committed Feb 25, 2025
1 parent e84b64c commit 39943de
Showing 1 changed file with 131 additions and 23 deletions.
154 changes: 131 additions & 23 deletions src/components/custom-widget-elements/Dial.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@
@click="widgetStore.editingMode && widgetStore.showElementPropsDrawer(miniWidget.hash)"
>
<div class="flex flex-row" :class="widgetStore.editingMode ? 'pointer-events-none' : 'pointer-events-auto'">
<div class="potentiometer-container w-full" :class="sizeClass" @mousedown="startDrag">
<div
class="potentiometer-container w-full"
:class="sizeClass"
@mousedown="startDrag"
@click="isEditingValue = false"
>
<div class="potentiometer-scale elevation-5">
<div
class="potentiometer-knob"
Expand All @@ -24,12 +29,47 @@
</div>
</div>
</div>
<p
v-if="miniWidget.options.layout?.minValue >= miniWidget.options.layout?.maxValue"
class="absolute bg-[#880000] pa-2 text-xs mt-2 -ml-12 border-2 border-white rounded-md z-50"
>
Min value is larger than Max value
</p>
</div>
<div
v-if="miniWidget.options.layout?.showValue"
class="value-display bg-[#FFFFFF22] border-[#FFFFFF44] border-[1px] rounded-md p-[2px] min-w-9 text-center elevation-1"
>
{{ Math.round(potentiometerValue) || 0 }}
<div v-if="miniWidget.options.layout?.showValue" class="flex" @keydown.esc="finishEditingValue">
<div
class="value-display bg-[#FFFFFF22] border-[1px] rounded-md p-[2px] min-w-[40px] text-center elevation-1"
:class="
isEditingValue
? 'pointer-events-auto border-blue-800 bg-[#0000FF11]'
: 'cursor-pointer border-[#FFFFFF44] bg-[#FFFFFF22]'
"
>
<div v-if="!isEditingValue" @click="startEditingValue">
{{ Math.round(potentiometerValue) || 0 }}
</div>
<input
v-if="isEditingValue"
v-model="editableValue"
class="bg-transparent border-0 text-center outline-none max-w-[30px]"
@keydown.enter="finishEditingValue"
@blur="finishEditingValue"
/>
</div>
<div class="flex flex-col w-4 h-7 justify-between items-center">
<v-icon
v-if="!isEditingValue"
class="text-white text-[16px] -mt-[2px] ml-2 cursor-pointer opacity-30"
@click="addDialValue"
>mdi-plus</v-icon
>
<v-icon
v-if="!isEditingValue"
class="text-white text-[16px] -mb-1 ml-2 cursor-pointer opacity-30"
@click="subtractDialValue"
>mdi-minus</v-icon
>
</div>
</div>
</div>
</template>
Expand Down Expand Up @@ -60,6 +100,8 @@ const miniWidget = toRefs(props).miniWidget
const potentiometerValue = ref(0)
const rotationAngle = ref(-150)
let listenerId: string | undefined
const isEditingValue = ref(false)
const editableValue = ref(String(Math.round(potentiometerValue.value) || 0))
watch(
() => widgetStore.miniWidgetManagerVars(miniWidget.value.hash).configMenuOpen,
Expand All @@ -85,8 +127,11 @@ const setDialValue = (value: number | string | undefined): void => {
potentiometerValue.value = numValue
const rotationRange = 300
const valueRange = miniWidget.value.options.layout?.maxValue - miniWidget.value.options.layout?.minValue || 1
rotationAngle.value = ((numValue - miniWidget.value.options.layout?.minValue) / valueRange) * rotationRange - 150
const minVal = miniWidget.value.options.layout?.minValue || 0
const maxVal = miniWidget.value.options.layout?.maxValue || 100
const valueRange = maxVal - minVal
rotationAngle.value = ((numValue - minVal) / valueRange) * rotationRange - 150
}
const startListeningDataLakeVariable = (): void => {
Expand All @@ -109,6 +154,15 @@ watch(
{ immediate: true }
)
watch(
() => [miniWidget.value.options.layout?.minValue, miniWidget.value.options.layout?.maxValue],
() => {
potentiometerValue.value = miniWidget.value.options.layout?.minValue || 0
setDialValue(potentiometerValue.value)
},
{ immediate: true }
)
onMounted(() => {
if (!miniWidget.value.options || Object.keys(miniWidget.value.options).length === 0) {
miniWidget.value.isCustomElement = true
Expand Down Expand Up @@ -140,6 +194,9 @@ const sizeClass = computed(() => {
}
})
let lastMouseAngle = 0
let lastUnwrappedAngle = 0
const startDrag = (event: MouseEvent): void => {
event.preventDefault()
Expand All @@ -148,30 +205,39 @@ const startDrag = (event: MouseEvent): void => {
const centerX = rect.left + rect.width / 2
const centerY = rect.top + rect.height / 2
let lastMouseAngle = Math.atan2(event.clientY - centerY, event.clientX - centerX) * (180 / Math.PI)
const initialMouseAngle = (Math.atan2(event.clientY - centerY, event.clientX - centerX) * 180) / Math.PI
lastMouseAngle = initialMouseAngle
lastUnwrappedAngle = initialMouseAngle
const initialKnobAngle = rotationAngle.value
const offsetAngle = initialKnobAngle - initialMouseAngle
const handleDrag = (moveEvent: MouseEvent): void => {
const currentMouseAngle = Math.atan2(moveEvent.clientY - centerY, moveEvent.clientX - centerX) * (180 / Math.PI)
let angleDelta = currentMouseAngle - lastMouseAngle
const rawAngle = (Math.atan2(moveEvent.clientY - centerY, moveEvent.clientX - centerX) * 180) / Math.PI
if (angleDelta > 180) {
angleDelta -= 360
} else if (angleDelta < -180) {
angleDelta += 360
}
let angleDiff = rawAngle - lastMouseAngle
if (angleDiff > 180) angleDiff -= 360
else if (angleDiff < -180) angleDiff += 360
let newRotationAngle = rotationAngle.value + angleDelta
const unwrappedAngle = lastUnwrappedAngle + angleDiff
lastMouseAngle = rawAngle
lastUnwrappedAngle = unwrappedAngle
let newRotationAngle = unwrappedAngle + offsetAngle
newRotationAngle = Math.max(-150, Math.min(150, newRotationAngle))
rotationAngle.value = newRotationAngle
lastMouseAngle = currentMouseAngle
rotationAngle.value = newRotationAngle
const rotationRange = 300
const valueRange =
(miniWidget.value.options.layout?.maxValue || 100) - (miniWidget.value.options.layout?.minValue || 0)
potentiometerValue.value =
((newRotationAngle + 150) / rotationRange) * valueRange + (miniWidget.value.options.layout?.minValue || 0)
const minVal = miniWidget.value.options.layout?.minValue || 0
const maxVal = miniWidget.value.options.layout?.maxValue || 100
const valueRange = maxVal - minVal
potentiometerValue.value = ((newRotationAngle + 150) / rotationRange) * valueRange + minVal
if (miniWidget.value.options.dataLakeVariable) {
if (widgetStore.editingMode) return
Expand All @@ -190,6 +256,48 @@ const startDrag = (event: MouseEvent): void => {
document.addEventListener('mouseup', stopDrag)
}
const addDialValue = (): void => {
const maxVal = miniWidget.value.options.layout?.maxValue || 100
if (potentiometerValue.value < maxVal) {
setDialValue(potentiometerValue.value + 1)
}
}
const subtractDialValue = (): void => {
const minVal = miniWidget.value.options.layout?.minValue || 0
if (potentiometerValue.value > minVal) {
setDialValue(potentiometerValue.value - 1)
}
}
const startEditingValue = (): void => {
if (widgetStore.editingMode) return
isEditingValue.value = true
editableValue.value = String(Math.round(potentiometerValue.value) || 0)
}
const finishEditingValue = (): void => {
let newValue = parseFloat(editableValue.value)
if (isNaN(newValue)) {
newValue = Math.round(potentiometerValue.value) || 0
}
const minVal = miniWidget.value.options.layout?.minValue || 0
const maxVal = miniWidget.value.options.layout?.maxValue || 100
if (newValue > maxVal) newValue = maxVal
if (newValue < minVal) newValue = minVal
setDialValue(newValue)
if (miniWidget.value.options.dataLakeVariable && !widgetStore.editingMode) {
const roundedValue = Math.round(newValue)
widgetStore.setMiniWidgetLastValue(miniWidget.value.hash, roundedValue)
setDataLakeVariableData(miniWidget.value.options.dataLakeVariable.name, roundedValue)
}
isEditingValue.value = false
}
onUnmounted(() => {
if (miniWidget.value.options.dataLakeVariable) {
deleteDataLakeVariable(miniWidget.value.options.dataLakeVariable.id)
Expand Down

0 comments on commit 39943de

Please sign in to comment.