Skip to content

Commit bfee2f4

Browse files
frontend: widgets: sonar360: Add heads-down option
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
1 parent d5d3c2b commit bfee2f4

4 files changed

Lines changed: 52 additions & 5 deletions

File tree

ping-viewer-next-frontend/src/components/widgets/sonar360/Ping360.vue

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue';
3131
import { getColorFromPalette } from '../SonarColorOptions.vue';
3232
import Sonar360Mask from './Sonar360Mask.vue';
3333
import Sonar360Shader from './Sonar360Shader.vue';
34+
import { useHeadDown } from './useHeadDown';
35+
36+
// When headDown is on we mirror the whole sonar container horizontally.
37+
// `Sonar360Mask` applies a second `scaleX(-1)` to its depth markers so their
38+
// text stays readable. Both flips must stay in sync.
39+
const headDown = useHeadDown();
3440
3541
const props = defineProps({
3642
measurement: {
@@ -157,12 +163,16 @@ const sectorBoundingBox = computed(() => {
157163
};
158164
});
159165
166+
const buildTransform = (...parts) => parts.filter(Boolean).join(' ');
167+
160168
const containerStyle = computed(() => {
169+
const flip = headDown.value ? 'scaleX(-1)' : '';
170+
161171
if (isHalfCircleView.value) {
162172
return {
163173
width: `${size.value}px`,
164174
height: `${size.value}px`,
165-
transform: 'translate(-50%, 48%)',
175+
transform: buildTransform('translate(-50%, 48%)', flip),
166176
position: 'fixed',
167177
left: '50%',
168178
bottom: '0',
@@ -180,8 +190,10 @@ const containerStyle = computed(() => {
180190
height: `${size.value}px`,
181191
};
182192
183-
if (offsetX !== 0 || offsetY !== 0) {
184-
style.transform = `translate(${offsetX}px, ${offsetY}px)`;
193+
const translate = offsetX !== 0 || offsetY !== 0 ? `translate(${offsetX}px, ${offsetY}px)` : '';
194+
const transform = buildTransform(translate, flip);
195+
if (transform) {
196+
style.transform = transform;
185197
}
186198
187199
return style;

ping-viewer-next-frontend/src/components/widgets/sonar360/Ping360Settings.vue

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,20 @@
7474
</div>
7575
</div>
7676

77+
<v-tooltip text="Used when the sonar is mounted upside down" location="left">
78+
<template v-slot:activator="{ props }">
79+
<div v-bind="props" style="display: inline-block">
80+
<v-checkbox
81+
v-model="sharedHeadDown"
82+
label="Head-down"
83+
hide-details
84+
density="compact"
85+
class="mt-2 mb-2"
86+
/>
87+
</div>
88+
</template>
89+
</v-tooltip>
90+
7791
<v-divider class="mb-4"></v-divider>
7892

7993
<v-btn block variant="tonal" @click="showAdvanced = !showAdvanced" class="mb-4">
@@ -196,6 +210,7 @@
196210
import { useDebounceFn } from '@vueuse/core';
197211
import { computed, ref, watch } from 'vue';
198212
import { useUnits } from '../../../composables/useUnits';
213+
import { useHeadDown } from './useHeadDown';
199214
200215
const { distanceLabel, speedUnit } = useUnits();
201216
@@ -242,6 +257,7 @@ const showAdvanced = ref(false);
242257
const autoMode = ref(true);
243258
const range = ref(10);
244259
const centerAngle = ref(180);
260+
const sharedHeadDown = useHeadDown();
245261
const width = ref(180);
246262
const angleRange = ref([0, 360]);
247263

ping-viewer-next-frontend/src/components/widgets/sonar360/Sonar360Mask.vue

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,29 +37,38 @@
3737
:y2="50 + maxRadius * Math.sin(adjustedAngleRad)"
3838
:stroke="`url(#${sweepGradientId})`" stroke-width="2"
3939
vector-effect="non-scaling-stroke" />
40+
4041
</svg>
4142

42-
<template v-if="showMarkers">
43+
<!--
44+
Intentional second scaleX(-1): the outer container in Ping360.vue is already
45+
flipped when headDown is true. Re-flipping the markers here keeps their text
46+
readable while the sonar itself remains mirrored.
47+
-->
48+
<div v-if="showMarkers" class="absolute inset-0" :style="headDown ? { transform: 'scaleX(-1)' } : {}">
4349
<div v-for="line in radiusLines" :key="line.distance"
4450
class="absolute text-4xl font-medium text-white depth-label transform -translate-x-1/2 -translate-y-1/2" :style="{
4551
left: `calc(${getMarkerPositionPercent(line.radius).x}% + 50px)`,
4652
top: `${getMarkerPositionPercent(line.radius).y}%`,
4753
}">
4854
{{ depthValue(line.distance).toFixed(1) }}{{ depthUnit }}
4955
</div>
50-
</template>
56+
</div>
5157
</div>
5258
</template>
5359

5460
<script setup lang="ts">
5561
import { computed, ref } from 'vue';
5662
import { useUnits } from '../../../composables/useUnits';
63+
import { useHeadDown } from './useHeadDown';
5764
5865
const instanceId = ref(Math.random().toString(36).slice(2, 8));
5966
const sweepGradientId = computed(() => `sweep-grad-${instanceId.value}`);
6067
6168
const { depthValue, depthUnit } = useUnits();
6269
70+
const headDown = useHeadDown();
71+
6372
const props = defineProps<{
6473
angle: number;
6574
lineColor: string;
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { useLocalStorage } from '@vueuse/core';
2+
3+
// Shared across all Ping360 widgets; persisted so the user's view preference
4+
// survives reloads. Currently a single global flag; if per-device scoping
5+
// becomes necessary, key this by deviceId via a Pinia store instead.
6+
const headDown = useLocalStorage('sonar360.headDown', false);
7+
8+
export function useHeadDown() {
9+
return headDown;
10+
}

0 commit comments

Comments
 (0)