223 lines
7.2 KiB
JavaScript
223 lines
7.2 KiB
JavaScript
/*
|
|
_ __ _ __
|
|
| | / /___ _(_) /____
|
|
| | /| / / __ `/ / / ___/
|
|
| |/ |/ / /_/ / / (__ )
|
|
|__/|__/\__,_/_/_/____/
|
|
The electron alternative for Go
|
|
(c) Lea Anthony 2019-present
|
|
*/
|
|
import { invoke, IsWindows } from "./system.js";
|
|
import { GetFlag } from "./flags.js";
|
|
import { canTrackButtons, eventTarget } from "./utils.js";
|
|
// Setup
|
|
let canDrag = false;
|
|
let dragging = false;
|
|
let resizable = false;
|
|
let canResize = false;
|
|
let resizing = false;
|
|
let resizeEdge = "";
|
|
let defaultCursor = "auto";
|
|
let buttons = 0;
|
|
const buttonsTracked = canTrackButtons();
|
|
window._wails = window._wails || {};
|
|
window._wails.setResizable = (value) => {
|
|
resizable = value;
|
|
if (!resizable) {
|
|
// Stop resizing if in progress.
|
|
canResize = resizing = false;
|
|
setResize();
|
|
}
|
|
};
|
|
window.addEventListener('mousedown', update, { capture: true });
|
|
window.addEventListener('mousemove', update, { capture: true });
|
|
window.addEventListener('mouseup', update, { capture: true });
|
|
for (const ev of ['click', 'contextmenu', 'dblclick']) {
|
|
window.addEventListener(ev, suppressEvent, { capture: true });
|
|
}
|
|
function suppressEvent(event) {
|
|
// Suppress click events while resizing or dragging.
|
|
if (dragging || resizing) {
|
|
event.stopImmediatePropagation();
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
}
|
|
}
|
|
// Use constants to avoid comparing strings multiple times.
|
|
const MouseDown = 0;
|
|
const MouseUp = 1;
|
|
const MouseMove = 2;
|
|
function update(event) {
|
|
// Windows suppresses mouse events at the end of dragging or resizing,
|
|
// so we need to be smart and synthesize button events.
|
|
let eventType, eventButtons = event.buttons;
|
|
switch (event.type) {
|
|
case 'mousedown':
|
|
eventType = MouseDown;
|
|
if (!buttonsTracked) {
|
|
eventButtons = buttons | (1 << event.button);
|
|
}
|
|
break;
|
|
case 'mouseup':
|
|
eventType = MouseUp;
|
|
if (!buttonsTracked) {
|
|
eventButtons = buttons & ~(1 << event.button);
|
|
}
|
|
break;
|
|
default:
|
|
eventType = MouseMove;
|
|
if (!buttonsTracked) {
|
|
eventButtons = buttons;
|
|
}
|
|
break;
|
|
}
|
|
let released = buttons & ~eventButtons;
|
|
let pressed = eventButtons & ~buttons;
|
|
buttons = eventButtons;
|
|
// Synthesize a release-press sequence if we detect a press of an already pressed button.
|
|
if (eventType === MouseDown && !(pressed & event.button)) {
|
|
released |= (1 << event.button);
|
|
pressed |= (1 << event.button);
|
|
}
|
|
// Suppress all button events during dragging and resizing,
|
|
// unless this is a mouseup event that is ending a drag action.
|
|
if (eventType !== MouseMove // Fast path for mousemove
|
|
&& resizing
|
|
|| (dragging
|
|
&& (eventType === MouseDown
|
|
|| event.button !== 0))) {
|
|
event.stopImmediatePropagation();
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
}
|
|
// Handle releases
|
|
if (released & 1) {
|
|
primaryUp(event);
|
|
}
|
|
// Handle presses
|
|
if (pressed & 1) {
|
|
primaryDown(event);
|
|
}
|
|
// Handle mousemove
|
|
if (eventType === MouseMove) {
|
|
onMouseMove(event);
|
|
}
|
|
;
|
|
}
|
|
function primaryDown(event) {
|
|
// Reset readiness state.
|
|
canDrag = false;
|
|
canResize = false;
|
|
// Ignore repeated clicks on macOS and Linux.
|
|
if (!IsWindows()) {
|
|
if (event.type === 'mousedown' && event.button === 0 && event.detail !== 1) {
|
|
return;
|
|
}
|
|
}
|
|
if (resizeEdge) {
|
|
// Ready to resize if the primary button was pressed for the first time.
|
|
canResize = true;
|
|
// Do not start drag operations when on resize edges.
|
|
return;
|
|
}
|
|
// Retrieve target element
|
|
const target = eventTarget(event);
|
|
// Ready to drag if the primary button was pressed for the first time on a draggable element.
|
|
// Ignore clicks on the scrollbar.
|
|
const style = window.getComputedStyle(target);
|
|
canDrag = (style.getPropertyValue("--wails-draggable").trim() === "drag"
|
|
&& (event.offsetX - parseFloat(style.paddingLeft) < target.clientWidth
|
|
&& event.offsetY - parseFloat(style.paddingTop) < target.clientHeight));
|
|
}
|
|
function primaryUp(event) {
|
|
// Stop dragging and resizing.
|
|
canDrag = false;
|
|
dragging = false;
|
|
canResize = false;
|
|
resizing = false;
|
|
}
|
|
const cursorForEdge = Object.freeze({
|
|
"se-resize": "nwse-resize",
|
|
"sw-resize": "nesw-resize",
|
|
"nw-resize": "nwse-resize",
|
|
"ne-resize": "nesw-resize",
|
|
"w-resize": "ew-resize",
|
|
"n-resize": "ns-resize",
|
|
"s-resize": "ns-resize",
|
|
"e-resize": "ew-resize",
|
|
});
|
|
function setResize(edge) {
|
|
if (edge) {
|
|
if (!resizeEdge) {
|
|
defaultCursor = document.body.style.cursor;
|
|
}
|
|
document.body.style.cursor = cursorForEdge[edge];
|
|
}
|
|
else if (!edge && resizeEdge) {
|
|
document.body.style.cursor = defaultCursor;
|
|
}
|
|
resizeEdge = edge || "";
|
|
}
|
|
function onMouseMove(event) {
|
|
if (canResize && resizeEdge) {
|
|
// Start resizing.
|
|
resizing = true;
|
|
invoke("wails:resize:" + resizeEdge);
|
|
}
|
|
else if (canDrag) {
|
|
// Start dragging.
|
|
dragging = true;
|
|
invoke("wails:drag");
|
|
}
|
|
if (dragging || resizing) {
|
|
// Either drag or resize is ongoing,
|
|
// reset readiness and stop processing.
|
|
canDrag = canResize = false;
|
|
return;
|
|
}
|
|
if (!resizable || !IsWindows()) {
|
|
if (resizeEdge) {
|
|
setResize();
|
|
}
|
|
return;
|
|
}
|
|
const resizeHandleHeight = GetFlag("system.resizeHandleHeight") || 5;
|
|
const resizeHandleWidth = GetFlag("system.resizeHandleWidth") || 5;
|
|
// Extra pixels for the corner areas.
|
|
const cornerExtra = GetFlag("resizeCornerExtra") || 10;
|
|
const rightBorder = (window.outerWidth - event.clientX) < resizeHandleWidth;
|
|
const leftBorder = event.clientX < resizeHandleWidth;
|
|
const topBorder = event.clientY < resizeHandleHeight;
|
|
const bottomBorder = (window.outerHeight - event.clientY) < resizeHandleHeight;
|
|
// Adjust for corner areas.
|
|
const rightCorner = (window.outerWidth - event.clientX) < (resizeHandleWidth + cornerExtra);
|
|
const leftCorner = event.clientX < (resizeHandleWidth + cornerExtra);
|
|
const topCorner = event.clientY < (resizeHandleHeight + cornerExtra);
|
|
const bottomCorner = (window.outerHeight - event.clientY) < (resizeHandleHeight + cornerExtra);
|
|
if (!leftCorner && !topCorner && !bottomCorner && !rightCorner) {
|
|
// Optimisation: out of all corner areas implies out of borders.
|
|
setResize();
|
|
}
|
|
// Detect corners.
|
|
else if (rightCorner && bottomCorner)
|
|
setResize("se-resize");
|
|
else if (leftCorner && bottomCorner)
|
|
setResize("sw-resize");
|
|
else if (leftCorner && topCorner)
|
|
setResize("nw-resize");
|
|
else if (topCorner && rightCorner)
|
|
setResize("ne-resize");
|
|
// Detect borders.
|
|
else if (leftBorder)
|
|
setResize("w-resize");
|
|
else if (topBorder)
|
|
setResize("n-resize");
|
|
else if (bottomBorder)
|
|
setResize("s-resize");
|
|
else if (rightBorder)
|
|
setResize("e-resize");
|
|
// Out of border area.
|
|
else
|
|
setResize();
|
|
}
|