Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
59a040e
feat(ContextMenu): New multiple context menu system
SpliiT May 18, 2026
f65c0df
Adaptative background
SpliiT May 18, 2026
c346207
test adapt
SpliiT May 18, 2026
443dee0
card order labels
SpliiT May 19, 2026
4866d3e
clean code
SpliiT May 19, 2026
9031ff5
Apply prepare changes
SpliiT May 19, 2026
3d5f541
oxcgo
SpliiT May 19, 2026
f962cdb
split responsabilities
SpliiT May 19, 2026
7182428
Apply prepare changes
SpliiT May 19, 2026
a8c95a9
rm useless ref
SpliiT May 19, 2026
34d041d
Merge branch 'next' of https://github.com/Geode-solutions/OpenGeodeWe…
SpliiT May 19, 2026
cc67efa
rm useless imports
SpliiT May 19, 2026
7c8bde4
Apply prepare changes
SpliiT May 19, 2026
73583a2
isOverToolbar
SpliiT May 19, 2026
a3857f1
Merge branch 'feat/SelectMenu' of https://github.com/Geode-solutions/…
SpliiT May 19, 2026
999e1ee
oxlint
SpliiT May 19, 2026
3f3aded
Apply prepare changes
SpliiT May 19, 2026
7c1bc45
color button
SpliiT May 19, 2026
c4fcff4
Merge branch 'feat/SelectMenu' of https://github.com/Geode-solutions/…
SpliiT May 19, 2026
6f8d4ef
oxc fix
SpliiT May 19, 2026
437af35
center button
SpliiT May 19, 2026
1c0e474
name troncate
SpliiT May 19, 2026
3f55026
Apply prepare changes
SpliiT May 19, 2026
a2932c1
opacity
SpliiT May 20, 2026
5b19ee5
Merge branch 'feat/SelectMenu' of https://github.com/Geode-solutions/…
SpliiT May 20, 2026
7be4dec
Apply prepare changes
SpliiT May 20, 2026
ed07f72
rename
SpliiT May 20, 2026
377c7e6
Merge branch 'feat/SelectMenu' of https://github.com/Geode-solutions/…
SpliiT May 20, 2026
52d39df
fix projects
SpliiT May 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions app/components/Viewer/ContextMenu/CenterButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<script setup>
import { computed, ref } from "vue";
import { useAdaptiveStyles } from "@ogw_front/composables/use_adaptive_styles";

const { isOverTreeview } = defineProps({
isOverTreeview: { type: Boolean, required: true },
});

const emit = defineEmits(["drag", "click"]);

const ADAPTIVE_BLUR_VAL = "15px";
const ADAPTIVE_OPACITY_VAL = 0.85;
const ADAPTIVE_BRIGHTNESS_VAL = 1.15;
const dragThreshold = 3;

const activatorBtn = ref(undefined);
const { adaptiveStyles } = useAdaptiveStyles(activatorBtn, {
maxOpacity: 0.85,
});

const computedItemStyles = computed(() => {
if (isOverTreeview) {
return {
"--adaptive-blur": ADAPTIVE_BLUR_VAL,
"--adaptive-opacity": ADAPTIVE_OPACITY_VAL,
"--adaptive-brightness": ADAPTIVE_BRIGHTNESS_VAL,
};
}
return adaptiveStyles.value;
});

let dragMoved = false;
let dragStartClientX = 0;
let dragStartClientY = 0;

function onMouseDown(event) {
dragMoved = false;
dragStartClientX = event.clientX;
dragStartClientY = event.clientY;
emit("drag", event);
}

function onMouseUp(event) {
const deltaX = event.clientX - dragStartClientX;
const deltaY = event.clientY - dragStartClientY;
if (Math.hypot(deltaX, deltaY) > dragThreshold) {
dragMoved = true;
}
}

function onCenterClick(event) {
event.stopPropagation();
if (!dragMoved) {
emit("click", event);
}
}
</script>

<template>
<v-btn
ref="activatorBtn"
icon
variant="outlined"
class="central-selector-btn elevation-6"
style="width: 52px; height: 52px; z-index: 5"
:style="computedItemStyles"
@mousedown="onMouseDown"
@mouseup="onMouseUp"
@click.stop="onCenterClick"
>
<v-icon icon="mdi-information-outline" size="28" color="primary" style="pointer-events: none" />
</v-btn>
</template>

<style scoped>
.central-selector-btn {
background: transparent !important;
border: 2px solid rgba(var(--v-theme-primary), 0.35) !important;
position: relative;
overflow: hidden;
transition: all 0.2s ease;
}

.central-selector-btn::before {
content: "";
position: absolute;
inset: 0;
background: rgba(255, 255, 255, var(--adaptive-opacity));
backdrop-filter: blur(var(--adaptive-blur)) brightness(var(--adaptive-brightness));
-webkit-backdrop-filter: blur(var(--adaptive-blur)) brightness(var(--adaptive-brightness));
z-index: 0;
pointer-events: none;
border-radius: inherit;
transition:
background-color 0.2s ease,
backdrop-filter 0.2s ease;
}

.central-selector-btn:hover {
transform: scale(1.08);
border-color: rgba(var(--v-theme-primary), 0.85) !important;
}

.central-selector-btn:hover::before {
background: rgba(255, 255, 255, calc(var(--adaptive-opacity, 0.15) + 0.15));
}
</style>
86 changes: 86 additions & 0 deletions app/components/Viewer/ContextMenu/CircularItems.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<script setup>
import { useMenuStore } from "@ogw_front/stores/menu";

const { menuItems, id, metaData, menuItemCount } = defineProps({
menuItems: { type: Array, required: true },
id: { type: String, required: true },
metaData: { type: Object, required: true },
menuItemCount: { type: Number, required: true },
});

const RADIUS = 80;
const Z_INDEX_ACTIVE_ITEM = 10;
const Z_INDEX_BASE_ITEM = 1;
const FULL_ANGLE = 360;
const ANGLE_45 = 45;
const ANGLE_135 = 135;
const ANGLE_225 = 225;
const ANGLE_315 = 315;

const menuStore = useMenuStore();

function getItemStyle(index) {
const angle = (index / menuItemCount) * 2 * Math.PI;
return {
transform: `translate(${Math.cos(angle) * RADIUS}px, ${Math.sin(angle) * RADIUS}px)`,
transition: "opacity 0.2s ease, transform 0.2s ease",
position: "absolute",
zIndex: menuStore.active_item_index === index ? Z_INDEX_ACTIVE_ITEM : Z_INDEX_BASE_ITEM,
};
}

function getTooltipLocation(index) {
const angle = (index / menuItemCount) * FULL_ANGLE;
if (angle < ANGLE_45 || angle >= ANGLE_315) {
return "right";
}
if (angle >= ANGLE_45 && angle < ANGLE_135) {
return "top";
}
if (angle >= ANGLE_135 && angle < ANGLE_225) {
return "left";
}
return "bottom";
}

function getTooltipOrigin(index) {
const angle = (index / menuItemCount) * FULL_ANGLE;
if (angle < ANGLE_45 || angle >= ANGLE_315) {
return "left";
}
if (angle >= ANGLE_45 && angle < ANGLE_135) {
return "bottom";
}
if (angle >= ANGLE_135 && angle < ANGLE_225) {
return "right";
}
return "top";
}
</script>

<template>
<component
v-for="(item, index) in menuItems"
:is="item"
:key="index"
:index="index"
:itemProps="{
id: id,
meta_data: metaData,
tooltip_location: getTooltipLocation(index),
tooltip_origin: getTooltipOrigin(index),
totalItems: menuItemCount,
}"
class="menu-item-wrapper"
:style="getItemStyle(index)"
@mousedown.stop
/>
</template>

<style scoped>
.menu-item-wrapper {
position: absolute;
transform-origin: center;
will-change: transform, opacity;
}
</style>
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
<script setup>
import { computed, ref, shallowRef, watch } from "vue";
import CenterButton from "@ogw_front/components/Viewer/ContextMenu/CenterButton";
import CircularItems from "@ogw_front/components/Viewer/ContextMenu/CircularItems";
import InfoCard from "@ogw_front/components/Viewer/ContextMenu/InfoCard";
import { useEventListener } from "@vueuse/core";
import { useMenuStore } from "@ogw_front/stores/menu";

const RADIUS = 80;
const MARGIN_OFFSET = 40;
const Z_INDEX_ACTIVE_ITEM = 10;
const Z_INDEX_BASE_ITEM = 1;
const FULL_ANGLE = 360;
const ANGLE_45 = 45;
const ANGLE_135 = 135;
const ANGLE_225 = 225;
const ANGLE_315 = 315;
const CLOSE_DELAY = 100;

const menuStore = useMenuStore();
import { useTreeviewStore } from "@ogw_front/stores/treeview";

const { id, x, y, containerWidth, containerHeight } = defineProps({
id: { type: String, required: true },
Expand All @@ -23,9 +15,20 @@ const { id, x, y, containerWidth, containerHeight } = defineProps({
containerHeight: { type: Number, required: true },
});

const RADIUS = 80;
const MARGIN_OFFSET = 40;
const CLOSE_DELAY = 100;
const TREEVIEW_MARGIN_LEFT = 10;
const TREEVIEW_ICON_WIDTH = 48;
const TREEVIEW_MARGIN_RIGHT = 20;

const menuStore = useMenuStore();
const treeviewStore = useTreeviewStore();

const meta_data = computed(() => menuStore.current_meta_data || {});

const show_menu = ref(true);
const showName = ref(false);
const isDragging = ref(false);
const dragStartX = ref(0);
const dragStartY = ref(0);
Expand Down Expand Up @@ -73,11 +76,24 @@ watch(

const menuItemCount = computed(() => menu_items.value.length);

const isOverTreeview = computed(() => {
const hasAdditional = treeviewStore.opened_views.some((view) => view.id !== "main");
const hasMain = treeviewStore.opened_views.some((view) => view.id === "main");
const firstColWidth = hasMain ? treeviewStore.panelWidth : 0;
const secondColWidth = hasAdditional ? treeviewStore.additionalPanelWidth : 0;
const treeviewWidth =
TREEVIEW_MARGIN_LEFT +
TREEVIEW_ICON_WIDTH +
firstColWidth +
secondColWidth +
TREEVIEW_MARGIN_RIGHT;
return menuX.value < treeviewWidth;
});

function startDrag(event) {
isDragging.value = true;
dragStartX.value = event.clientX - menuX.value;
dragStartY.value = event.clientY - menuY.value;
event.preventDefault();
}

function clampPosition(posX, posY) {
Expand Down Expand Up @@ -112,42 +128,8 @@ function getMenuStyle() {
};
}

function getTooltipLocation(index) {
const angle = (index / menuItemCount.value) * FULL_ANGLE;
if (angle < ANGLE_45 || angle >= ANGLE_315) {
return "right";
}
if (angle >= ANGLE_45 && angle < ANGLE_135) {
return "top";
}
if (angle >= ANGLE_135 && angle < ANGLE_225) {
return "left";
}
return "bottom";
}

function getTooltipOrigin(index) {
const angle = (index / menuItemCount.value) * FULL_ANGLE;
if (angle < ANGLE_45 || angle >= ANGLE_315) {
return "left";
}
if (angle >= ANGLE_45 && angle < ANGLE_135) {
return "bottom";
}
if (angle >= ANGLE_135 && angle < ANGLE_225) {
return "right";
}
return "top";
}

function getItemStyle(index) {
const angle = (index / menuItemCount.value) * 2 * Math.PI;
return {
transform: `translate(${Math.cos(angle) * RADIUS}px, ${Math.sin(angle) * RADIUS}px)`,
transition: "opacity 0.2s ease, transform 0.2s ease",
position: "absolute",
zIndex: menuStore.active_item_index === index ? Z_INDEX_ACTIVE_ITEM : Z_INDEX_BASE_ITEM,
};
function toggleShowName() {
showName.value = !showName.value;
}
</script>

Expand All @@ -165,22 +147,20 @@ function getItemStyle(index) {
class="circular-menu-items"
:style="{ width: `${RADIUS * 2}px`, height: `${RADIUS * 2}px` }"
>
<component
v-for="(item, index) in menu_items"
:is="item"
:key="index"
:index="index"
:itemProps="{
id: id,
meta_data,
tooltip_location: getTooltipLocation(index),
tooltip_origin: getTooltipOrigin(index),
totalItems: menuItemCount,
}"
class="menu-item-wrapper"
:style="getItemStyle(index)"
@mousedown.stop
<CircularItems
:menu-items="menu_items"
:id="id"
:meta-data="meta_data"
:menu-item-count="menuItemCount"
/>

<CenterButton
:is-over-treeview="isOverTreeview"
@drag="startDrag"
@click="toggleShowName"
/>

<InfoCard v-model:show="showName" :meta-data="meta_data" />
</div>
</div>
</v-menu>
Expand All @@ -200,6 +180,8 @@ function getItemStyle(index) {
height: 100%;
border-radius: 50%;
cursor: grab;
user-select: none;
-webkit-user-select: none;
}

.circular-menu-drag-handle:active {
Expand All @@ -215,10 +197,4 @@ function getItemStyle(index) {
background-color: transparent;
border: none;
}

.menu-item-wrapper {
position: absolute;
transform-origin: center;
will-change: transform, opacity;
}
</style>
Loading
Loading