| 
3 | 3 |     v-model="show_menu"  | 
4 | 4 |     content-class="circular-menu"  | 
5 | 5 |     :style="getMenuStyle()"  | 
 | 6 | +    :close-on-content-click="false"  | 
 | 7 | +    :close-delay="100"  | 
6 | 8 |   >  | 
7 |  | -    <div  | 
8 |  | -      class="circular-menu-items"  | 
9 |  | -      :style="{ width: `${radius * 2}px`, height: `${radius * 2}px` }"  | 
10 |  | -    >  | 
11 |  | -      <component  | 
12 |  | -        v-for="(item, index) in menu_items"  | 
13 |  | -        :is="item"  | 
14 |  | -        :key="index"  | 
15 |  | -        :itemProps="{  | 
16 |  | -          id: props.id,  | 
17 |  | -          tooltip_location: getTooltipLocation(index),  | 
18 |  | -          tooltip_origin: getTooltipOrigin(index),  | 
19 |  | -        }"  | 
20 |  | -        class="menu-item-wrapper"  | 
21 |  | -        :style="getItemStyle(index)"  | 
22 |  | -      />  | 
 | 9 | +    <div class="circular-menu-drag-handle" @mousedown.stop="startDrag">  | 
 | 10 | +      <div  | 
 | 11 | +        class="circular-menu-items"  | 
 | 12 | +        :style="{ width: `${radius * 2}px`, height: `${radius * 2}px` }"  | 
 | 13 | +      >  | 
 | 14 | +        <component  | 
 | 15 | +          v-for="(item, index) in menu_items"  | 
 | 16 | +          :is="item"  | 
 | 17 | +          :key="index"  | 
 | 18 | +          :itemProps="{  | 
 | 19 | +            id: props.id,  | 
 | 20 | +            tooltip_location: getTooltipLocation(index),  | 
 | 21 | +            tooltip_origin: getTooltipOrigin(index),  | 
 | 22 | +          }"  | 
 | 23 | +          class="menu-item-wrapper"  | 
 | 24 | +          :style="getItemStyle(index)"  | 
 | 25 | +          @mousedown.stop  | 
 | 26 | +        />  | 
 | 27 | +      </div>  | 
23 | 28 |     </div>  | 
24 | 29 |   </v-menu>  | 
25 | 30 | </template>  | 
 | 
40 | 45 |     const itemId = props.id || menuStore.current_id  | 
41 | 46 |     return itemId ? dataBaseStore.itemMetaDatas(itemId) : {}  | 
42 | 47 |   })  | 
 | 48 | +
  | 
43 | 49 |   const radius = 80  | 
44 | 50 |   const show_menu = ref(true)  | 
 | 51 | +  const isDragging = ref(false)  | 
 | 52 | +  const dragStartX = ref(0)  | 
 | 53 | +  const dragStartY = ref(0)  | 
 | 54 | +  const menuX = ref(props.x || menuStore.menuX)  | 
 | 55 | +  const menuY = ref(props.y || menuStore.menuY)  | 
 | 56 | +
  | 
 | 57 | +  const { pause: pauseDragListeners, resume: resumeDragListeners } =  | 
 | 58 | +    useEventListener(  | 
 | 59 | +      "mousemove",  | 
 | 60 | +      (e) => {  | 
 | 61 | +        if (!isDragging.value) return  | 
 | 62 | +        handleDrag(e)  | 
 | 63 | +      },  | 
 | 64 | +      { passive: true },  | 
 | 65 | +    )  | 
 | 66 | +
  | 
 | 67 | +  const { pause: pauseStopListeners, resume: resumeStopListeners } =  | 
 | 68 | +    useEventListener("mouseup", (e) => {  | 
 | 69 | +      if (!isDragging.value) return  | 
 | 70 | +      stopDrag(e)  | 
 | 71 | +    })  | 
45 | 72 | 
  | 
46 |  | -  watch(show_menu, (value) => {  | 
47 |  | -    if (!value) {  | 
48 |  | -      menuStore.closeMenu()  | 
 | 73 | +  useEventListener(  | 
 | 74 | +    "touchstart",  | 
 | 75 | +    (e) => {  | 
 | 76 | +      startDrag(e.touches[0])  | 
 | 77 | +      e.preventDefault()  | 
 | 78 | +    },  | 
 | 79 | +    { passive: false },  | 
 | 80 | +  )  | 
 | 81 | +
  | 
 | 82 | +  useEventListener(  | 
 | 83 | +    "touchmove",  | 
 | 84 | +    (e) => {  | 
 | 85 | +      if (!isDragging.value) return  | 
 | 86 | +      handleDrag(e.touches[0])  | 
 | 87 | +      e.preventDefault()  | 
 | 88 | +    },  | 
 | 89 | +    { passive: false },  | 
 | 90 | +  )  | 
 | 91 | +
  | 
 | 92 | +  useEventListener("touchend", (e) => {  | 
 | 93 | +    if (!isDragging.value) return  | 
 | 94 | +    stopDrag(e.changedTouches[0])  | 
 | 95 | +  })  | 
 | 96 | +
  | 
 | 97 | +  watch(show_menu, (newVal) => {  | 
 | 98 | +    if (!newVal && isDragging.value) {  | 
 | 99 | +      setTimeout(() => {  | 
 | 100 | +        show_menu.value = true  | 
 | 101 | +      }, 10)  | 
49 | 102 |     }  | 
50 | 103 |   })  | 
51 | 104 | 
  | 
 | 
58 | 111 | 
  | 
59 | 112 |   const menuItemCount = computed(() => menu_items.value.length)  | 
60 | 113 | 
  | 
61 |  | -  function getMenuStyle() {  | 
62 |  | -    const x = props.x || menuStore.menuX  | 
63 |  | -    const y = props.y || menuStore.menuY  | 
64 |  | -    const width = props.containerWidth || menuStore.containerWidth  | 
65 |  | -    const height = props.containerHeight || menuStore.containerHeight  | 
 | 114 | +  function startDrag(e) {  | 
 | 115 | +    isDragging.value = true  | 
 | 116 | +    dragStartX.value = e.clientX - menuX.value  | 
 | 117 | +    dragStartY.value = e.clientY - menuY.value  | 
 | 118 | +    resumeDragListeners()  | 
 | 119 | +    resumeStopListeners()  | 
 | 120 | +    e.preventDefault()  | 
 | 121 | +  }  | 
66 | 122 | 
  | 
67 |  | -    const adjustedX = Math.min(Math.max(x, radius), width - radius)  | 
68 |  | -    const adjustedY = Math.min(Math.max(y, radius), height - radius)  | 
 | 123 | +  function handleDrag(e) {  | 
 | 124 | +    if (!isDragging.value) return  | 
 | 125 | +    menuX.value = e.clientX - dragStartX.value  | 
 | 126 | +    menuY.value = e.clientY - dragStartY.value  | 
69 | 127 | 
  | 
 | 128 | +    menuX.value = Math.min(  | 
 | 129 | +      Math.max(menuX.value, radius),  | 
 | 130 | +      props.containerWidth - radius,  | 
 | 131 | +    )  | 
 | 132 | +    menuY.value = Math.min(  | 
 | 133 | +      Math.max(menuY.value, radius),  | 
 | 134 | +      props.containerHeight - radius,  | 
 | 135 | +    )  | 
 | 136 | +  }  | 
 | 137 | +
  | 
 | 138 | +  function stopDrag(e) {  | 
 | 139 | +    isDragging.value = false  | 
 | 140 | +    pauseDragListeners()  | 
 | 141 | +    pauseStopListeners()  | 
 | 142 | +    e.stopPropagation()  | 
 | 143 | +    menuStore.setMenuPosition(menuX.value, menuY.value)  | 
 | 144 | +  }  | 
 | 145 | +
  | 
 | 146 | +  function getMenuStyle() {  | 
70 | 147 |     return {  | 
71 |  | -      left: `${adjustedX - radius}px`,  | 
72 |  | -      top: `${adjustedY - radius}px`,  | 
 | 148 | +      left: `${menuX.value - radius}px`,  | 
 | 149 | +      top: `${menuY.value - radius}px`,  | 
73 | 150 |     }  | 
74 | 151 |   }  | 
75 | 152 | 
  | 
 | 
92 | 169 |   function getItemStyle(index) {  | 
93 | 170 |     const angle = (index / menuItemCount.value) * 2 * Math.PI  | 
94 | 171 |     return {  | 
95 |  | -      transform: `translate(${Math.cos(angle) * radius}px, ${  | 
96 |  | -        Math.sin(angle) * radius  | 
97 |  | -      }px)`,  | 
 | 172 | +      transform: `translate(${Math.cos(angle) * radius}px, ${Math.sin(angle) * radius}px)`,  | 
98 | 173 |       transition: "opacity 0.1s ease, transform 0.1s ease",  | 
99 | 174 |       position: "absolute",  | 
100 | 175 |     }  | 
 | 
106 | 181 |     position: absolute;  | 
107 | 182 |     border-radius: 50%;  | 
108 | 183 |     background-color: rgba(0, 0, 0, 0.8);  | 
 | 184 | +    user-select: none;  | 
 | 185 | +    cursor: grab;  | 
 | 186 | +    z-index: 1000;  | 
 | 187 | +  }  | 
 | 188 | +
  | 
 | 189 | +  .circular-menu-drag-handle {  | 
 | 190 | +    width: 100%;  | 
 | 191 | +    height: 100%;  | 
 | 192 | +    border-radius: 50%;  | 
 | 193 | +    cursor: grab;  | 
 | 194 | +  }  | 
 | 195 | +
  | 
 | 196 | +  .circular-menu-drag-handle:active {  | 
 | 197 | +    cursor: grabbing;  | 
109 | 198 |   }  | 
110 | 199 | 
  | 
111 | 200 |   .circular-menu-items {  | 
 | 
0 commit comments