Custom HTML Prompt Window Dragger
Purpose
When you have a Custom HTML Prompt without a caption, you generally can't drag it around the screen.
This code allows you to fix that.
REQUIRES
- macOS Ventura or later
Usage
Click to expand
I've provided complete examples in the macros, but it's easier to explain if I skip the actual implementation code right now.
You'll copy the code for two classes (explained later) into the script portion of your HTML code:
<script type="text/javascript">
class KM {
...
}
class WindowDragger {
...
}
....
</script>
You can find the complete source for these classes in
- The installed macro "Custom HTML Prompt Window Dragger - Implementation Classes"
- Either of the Example macros
- ... and later below in this post
Simplest Usage
Click to expand
In your KMWindow function, include code like this:
function KMWindow() {
...
const windowDragger = new WindowDragger("divClient");
windowDragger.attach();
...
}
In this example, "divClient" is the HTML element I want to be able to click on, to drag the window.
That's all you have to do - include the code that actually implements the dragging, and invoke it as shown above.
Modifier Keys
Click to expand
If you want the drag to only happen when certain modifier keys are pressed, you can pass a second parameter to the constructor, like this:
function KMWindow() {
...
const windowDragger = new WindowDragger("divClient", {cmdKey: true});
windowDragger.attach();
}
(The second parameter in this example is {cmdKey: true}
In this example, dragging will only happen if the user holds down the Command Key when pressing the mouse button down.
See the macro "Example 1" for the actual implementation.
You can specify more modifier keys, by passing something like this:
{shiftKey: true, cmdKey: true};
Here's all the options:
{shiftKey: true, ctrlKey: true, altKey: true, cmdKey: true};
If you include this parameter, then the keys you specify must be down, and the other modifier keys must not be down, or the "mouse down" event is ignored.
Temporarily Enble or Disable Dragging
Click to expand
The macro "Example 2" shows how to enable dragging when you want, and disable it other times.
Basically, you call "windowDragger.attach()
" method to enable dragging, and "windowDragger.detach()
" method to disable dragging.
Implementation
Click to expand
The code that implements all this is in both exanples. There's two classes you need to include.
WindowDragger
This is where all the magic happens.
class WindowDragger {
#element;
#modifierKeys;
#offsetX;
#offsetY;
#pointerDownEventHandler;
#pointerMoveEventHandler;
#pointerUpEventHandler;
#attached;
/**
*
* @param {elementId} element
* @param {{shiftKey: boolean, ctrlKey: boolean, altKey: boolean, cmdKey: boolean}} modifierKeys
*/
constructor(elementId, modifierKeys) {
this.#element = document.getElementById(elementId);
if (modifierKeys) {
this.#modifierKeys = {
shiftKey: modifierKeys.shiftKey ?? false,
ctrlKey: modifierKeys.ctrlKey ?? false,
altKey: modifierKeys.altKey ?? false,
metaKey: modifierKeys.cmdKey ?? modifierKeys.metaKey ?? false
};
}
this.#pointerDownEventHandler = this.#pointerDown.bind(this);
this.#pointerMoveEventHandler = this.#drag.bind(this);
this.#pointerUpEventHandler = this.#stopDragging.bind(this);
}
attach() {
this.#element.addEventListener("pointerdown", this.#pointerDownEventHandler);
this.#attached = true;
}
detach() {
if (!this.#attached)
return;
this.#element.removeEventListener("pointerdown", this.#pointerDownEventHandler);
this.#attached = false;
}
get isAttached() { return this.#attached ?? false; }
#pointerDown(e) {
if (e.button !== 0)
return;
console.log(e);
if (this.#modifierKeys) {
const keyNames = ["shiftKey", "ctrlKey", "altKey", "metaKey"];
if (!keyNames.every(keyName => e[keyName] === this.#modifierKeys[keyName]))
return;
}
this.#beginDragging(e);
}
#beginDragging(e) {
this.#element.addEventListener("pointermove", this.#pointerMoveEventHandler);
this.#element.addEventListener("pointerup", this.#pointerUpEventHandler);
this.#element.setPointerCapture(e.pointerId);
this.#offsetX = KM.getWindowLeft() - KM.getCursorX();
this.#offsetY = KM.getWindowTop() - KM.getCursorY();
}
#drag(e) {
KM.moveWindow(KM.getCursorX() + this.#offsetX, KM.getCursorY() + this.#offsetY);
}
#stopDragging(e) {
this.#element.removeEventListener("pointermove", this.#pointerMoveEventHandler);
this.#element.removeEventListener("pointerup", this.#pointerUpEventHandler);
}
}
KM
This class contains some utility routines used by the main class.
class KM {
static calculate(value) { return parseInt(window.KeyboardMaestro.Calculate(value)); }
static getWindowLeft() { return this.calculate("WINDOW(Left)"); }
static getWindowTop() { return this.calculate("WINDOW(Top)"); }
static getWindowWidth() { return this.calculate("WINDOW(Width)"); }
static getWindowHeight() { return this.calculate("WINDOW(height)"); }
static getWindowBounds() { return new Rect(this.getWindowLeft(), this.getWindowTop(), this.getWindowWidth(), this.getWindowHeight()); }
static getCursorX() { return this.calculate("MOUSEX()"); }
static getCursorY() { return this.calculate("MOUSEY()"); }
static moveWindow(x, y) { window.KeyboardMaestro.ResizeWindow(`${x},${y},${this.getWindowWidth()},${this.getWindowHeight()}`); }
}
Here's a complete web page, which is included in the macro "Example 1":
Complete Example
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style type="text/css">
#divClient {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: blue;
color: white;
}
</style>
<script type="text/javascript">
class KM {
static calculate(value) { return parseInt(window.KeyboardMaestro.Calculate(value)); }
static getWindowLeft() { return this.calculate("WINDOW(Left)"); }
static getWindowTop() { return this.calculate("WINDOW(Top)"); }
static getWindowWidth() { return this.calculate("WINDOW(Width)"); }
static getWindowHeight() { return this.calculate("WINDOW(height)"); }
static getWindowBounds() { return new Rect(this.getWindowLeft(), this.getWindowTop(), this.getWindowWidth(), this.getWindowHeight()); }
static getCursorX() { return this.calculate("MOUSEX()"); }
static getCursorY() { return this.calculate("MOUSEY()"); }
static moveWindow(x, y) { window.KeyboardMaestro.ResizeWindow(`${x},${y},${this.getWindowWidth()},${this.getWindowHeight()}`); }
}
class WindowDragger {
#element;
#modifierKeys;
#offsetX;
#offsetY;
#pointerDownEventHandler;
#pointerMoveEventHandler;
#pointerUpEventHandler;
#attached;
/**
*
* @param {elementId} element
* @param {{shiftKey: boolean, ctrlKey: boolean, altKey: boolean, cmdKey: boolean}} modifierKeys
*/
constructor(elementId, modifierKeys) {
this.#element = document.getElementById(elementId);
if (modifierKeys) {
this.#modifierKeys = {
shiftKey: modifierKeys.shiftKey ?? false,
ctrlKey: modifierKeys.ctrlKey ?? false,
altKey: modifierKeys.altKey ?? false,
metaKey: modifierKeys.cmdKey ?? modifierKeys.metaKey ?? false
};
}
this.#pointerDownEventHandler = this.#pointerDown.bind(this);
this.#pointerMoveEventHandler = this.#drag.bind(this);
this.#pointerUpEventHandler = this.#stopDragging.bind(this);
}
attach() {
this.#element.addEventListener("pointerdown", this.#pointerDownEventHandler);
this.#attached = true;
}
detach() {
if (!this.#attached)
return;
this.#element.removeEventListener("pointerdown", this.#pointerDownEventHandler);
this.#attached = false;
}
get isAttached() { return this.#attached ?? false; }
#pointerDown(e) {
if (e.button !== 0)
return;
console.log(e);
if (this.#modifierKeys) {
const keyNames = ["shiftKey", "ctrlKey", "altKey", "metaKey"];
if (!keyNames.every(keyName => e[keyName] === this.#modifierKeys[keyName]))
return;
}
this.#beginDragging(e);
}
#beginDragging(e) {
this.#element.addEventListener("pointermove", this.#pointerMoveEventHandler);
this.#element.addEventListener("pointerup", this.#pointerUpEventHandler);
this.#element.setPointerCapture(e.pointerId);
this.#offsetX = KM.getWindowLeft() - KM.getCursorX();
this.#offsetY = KM.getWindowTop() - KM.getCursorY();
}
#drag(e) {
KM.moveWindow(KM.getCursorX() + this.#offsetX, KM.getCursorY() + this.#offsetY);
}
#stopDragging(e) {
this.#element.removeEventListener("pointermove", this.#pointerMoveEventHandler);
this.#element.removeEventListener("pointerup", this.#pointerUpEventHandler);
}
}
function KMWindow() {
const windowDragger = new WindowDragger("divClient", {cmdKey: true});
windowDragger.attach();
return "SCREENVISIBLE(Main, MidX) - 350, SCREENVISIBLE(Main, MidY) - 200, 700, 400"
}
</script>
</head>
<body>
<div id="divClient">
<div>Hold down the Command Key & left mouse button to start drgging</div>
<button id="btnClose" onclick="window.close()">close</button>
</div>
</body>
</html>
INSTALLATION
Download the zip file, unzip it, and double-click the .kmmacros file.
Custom HTML Prompt Window Dragger.v1.0.kmmacros.zip (5.4 KB)