Skip to content

Commit a39915b

Browse files
Add option for multi-camera controls on the top panel to the left of visibility (#1568)
* Add multi-cam top buttons option * Check in vue for multi-cam toolbar * Replace dropdown toggle with fast option to draw on opposite camera * Add configuration option to settings * Add quick keypress option * Switch to fixed 4 buttons * refactor editor to defineComponent, add AnnotationVisibility component, add expand/collaspe icons and localStorage Setting --------- Co-authored-by: Bryon Lewis <Bryon.Lewis@kitware.com>
1 parent 71e6e20 commit a39915b

File tree

7 files changed

+1088
-180
lines changed

7 files changed

+1088
-180
lines changed
Lines changed: 326 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
<script lang="ts">
2+
import {
3+
computed,
4+
defineComponent,
5+
PropType,
6+
ref,
7+
watch,
8+
} from 'vue';
9+
10+
import { VisibleAnnotationTypes } from 'vue-media-annotator/layers';
11+
12+
interface ButtonData {
13+
id: string;
14+
icon: string;
15+
type?: VisibleAnnotationTypes;
16+
active: boolean;
17+
description: string;
18+
click: () => void;
19+
}
20+
21+
export default defineComponent({
22+
name: 'AnnotationVisibilityMenu',
23+
props: {
24+
visibleModes: {
25+
type: Array as PropType<(VisibleAnnotationTypes)[]>,
26+
required: true,
27+
},
28+
tailSettings: {
29+
type: Object as PropType<{ before: number; after: number }>,
30+
required: true,
31+
},
32+
},
33+
emits: ['set-annotation-state', 'update:tail-settings'],
34+
setup(props, { emit }) {
35+
const STORAGE_KEY = 'annotationVisibilityMenu.expanded';
36+
37+
// Load from localStorage or default to true
38+
const loadExpandedState = (): boolean => {
39+
const stored = localStorage.getItem(STORAGE_KEY);
40+
return stored !== null ? stored === 'true' : true;
41+
};
42+
43+
const isExpanded = ref(loadExpandedState());
44+
45+
// Save to localStorage when state changes
46+
watch(isExpanded, (value) => {
47+
localStorage.setItem(STORAGE_KEY, String(value));
48+
});
49+
50+
const isVisible = (mode: VisibleAnnotationTypes) => props.visibleModes.includes(mode);
51+
52+
const toggleVisible = (mode: VisibleAnnotationTypes) => {
53+
if (isVisible(mode)) {
54+
emit('set-annotation-state', {
55+
visible: props.visibleModes.filter((m) => m !== mode),
56+
});
57+
} else {
58+
emit('set-annotation-state', {
59+
visible: props.visibleModes.concat([mode]),
60+
});
61+
}
62+
};
63+
64+
const toggleExpanded = () => {
65+
isExpanded.value = !isExpanded.value;
66+
};
67+
68+
const viewButtons = computed((): ButtonData[] => (
69+
/* Only geometry primitives can be visible types right now */
70+
[
71+
{
72+
id: 'rectangle',
73+
type: 'rectangle',
74+
icon: 'mdi-vector-square',
75+
active: isVisible('rectangle'),
76+
description: 'Rectangle',
77+
click: () => toggleVisible('rectangle'),
78+
},
79+
{
80+
id: 'Polygon',
81+
type: 'Polygon',
82+
icon: 'mdi-vector-polygon',
83+
active: isVisible('Polygon'),
84+
description: 'Polygon',
85+
click: () => toggleVisible('Polygon'),
86+
},
87+
{
88+
id: 'LineString',
89+
type: 'LineString',
90+
active: isVisible('LineString'),
91+
icon: 'mdi-vector-line',
92+
description: 'Head Tail',
93+
click: () => toggleVisible('LineString'),
94+
},
95+
{
96+
id: 'text',
97+
type: 'text',
98+
active: isVisible('text'),
99+
icon: 'mdi-format-text',
100+
description: 'Text',
101+
click: () => toggleVisible('text'),
102+
},
103+
{
104+
id: 'tooltip',
105+
type: 'tooltip',
106+
active: isVisible('tooltip'),
107+
icon: 'mdi-tooltip-text-outline',
108+
description: 'Tooltip',
109+
click: () => toggleVisible('tooltip'),
110+
},
111+
]));
112+
113+
const updateTailSettings = (type: 'before' | 'after', event: Event) => {
114+
const value = Number.parseFloat((event.target as HTMLInputElement).value);
115+
const settings = { ...props.tailSettings, [type]: value };
116+
emit('update:tail-settings', settings);
117+
};
118+
119+
return {
120+
isExpanded,
121+
viewButtons,
122+
isVisible,
123+
toggleVisible,
124+
toggleExpanded,
125+
updateTailSettings,
126+
};
127+
},
128+
});
129+
</script>
130+
131+
<template>
132+
<span class="pb-1">
133+
<!-- Dropdown mode when collapsed -->
134+
<v-menu
135+
v-if="!isExpanded"
136+
offset-y
137+
:close-on-content-click="false"
138+
min-width="300"
139+
>
140+
<template #activator="{ on, attrs }">
141+
<v-btn
142+
v-bind="attrs"
143+
class="mx-1 mode-button"
144+
small
145+
v-on="on"
146+
>
147+
<v-icon>mdi-eye</v-icon>
148+
<v-btn
149+
icon
150+
x-small
151+
class="ml-1 expand-toggle"
152+
@click.stop="toggleExpanded"
153+
>
154+
<v-icon small>mdi-chevron-right</v-icon>
155+
</v-btn>
156+
</v-btn>
157+
</template>
158+
<v-list dense>
159+
<v-list-item
160+
v-for="button in viewButtons"
161+
:key="`${button.id}-menu`"
162+
@click="button.click"
163+
>
164+
<v-list-item-icon>
165+
<v-btn
166+
:color="button.active ? 'grey darken-2' : ''"
167+
class="mx-1 mode-button"
168+
small
169+
@click="button.click"
170+
>
171+
<v-icon>{{ button.icon }}</v-icon>
172+
</v-btn>
173+
</v-list-item-icon>
174+
<v-list-item-content>
175+
<v-list-item-title>{{ button.description }}</v-list-item-title>
176+
</v-list-item-content>
177+
</v-list-item>
178+
<v-list-item>
179+
<v-list-item-icon>
180+
<v-btn
181+
:color="isVisible('TrackTail') ? 'grey darken-2' : ''"
182+
class="mx-1 mode-button"
183+
small
184+
@click="toggleVisible('TrackTail')"
185+
>
186+
<v-icon>mdi-navigation</v-icon>
187+
</v-btn>
188+
</v-list-item-icon>
189+
<v-list-item-content>
190+
<v-list-item-title>Track Trails</v-list-item-title>
191+
</v-list-item-content>
192+
</v-list-item>
193+
<v-divider />
194+
<v-list-item v-if="isVisible('TrackTail')">
195+
<v-list-item-content>
196+
<v-card
197+
class="pa-4 flex-column d-flex"
198+
outlined
199+
flat
200+
>
201+
<label for="frames-before">Frames before: {{ tailSettings.before }}</label>
202+
<input
203+
id="frames-before"
204+
type="range"
205+
name="frames-before"
206+
class="tail-slider-width"
207+
label
208+
min="0"
209+
max="100"
210+
:value="tailSettings.before"
211+
@input="updateTailSettings('before', $event)"
212+
>
213+
<div class="py-2" />
214+
<label for="frames-after">Frames after: {{ tailSettings.after }}</label>
215+
<input
216+
id="frames-after"
217+
type="range"
218+
name="frames-after"
219+
class="tail-slider-width"
220+
min="0"
221+
max="100"
222+
:value="tailSettings.after"
223+
@input="updateTailSettings('after', $event)"
224+
>
225+
</v-card>
226+
</v-list-item-content>
227+
</v-list-item>
228+
</v-list>
229+
</v-menu>
230+
231+
<!-- Full button mode when expanded -->
232+
<template v-else>
233+
<span class="mr-1 px-3 py-1">
234+
<v-icon class="pr-1">
235+
mdi-eye
236+
</v-icon>
237+
<span class="text-subtitle-2">
238+
Visibility
239+
</span>
240+
<v-btn
241+
icon
242+
x-small
243+
class="ml-1 expand-toggle"
244+
@click="toggleExpanded"
245+
>
246+
<v-icon small>mdi-chevron-left</v-icon>
247+
</v-btn>
248+
</span>
249+
<v-btn
250+
v-for="button in viewButtons"
251+
:key="`${button.id}-view`"
252+
:color="button.active ? 'grey darken-2' : ''"
253+
class="mx-1 mode-button"
254+
small
255+
@click="button.click"
256+
>
257+
<v-icon>{{ button.icon }}</v-icon>
258+
</v-btn>
259+
<v-menu
260+
open-on-hover
261+
bottom
262+
offset-y
263+
:close-on-content-click="false"
264+
>
265+
<template #activator="{ on, attrs }">
266+
<v-btn
267+
v-bind="attrs"
268+
:color="isVisible('TrackTail') ? 'grey darken-2' : ''"
269+
class="mx-1 mode-button"
270+
small
271+
v-on="on"
272+
@click="toggleVisible('TrackTail')"
273+
>
274+
<v-icon>mdi-navigation</v-icon>
275+
</v-btn>
276+
</template>
277+
<v-card
278+
class="pa-4 flex-column d-flex"
279+
outlined
280+
>
281+
<label for="frames-before-full">Frames before: {{ tailSettings.before }}</label>
282+
<input
283+
id="frames-before-full"
284+
type="range"
285+
name="frames-before-full"
286+
class="tail-slider-width"
287+
label
288+
min="0"
289+
max="100"
290+
:value="tailSettings.before"
291+
@input="updateTailSettings('before', $event)"
292+
>
293+
<div class="py-2" />
294+
<label for="frames-after-full">Frames after: {{ tailSettings.after }}</label>
295+
<input
296+
id="frames-after-full"
297+
type="range"
298+
name="frames-after-full"
299+
class="tail-slider-width"
300+
min="0"
301+
max="100"
302+
:value="tailSettings.after"
303+
@input="updateTailSettings('after', $event)"
304+
>
305+
</v-card>
306+
</v-menu>
307+
</template>
308+
</span>
309+
</template>
310+
311+
<style scoped>
312+
.mode-button {
313+
border: 1px solid grey;
314+
min-width: 36px;
315+
}
316+
.tail-slider-width {
317+
width: 240px;
318+
}
319+
.expand-toggle {
320+
opacity: 0.5;
321+
transition: opacity 0.2s;
322+
}
323+
.expand-toggle:hover {
324+
opacity: 1;
325+
}
326+
</style>

0 commit comments

Comments
 (0)