Update assets/js/script.js
Browse files- assets/js/script.js +148 -177
assets/js/script.js
CHANGED
|
@@ -74,37 +74,37 @@ const emails = [
|
|
| 74 |
},
|
| 75 |
{
|
| 76 |
id: 11,
|
| 77 |
-
sender: "
|
| 78 |
-
subject: "
|
| 79 |
-
snippet: "
|
| 80 |
timestamp: "Aug 29"
|
| 81 |
},
|
| 82 |
{
|
| 83 |
id: 12,
|
| 84 |
sender: "University Alumni",
|
| 85 |
-
subject: "Alumni
|
| 86 |
-
snippet: "Join us for
|
| 87 |
timestamp: "Aug 28"
|
| 88 |
},
|
| 89 |
{
|
| 90 |
id: 13,
|
| 91 |
-
sender: "
|
| 92 |
-
subject: "
|
| 93 |
-
snippet: "
|
| 94 |
timestamp: "Aug 27"
|
| 95 |
},
|
| 96 |
{
|
| 97 |
id: 14,
|
| 98 |
-
sender: "
|
| 99 |
-
subject: "
|
| 100 |
-
snippet: "
|
| 101 |
timestamp: "Aug 26"
|
| 102 |
},
|
| 103 |
{
|
| 104 |
id: 15,
|
| 105 |
-
sender: "
|
| 106 |
-
subject: "Your
|
| 107 |
-
snippet: "
|
| 108 |
timestamp: "Aug 25"
|
| 109 |
}
|
| 110 |
];
|
|
@@ -199,14 +199,10 @@ class UIManager {
|
|
| 199 |
this.selectionHighlight = document.getElementById('selectionHighlight');
|
| 200 |
this.handLandmarks = document.getElementById('handLandmarks');
|
| 201 |
this.gesturePath = document.getElementById('gesturePath');
|
| 202 |
-
this.
|
| 203 |
|
| 204 |
this.selectedEmail = null;
|
| 205 |
this.emailElements = [];
|
| 206 |
-
this.scrollOffset = 0;
|
| 207 |
-
this.maxScroll = 0;
|
| 208 |
-
this.scrollSpeed = 0;
|
| 209 |
-
this.isScrolling = false;
|
| 210 |
|
| 211 |
this.renderEmails();
|
| 212 |
this.setupEventListeners();
|
|
@@ -214,8 +210,8 @@ class UIManager {
|
|
| 214 |
// Create confirmation overlay
|
| 215 |
this.createConfirmationOverlay();
|
| 216 |
|
| 217 |
-
//
|
| 218 |
-
this.
|
| 219 |
}
|
| 220 |
|
| 221 |
createConfirmationOverlay() {
|
|
@@ -256,20 +252,6 @@ class UIManager {
|
|
| 256 |
});
|
| 257 |
}
|
| 258 |
|
| 259 |
-
setupScrollIndicator() {
|
| 260 |
-
this.scrollIndicator.style.display = 'none';
|
| 261 |
-
}
|
| 262 |
-
|
| 263 |
-
showScrollIndicator(direction) {
|
| 264 |
-
this.scrollIndicator.style.display = 'block';
|
| 265 |
-
this.scrollIndicator.className = `scroll-indicator ${direction}`;
|
| 266 |
-
|
| 267 |
-
// Auto-hide after 500ms
|
| 268 |
-
setTimeout(() => {
|
| 269 |
-
this.scrollIndicator.style.display = 'none';
|
| 270 |
-
}, 500);
|
| 271 |
-
}
|
| 272 |
-
|
| 273 |
showConfirmation(message, callback) {
|
| 274 |
this.confirmationOverlay.querySelector('.confirmation-message').textContent = message;
|
| 275 |
this.confirmationCallback = callback;
|
|
@@ -281,6 +263,23 @@ class UIManager {
|
|
| 281 |
this.confirmationCallback = null;
|
| 282 |
}
|
| 283 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
renderEmails() {
|
| 285 |
this.emailList.innerHTML = '';
|
| 286 |
this.emailElements = [];
|
|
@@ -309,9 +308,6 @@ class UIManager {
|
|
| 309 |
|
| 310 |
// Update email positions
|
| 311 |
this.updateEmailPositions();
|
| 312 |
-
|
| 313 |
-
// Calculate max scroll
|
| 314 |
-
this.maxScroll = this.emailList.scrollHeight - this.emailList.clientHeight;
|
| 315 |
}
|
| 316 |
|
| 317 |
updateEmailPositions() {
|
|
@@ -355,7 +351,7 @@ class UIManager {
|
|
| 355 |
showSelectionHighlight(rect) {
|
| 356 |
this.selectionHighlight.style.display = 'block';
|
| 357 |
this.selectionHighlight.style.left = `${rect.left}px`;
|
| 358 |
-
this.selectionHighlight.style.top = `${rect.top}px
|
| 359 |
this.selectionHighlight.style.width = `${rect.width}px`;
|
| 360 |
this.selectionHighlight.style.height = `${rect.height}px`;
|
| 361 |
|
|
@@ -409,15 +405,23 @@ class UIManager {
|
|
| 409 |
this.showSelectionHighlight(element.rect);
|
| 410 |
}
|
| 411 |
}
|
| 412 |
-
|
| 413 |
-
// Recalculate max scroll
|
| 414 |
-
this.maxScroll = this.emailList.scrollHeight - this.emailList.clientHeight;
|
| 415 |
});
|
| 416 |
|
| 417 |
-
// Add scroll event
|
| 418 |
this.emailList.addEventListener('scroll', () => {
|
| 419 |
-
|
| 420 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
});
|
| 422 |
}
|
| 423 |
|
|
@@ -462,38 +466,11 @@ class UIManager {
|
|
| 462 |
}
|
| 463 |
}
|
| 464 |
|
| 465 |
-
//
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
// Update scroll offset
|
| 471 |
-
this.scrollOffset = this.emailList.scrollTop;
|
| 472 |
-
|
| 473 |
-
// Show scroll indicator
|
| 474 |
-
this.showScrollIndicator(direction);
|
| 475 |
-
|
| 476 |
-
// Update email positions
|
| 477 |
-
this.updateEmailPositions();
|
| 478 |
-
}
|
| 479 |
-
|
| 480 |
-
// Calculate the position of the hand relative to the email list
|
| 481 |
-
getRelativePosition(landmark, videoParams) {
|
| 482 |
-
const emailListRect = this.emailList.getBoundingClientRect();
|
| 483 |
-
const videoRect = document.getElementById('webcam').getBoundingClientRect();
|
| 484 |
-
|
| 485 |
-
// Convert landmark coordinates to screen coordinates
|
| 486 |
-
const screenX = landmark.x * videoRect.width;
|
| 487 |
-
const screenY = landmark.y * videoRect.height;
|
| 488 |
-
|
| 489 |
-
// Calculate relative position within email list
|
| 490 |
-
const relativeX = screenX - emailListRect.left;
|
| 491 |
-
const relativeY = screenY - emailListRect.top + this.scrollOffset;
|
| 492 |
-
|
| 493 |
-
return {
|
| 494 |
-
x: relativeX,
|
| 495 |
-
y: relativeY
|
| 496 |
-
};
|
| 497 |
}
|
| 498 |
}
|
| 499 |
|
|
@@ -507,7 +484,6 @@ class GestureDetector {
|
|
| 507 |
this.selectedEmailId = null;
|
| 508 |
this.gestureBuffer = [];
|
| 509 |
this.circlePoints = [];
|
| 510 |
-
this.scrollBuffer = [];
|
| 511 |
this.circleThreshold = 12;
|
| 512 |
this.swipeThreshold = 35;
|
| 513 |
this.scrollThreshold = 20;
|
|
@@ -518,9 +494,8 @@ class GestureDetector {
|
|
| 518 |
|
| 519 |
this.gestureStartPos = null;
|
| 520 |
this.holdTimer = null;
|
| 521 |
-
this.
|
| 522 |
-
this.
|
| 523 |
-
this.scrollSpeedFactor = 1.5;
|
| 524 |
|
| 525 |
this.debugManager.updateStatus('Setting up MediaPipe...');
|
| 526 |
this.setupMediaPipe();
|
|
@@ -611,7 +586,6 @@ class GestureDetector {
|
|
| 611 |
this.uiManager.clearSelection();
|
| 612 |
this.gestureBuffer = [];
|
| 613 |
this.circlePoints = [];
|
| 614 |
-
this.scrollBuffer = [];
|
| 615 |
this.debugManager.updateGestureType('None');
|
| 616 |
this.debugManager.updateBufferCount(0);
|
| 617 |
this.debugManager.updateCircleCount(0);
|
|
@@ -621,52 +595,6 @@ class GestureDetector {
|
|
| 621 |
}
|
| 622 |
}
|
| 623 |
|
| 624 |
-
calculateCircleMetrics() {
|
| 625 |
-
if (this.circlePoints.length < 3) {
|
| 626 |
-
return { center: { x: 0, y: 0 }, radius: 0, circularity: 0 };
|
| 627 |
-
}
|
| 628 |
-
|
| 629 |
-
// Find centroid
|
| 630 |
-
let centerX = 0;
|
| 631 |
-
let centerY = 0;
|
| 632 |
-
for (const point of this.circlePoints) {
|
| 633 |
-
centerX += point.x;
|
| 634 |
-
centerY += point.y;
|
| 635 |
-
}
|
| 636 |
-
centerX /= this.circlePoints.length;
|
| 637 |
-
centerY /= this.circlePoints.length;
|
| 638 |
-
|
| 639 |
-
// Calculate radius and circularity
|
| 640 |
-
let totalRadius = 0;
|
| 641 |
-
let radiusVariance = 0;
|
| 642 |
-
const radii = [];
|
| 643 |
-
|
| 644 |
-
for (const point of this.circlePoints) {
|
| 645 |
-
const dx = point.x - centerX;
|
| 646 |
-
const dy = point.y - centerY;
|
| 647 |
-
const radius = Math.sqrt(dx * dx + dy * dy);
|
| 648 |
-
radii.push(radius);
|
| 649 |
-
totalRadius += radius;
|
| 650 |
-
}
|
| 651 |
-
|
| 652 |
-
const avgRadius = totalRadius / this.circlePoints.length;
|
| 653 |
-
|
| 654 |
-
// Calculate variance to determine circularity
|
| 655 |
-
for (const radius of radii) {
|
| 656 |
-
radiusVariance += Math.pow(radius - avgRadius, 2);
|
| 657 |
-
}
|
| 658 |
-
radiusVariance /= this.circlePoints.length;
|
| 659 |
-
|
| 660 |
-
// Calculate circularity (1 = perfect circle, 0 = not circular)
|
| 661 |
-
const circularity = radiusVariance < 0.0001 ? 1 : Math.max(0, 1 - radiusVariance / (avgRadius * avgRadius));
|
| 662 |
-
|
| 663 |
-
return {
|
| 664 |
-
center: { x: centerX, y: centerY },
|
| 665 |
-
radius: avgRadius,
|
| 666 |
-
circularity: circularity
|
| 667 |
-
};
|
| 668 |
-
}
|
| 669 |
-
|
| 670 |
detectGesture(landmarks) {
|
| 671 |
try {
|
| 672 |
const indexTip = landmarks[8];
|
|
@@ -677,23 +605,18 @@ class GestureDetector {
|
|
| 677 |
const screenX = indexTip.x * window.innerWidth;
|
| 678 |
const screenY = indexTip.y * window.innerHeight;
|
| 679 |
|
| 680 |
-
//
|
| 681 |
-
const
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
const relativePos = this.uiManager.getRelativePosition(indexTip, videoParams);
|
| 685 |
|
| 686 |
// Pointing detection (index finger higher than middle)
|
| 687 |
if (indexTip.y < middleTip.y && wrist.y > indexTip.y) {
|
| 688 |
-
this.checkEmailSelection(
|
| 689 |
-
|
| 690 |
-
// Check for scroll gestures
|
| 691 |
-
this.checkScrollGesture(landmarks);
|
| 692 |
} else {
|
| 693 |
this.uiManager.clearSelection();
|
| 694 |
this.gestureBuffer = [];
|
| 695 |
this.circlePoints = [];
|
| 696 |
-
this.scrollBuffer = [];
|
| 697 |
this.debugManager.updateGestureType('None');
|
| 698 |
this.debugManager.updateBufferCount(0);
|
| 699 |
this.debugManager.updateCircleCount(0);
|
|
@@ -701,6 +624,7 @@ class GestureDetector {
|
|
| 701 |
|
| 702 |
// Only process gestures if an email is selected
|
| 703 |
if (this.selectedEmailId === null) {
|
|
|
|
| 704 |
return;
|
| 705 |
}
|
| 706 |
|
|
@@ -774,55 +698,102 @@ class GestureDetector {
|
|
| 774 |
}
|
| 775 |
}
|
| 776 |
|
| 777 |
-
|
| 778 |
const wrist = landmarks[0];
|
| 779 |
const indexTip = landmarks[8];
|
| 780 |
-
|
| 781 |
-
|
| 782 |
-
|
| 783 |
-
|
| 784 |
-
|
| 785 |
-
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 790 |
|
| 791 |
-
|
| 792 |
-
|
| 793 |
-
|
| 794 |
-
|
| 795 |
-
|
| 796 |
-
|
|
|
|
|
|
|
|
|
|
| 797 |
|
| 798 |
-
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
this.debugManager.updateGestureType(`Scroll ${direction}`);
|
| 811 |
-
}
|
| 812 |
-
}
|
| 813 |
}
|
| 814 |
}
|
| 815 |
}
|
| 816 |
|
| 817 |
-
|
| 818 |
-
|
| 819 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 820 |
|
| 821 |
return {
|
| 822 |
-
|
| 823 |
-
|
| 824 |
-
|
| 825 |
-
aspectRatio: video.videoWidth / video.videoHeight
|
| 826 |
};
|
| 827 |
}
|
| 828 |
|
|
|
|
| 74 |
},
|
| 75 |
{
|
| 76 |
id: 11,
|
| 77 |
+
sender: "Tech News",
|
| 78 |
+
subject: "The Future of AI is Here",
|
| 79 |
+
snippet: "Discover how AI is transforming industries and what it means for your career.",
|
| 80 |
timestamp: "Aug 29"
|
| 81 |
},
|
| 82 |
{
|
| 83 |
id: 12,
|
| 84 |
sender: "University Alumni",
|
| 85 |
+
subject: "Alumni Reunion This Weekend",
|
| 86 |
+
snippet: "Join us for our annual alumni reunion and reconnect with old friends.",
|
| 87 |
timestamp: "Aug 28"
|
| 88 |
},
|
| 89 |
{
|
| 90 |
id: 13,
|
| 91 |
+
sender: "Travel Agency",
|
| 92 |
+
subject: "Exclusive Deal: 50% Off Caribbean Cruise",
|
| 93 |
+
snippet: "Book now and enjoy a luxury cruise with significant savings.",
|
| 94 |
timestamp: "Aug 27"
|
| 95 |
},
|
| 96 |
{
|
| 97 |
id: 14,
|
| 98 |
+
sender: "Fitness Center",
|
| 99 |
+
subject: "New Workouts Available",
|
| 100 |
+
snippet: "Check out our new workout routines designed by top trainers.",
|
| 101 |
timestamp: "Aug 26"
|
| 102 |
},
|
| 103 |
{
|
| 104 |
id: 15,
|
| 105 |
+
sender: "Online Course",
|
| 106 |
+
subject: "Your Certificate of Completion",
|
| 107 |
+
snippet: "Congratulations! You've completed the course. Download your certificate now.",
|
| 108 |
timestamp: "Aug 25"
|
| 109 |
}
|
| 110 |
];
|
|
|
|
| 199 |
this.selectionHighlight = document.getElementById('selectionHighlight');
|
| 200 |
this.handLandmarks = document.getElementById('handLandmarks');
|
| 201 |
this.gesturePath = document.getElementById('gesturePath');
|
| 202 |
+
this.emailListRect = null;
|
| 203 |
|
| 204 |
this.selectedEmail = null;
|
| 205 |
this.emailElements = [];
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
this.renderEmails();
|
| 208 |
this.setupEventListeners();
|
|
|
|
| 210 |
// Create confirmation overlay
|
| 211 |
this.createConfirmationOverlay();
|
| 212 |
|
| 213 |
+
// Create scroll indicator
|
| 214 |
+
this.createScrollIndicator();
|
| 215 |
}
|
| 216 |
|
| 217 |
createConfirmationOverlay() {
|
|
|
|
| 252 |
});
|
| 253 |
}
|
| 254 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
showConfirmation(message, callback) {
|
| 256 |
this.confirmationOverlay.querySelector('.confirmation-message').textContent = message;
|
| 257 |
this.confirmationCallback = callback;
|
|
|
|
| 263 |
this.confirmationCallback = null;
|
| 264 |
}
|
| 265 |
|
| 266 |
+
createScrollIndicator() {
|
| 267 |
+
const indicator = document.createElement('div');
|
| 268 |
+
indicator.className = 'scroll-indicator';
|
| 269 |
+
indicator.innerHTML = '↑';
|
| 270 |
+
|
| 271 |
+
document.body.appendChild(indicator);
|
| 272 |
+
this.scrollIndicator = indicator;
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
showScrollIndicator() {
|
| 276 |
+
this.scrollIndicator.classList.add('show');
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
hideScrollIndicator() {
|
| 280 |
+
this.scrollIndicator.classList.remove('show');
|
| 281 |
+
}
|
| 282 |
+
|
| 283 |
renderEmails() {
|
| 284 |
this.emailList.innerHTML = '';
|
| 285 |
this.emailElements = [];
|
|
|
|
| 308 |
|
| 309 |
// Update email positions
|
| 310 |
this.updateEmailPositions();
|
|
|
|
|
|
|
|
|
|
| 311 |
}
|
| 312 |
|
| 313 |
updateEmailPositions() {
|
|
|
|
| 351 |
showSelectionHighlight(rect) {
|
| 352 |
this.selectionHighlight.style.display = 'block';
|
| 353 |
this.selectionHighlight.style.left = `${rect.left}px`;
|
| 354 |
+
this.selectionHighlight.style.top = `${rect.top}px`;
|
| 355 |
this.selectionHighlight.style.width = `${rect.width}px`;
|
| 356 |
this.selectionHighlight.style.height = `${rect.height}px`;
|
| 357 |
|
|
|
|
| 405 |
this.showSelectionHighlight(element.rect);
|
| 406 |
}
|
| 407 |
}
|
|
|
|
|
|
|
|
|
|
| 408 |
});
|
| 409 |
|
| 410 |
+
// Add scroll event for indicator
|
| 411 |
this.emailList.addEventListener('scroll', () => {
|
| 412 |
+
if (this.emailList.scrollTop > 0) {
|
| 413 |
+
this.showScrollIndicator();
|
| 414 |
+
} else {
|
| 415 |
+
this.hideScrollIndicator();
|
| 416 |
+
}
|
| 417 |
+
});
|
| 418 |
+
|
| 419 |
+
// Click handler for scroll indicator
|
| 420 |
+
this.scrollIndicator?.addEventListener('click', () => {
|
| 421 |
+
this.emailList.scrollTo({
|
| 422 |
+
top: 0,
|
| 423 |
+
behavior: 'smooth'
|
| 424 |
+
});
|
| 425 |
});
|
| 426 |
}
|
| 427 |
|
|
|
|
| 466 |
}
|
| 467 |
}
|
| 468 |
|
| 469 |
+
// Audio feedback simulation (if needed)
|
| 470 |
+
provideAudioFeedback() {
|
| 471 |
+
// Could implement audio feedback here
|
| 472 |
+
// const sound = new Audio('click-sound.mp3');
|
| 473 |
+
// sound.play();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 474 |
}
|
| 475 |
}
|
| 476 |
|
|
|
|
| 484 |
this.selectedEmailId = null;
|
| 485 |
this.gestureBuffer = [];
|
| 486 |
this.circlePoints = [];
|
|
|
|
| 487 |
this.circleThreshold = 12;
|
| 488 |
this.swipeThreshold = 35;
|
| 489 |
this.scrollThreshold = 20;
|
|
|
|
| 494 |
|
| 495 |
this.gestureStartPos = null;
|
| 496 |
this.holdTimer = null;
|
| 497 |
+
this.scrollActive = false;
|
| 498 |
+
this.scrollDirection = null;
|
|
|
|
| 499 |
|
| 500 |
this.debugManager.updateStatus('Setting up MediaPipe...');
|
| 501 |
this.setupMediaPipe();
|
|
|
|
| 586 |
this.uiManager.clearSelection();
|
| 587 |
this.gestureBuffer = [];
|
| 588 |
this.circlePoints = [];
|
|
|
|
| 589 |
this.debugManager.updateGestureType('None');
|
| 590 |
this.debugManager.updateBufferCount(0);
|
| 591 |
this.debugManager.updateCircleCount(0);
|
|
|
|
| 595 |
}
|
| 596 |
}
|
| 597 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 598 |
detectGesture(landmarks) {
|
| 599 |
try {
|
| 600 |
const indexTip = landmarks[8];
|
|
|
|
| 605 |
const screenX = indexTip.x * window.innerWidth;
|
| 606 |
const screenY = indexTip.y * window.innerHeight;
|
| 607 |
|
| 608 |
+
// Calculate relative position within email list
|
| 609 |
+
const emailListRect = this.uiManager.emailList.getBoundingClientRect();
|
| 610 |
+
const relativeX = screenX - emailListRect.left;
|
| 611 |
+
const relativeY = screenY - emailListRect.top;
|
|
|
|
| 612 |
|
| 613 |
// Pointing detection (index finger higher than middle)
|
| 614 |
if (indexTip.y < middleTip.y && wrist.y > indexTip.y) {
|
| 615 |
+
this.checkEmailSelection(relativeX, relativeY);
|
|
|
|
|
|
|
|
|
|
| 616 |
} else {
|
| 617 |
this.uiManager.clearSelection();
|
| 618 |
this.gestureBuffer = [];
|
| 619 |
this.circlePoints = [];
|
|
|
|
| 620 |
this.debugManager.updateGestureType('None');
|
| 621 |
this.debugManager.updateBufferCount(0);
|
| 622 |
this.debugManager.updateCircleCount(0);
|
|
|
|
| 624 |
|
| 625 |
// Only process gestures if an email is selected
|
| 626 |
if (this.selectedEmailId === null) {
|
| 627 |
+
this.processScrolling(landmarks);
|
| 628 |
return;
|
| 629 |
}
|
| 630 |
|
|
|
|
| 698 |
}
|
| 699 |
}
|
| 700 |
|
| 701 |
+
processScrolling(landmarks) {
|
| 702 |
const wrist = landmarks[0];
|
| 703 |
const indexTip = landmarks[8];
|
| 704 |
+
|
| 705 |
+
// Only process scrolling when no email is selected
|
| 706 |
+
if (this.selectedEmailId !== null) return;
|
| 707 |
+
|
| 708 |
+
// Calculate screen coordinates
|
| 709 |
+
const screenX = indexTip.x * window.innerWidth;
|
| 710 |
+
const screenY = indexTip.y * window.innerHeight;
|
| 711 |
+
|
| 712 |
+
// Calculate relative position within email list
|
| 713 |
+
const emailListRect = this.uiManager.emailList.getBoundingClientRect();
|
| 714 |
+
const relativeX = screenX - emailListRect.left;
|
| 715 |
+
const relativeY = screenY - emailListRect.top;
|
| 716 |
+
|
| 717 |
+
// Only scroll if finger is in the email list area
|
| 718 |
+
if (relativeX < 0 || relativeX > emailListRect.width ||
|
| 719 |
+
relativeY < 0 || relativeY > emailListRect.height) {
|
| 720 |
+
return;
|
| 721 |
+
}
|
| 722 |
+
|
| 723 |
+
// Calculate velocity for scrolling
|
| 724 |
+
if (this.gestureBuffer.length > 1) {
|
| 725 |
+
const lastPoint = this.gestureBuffer[this.gestureBuffer.length - 2];
|
| 726 |
+
const currentPoint = this.gestureBuffer[this.gestureBuffer.length - 1];
|
| 727 |
|
| 728 |
+
const dx = (currentPoint.x - lastPoint.x) * window.innerWidth;
|
| 729 |
+
const dy = (currentPoint.y - lastPoint.y) * window.innerHeight;
|
| 730 |
+
const distance = Math.sqrt(dx * dx + dy * dy);
|
| 731 |
+
const speed = distance / (this.lastTimestamp - lastPoint.timestamp);
|
| 732 |
+
|
| 733 |
+
// Only scroll if movement is primarily vertical
|
| 734 |
+
if (Math.abs(dy) > this.scrollThreshold && Math.abs(dy) > Math.abs(dx) * 1.5 && speed > 0.5) {
|
| 735 |
+
this.scrollActive = true;
|
| 736 |
+
this.scrollDirection = dy > 0 ? 'down' : 'up';
|
| 737 |
|
| 738 |
+
// Show scroll indicator
|
| 739 |
+
this.uiManager.showScrollIndicator();
|
| 740 |
+
|
| 741 |
+
// Perform scroll
|
| 742 |
+
const scrollAmount = Math.min(150, Math.abs(dy) * 2);
|
| 743 |
+
this.uiManager.emailList.scrollBy({
|
| 744 |
+
top: this.scrollDirection === 'up' ? -scrollAmount : scrollAmount,
|
| 745 |
+
behavior: 'smooth'
|
| 746 |
+
});
|
| 747 |
+
|
| 748 |
+
// Reset buffer to prevent multiple scrolls from same movement
|
| 749 |
+
this.gestureBuffer = [];
|
|
|
|
|
|
|
|
|
|
| 750 |
}
|
| 751 |
}
|
| 752 |
}
|
| 753 |
|
| 754 |
+
calculateCircleMetrics() {
|
| 755 |
+
if (this.circlePoints.length < 3) {
|
| 756 |
+
return { center: { x: 0, y: 0 }, radius: 0, circularity: 0 };
|
| 757 |
+
}
|
| 758 |
+
|
| 759 |
+
// Find centroid
|
| 760 |
+
let centerX = 0;
|
| 761 |
+
let centerY = 0;
|
| 762 |
+
for (const point of this.circlePoints) {
|
| 763 |
+
centerX += point.x;
|
| 764 |
+
centerY += point.y;
|
| 765 |
+
}
|
| 766 |
+
centerX /= this.circlePoints.length;
|
| 767 |
+
centerY /= this.circlePoints.length;
|
| 768 |
+
|
| 769 |
+
// Calculate radius and circularity
|
| 770 |
+
let totalRadius = 0;
|
| 771 |
+
let radiusVariance = 0;
|
| 772 |
+
const radii = [];
|
| 773 |
+
|
| 774 |
+
for (const point of this.circlePoints) {
|
| 775 |
+
const dx = point.x - centerX;
|
| 776 |
+
const dy = point.y - centerY;
|
| 777 |
+
const radius = Math.sqrt(dx * dx + dy * dy);
|
| 778 |
+
radii.push(radius);
|
| 779 |
+
totalRadius += radius;
|
| 780 |
+
}
|
| 781 |
+
|
| 782 |
+
const avgRadius = totalRadius / this.circlePoints.length;
|
| 783 |
+
|
| 784 |
+
// Calculate variance to determine circularity
|
| 785 |
+
for (const radius of radii) {
|
| 786 |
+
radiusVariance += Math.pow(radius - avgRadius, 2);
|
| 787 |
+
}
|
| 788 |
+
radiusVariance /= this.circlePoints.length;
|
| 789 |
+
|
| 790 |
+
// Calculate circularity (1 = perfect circle, 0 = not circular)
|
| 791 |
+
const circularity = radiusVariance < 0.0001 ? 1 : Math.max(0, 1 - radiusVariance / (avgRadius * avgRadius));
|
| 792 |
|
| 793 |
return {
|
| 794 |
+
center: { x: centerX, y: centerY },
|
| 795 |
+
radius: avgRadius,
|
| 796 |
+
circularity: circularity
|
|
|
|
| 797 |
};
|
| 798 |
}
|
| 799 |
|