149 lines
3.7 KiB
Vue
149 lines
3.7 KiB
Vue
|
<script setup>
|
||
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||
|
|
||
|
const props = defineProps({
|
||
|
windowData: Object,
|
||
|
});
|
||
|
|
||
|
const emit = defineEmits([
|
||
|
'close',
|
||
|
'minimize',
|
||
|
'bring-to-front',
|
||
|
'update-position',
|
||
|
'update-dimensions',
|
||
|
]);
|
||
|
|
||
|
const isDragging = ref(false);
|
||
|
const startX = ref(0);
|
||
|
const startY = ref(0);
|
||
|
const initialX = ref(0); // Store initial x position when drag starts
|
||
|
const initialY = ref(0); // Store initial y position when drag starts
|
||
|
const currentX = ref(0); // New ref for current X during drag
|
||
|
const currentY = ref(0); // New ref for current Y during drag
|
||
|
|
||
|
const windowRef = ref(null);
|
||
|
|
||
|
const handleMouseDown = (e) => {
|
||
|
if (e.target.classList.contains('window-header') || e.target.classList.contains('window-title')) {
|
||
|
isDragging.value = true;
|
||
|
startX.value = e.clientX;
|
||
|
startY.value = e.clientY;
|
||
|
initialX.value = props.windowData.x; // Initialize with prop value at drag start
|
||
|
initialY.value = props.windowData.y; // Initialize with prop value at drag start
|
||
|
currentX.value = props.windowData.x; // Initialize current with prop value
|
||
|
currentY.value = props.windowData.y; // Initialize current with prop value
|
||
|
emit('bring-to-front');
|
||
|
}
|
||
|
};
|
||
|
|
||
|
const handleMouseMove = (e) => {
|
||
|
if (!isDragging.value) return;
|
||
|
const dx = e.clientX - startX.value;
|
||
|
const dy = e.clientY - startY.value;
|
||
|
// Update local position during drag based on initial position
|
||
|
currentX.value = initialX.value + dx; // Use initialX, not props.windowData.x
|
||
|
currentY.value = initialY.value + dy; // Use initialY, not props.windowData.y
|
||
|
};
|
||
|
|
||
|
const handleMouseUp = () => {
|
||
|
if (isDragging.value) {
|
||
|
isDragging.value = false;
|
||
|
// Emit final position only once after drag ends
|
||
|
emit('update-position', { x: currentX.value, y: currentY.value });
|
||
|
}
|
||
|
};
|
||
|
|
||
|
onMounted(() => {
|
||
|
window.addEventListener('mousemove', handleMouseMove);
|
||
|
window.addEventListener('mouseup', handleMouseUp);
|
||
|
});
|
||
|
|
||
|
onUnmounted(() => {
|
||
|
window.removeEventListener('mousemove', handleMouseMove);
|
||
|
window.removeEventListener('mouseup', handleMouseUp);
|
||
|
});
|
||
|
|
||
|
const closeWindow = () => {
|
||
|
emit('close');
|
||
|
};
|
||
|
|
||
|
const minimizeWindow = () => {
|
||
|
emit('minimize');
|
||
|
};
|
||
|
|
||
|
const bringToFront = () => {
|
||
|
emit('bring-to-front');
|
||
|
};
|
||
|
</script>
|
||
|
|
||
|
<template>
|
||
|
<div
|
||
|
ref="windowRef"
|
||
|
class="window"
|
||
|
:style="{
|
||
|
// Use currentX and currentY for positioning during drag
|
||
|
left: `${isDragging ? currentX : windowData.x}px`,
|
||
|
top: `${isDragging ? currentY : windowData.y}px`,
|
||
|
width: `${windowData.width}px`,
|
||
|
height: `${windowData.height}px`,
|
||
|
zIndex: windowData.zIndex,
|
||
|
}"
|
||
|
@mousedown="bringToFront"
|
||
|
>
|
||
|
<div class="window-header" @mousedown="handleMouseDown">
|
||
|
<span class="window-title">{{ windowData.title }}</span>
|
||
|
<div class="window-controls">
|
||
|
<button @click="minimizeWindow">_</button>
|
||
|
<button @click="closeWindow">X</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="window-content">
|
||
|
<slot></slot>
|
||
|
</div>
|
||
|
</div>
|
||
|
</template>
|
||
|
|
||
|
<style scoped>
|
||
|
.window {
|
||
|
position: absolute;
|
||
|
border: 2px solid #000;
|
||
|
box-shadow: 3px 3px 0px rgba(0, 0, 0, 0.5);
|
||
|
background-color: #C0C0C0;
|
||
|
display: flex;
|
||
|
flex-direction: column;
|
||
|
resize: both;
|
||
|
overflow: auto;
|
||
|
}
|
||
|
|
||
|
.window-header {
|
||
|
background-color: #000080;
|
||
|
color: white;
|
||
|
padding: 4px;
|
||
|
cursor: grab;
|
||
|
display: flex;
|
||
|
justify-content: space-between;
|
||
|
align-items: center;
|
||
|
font-family: 'MS Sans Serif', 'Arial', sans-serif;
|
||
|
font-size: 14px;
|
||
|
}
|
||
|
|
||
|
.window-title {
|
||
|
font-weight: bold;
|
||
|
}
|
||
|
|
||
|
.window-controls button {
|
||
|
background-color: #C0C0C0;
|
||
|
border: 1px solid #000;
|
||
|
padding: 1px 6px;
|
||
|
margin-left: 4px;
|
||
|
cursor: pointer;
|
||
|
font-family: 'MS Sans Serif', 'Arial', sans-serif;
|
||
|
font-size: 12px;
|
||
|
}
|
||
|
|
||
|
.window-content {
|
||
|
flex-grow: 1;
|
||
|
background-color: #C0C0C0;
|
||
|
border-top: 2px solid #000;
|
||
|
}
|
||
|
</style>
|