Skip to content

Commit

Permalink
Merge pull request #93 from RAIRLab/64-rewrite-with-universal-event-l…
Browse files Browse the repository at this point in the history
…isteners

Homunculus Event Handlers with a splash of #48
  • Loading branch information
James-Oswald authored Oct 9, 2023
2 parents 593bc9a + 9db6301 commit 0bea196
Show file tree
Hide file tree
Showing 6 changed files with 290 additions and 23 deletions.
8 changes: 4 additions & 4 deletions src/AEG/AtomNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ export class AtomNode {
* @param rect The rectangle to be set as the boundary box of this node.
* @param val The value of the proposition represented by this node.
*/
public constructor(val: string, origin: Point, rect: Rectangle) {
this.rect = rect;
this.identifier = val;
this.origin = origin;
public constructor(val?: string, origin?: Point, rect?: Rectangle) {
this.rect = rect ?? new Rectangle();
this.identifier = val ?? "";
this.origin = origin ?? new Point();
}

/**
Expand Down
11 changes: 11 additions & 0 deletions src/AEG/Point.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ export class Point {
this.y = coordY;
}

/**
* Returns the distance between this Point and the other.
* @param otherPoint the other Point
* @returns the distance between the two
*/
public distance(otherPoint: Point): number {
const dx = this.x - otherPoint.x;
const dy = this.y - otherPoint.y;
return Math.sqrt(dx * dx + dy * dy);
}

/**
* Method that returns a string representation of the point.
* @returns The coordinates of the point.
Expand Down
8 changes: 4 additions & 4 deletions src/AEG/Rectangle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ export class Rectangle {
* @param w The width of the rectangle.
* @param h The height of the rectangle.
*/
public constructor(vertex: Point, w: number, h: number) {
this.startVertex = vertex;
this.width = w;
this.height = h;
public constructor(vertex?: Point, w?: number, h?: number) {
this.startVertex = vertex ?? new Point();
this.width = w ?? 0;
this.height = h ?? 0;
}

/**
Expand Down
108 changes: 108 additions & 0 deletions src/AtomMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import {Point} from "./AEG/Point";
import {AtomNode} from "./AEG/AtomNode";
import {redrawCut, tree} from "./index";
import {Rectangle} from "./AEG/Rectangle";

const canvas: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById("canvas");
const res: CanvasRenderingContext2D | null = canvas.getContext("2d");
if (res === null) {
throw Error("2d rendering context not supported");
}
const ctx: CanvasRenderingContext2D = res;
let atomMetrics: TextMetrics;

let hasMouseDown: Boolean = false;
let hasAtom: Boolean = false;
let currentAtom: AtomNode = new AtomNode();

/**
* Will compare the event given with all possible events it could be.
* keypress checks to see if the key was a letter and if yes sets it to that letter.
* mousedown sets the atom down, calculates the bounding box, and checks for what color.
* mousemove will alter the origin position and the starting vertex of the bounding box.
* mouseup will add the atom to the tree if it is in a valid location.
* mosueout will end drawing early.
* @param event The event that will be used
* @param event the event that will be used
*/
export function atomHandler(event: Event) {
if (event.type === "keypress") {
const thisEvent: KeyboardEvent = <KeyboardEvent>event;
const regex = new RegExp(/^[A-Za-z]$/);
if (regex.test(thisEvent.key)) {
currentAtom.identifier = thisEvent.key;
hasAtom = true;
}
} else if (event.type === "mousedown" && hasAtom) {
const thisEvent: MouseEvent = <MouseEvent>event;
atomMetrics = ctx.measureText(currentAtom.identifier);
const startVertex: Point = new Point(
thisEvent.clientX,
thisEvent.clientY - atomMetrics.actualBoundingBoxAscent
);
currentAtom.rect = new Rectangle(
startVertex,
atomMetrics.width,
atomMetrics.fontBoundingBoxDescent + atomMetrics.actualBoundingBoxAscent
);
currentAtom.origin = new Point(thisEvent.clientX, thisEvent.clientY);

ctx.clearRect(0, 0, canvas.width, canvas.height);
redrawCut(tree.sheet);
if (tree.canInsert(currentAtom)) {
drawAtom(currentAtom, "#00FF00");
} else {
drawAtom(currentAtom, "#6600ff");
}
hasMouseDown = true;
} else if (event.type === "mousemove" && hasMouseDown) {
const thisEvent: MouseEvent = <MouseEvent>event;
currentAtom.origin = new Point(thisEvent.clientX, thisEvent.clientY);
currentAtom.rect.startVertex = new Point(
thisEvent.clientX,
thisEvent.clientY - atomMetrics.actualBoundingBoxAscent
);

ctx.clearRect(0, 0, canvas.width, canvas.height);
redrawCut(tree.sheet);
if (tree.canInsert(currentAtom)) {
drawAtom(currentAtom, "#00FF00");
} else {
drawAtom(currentAtom, "#FF0000");
}
} else if (event.type === "mouseup" && hasMouseDown) {
if (tree.canInsert(currentAtom)) {
tree.insert(currentAtom);
}
currentAtom = new AtomNode(currentAtom.identifier);
ctx.clearRect(0, 0, canvas.width, canvas.height);
redrawCut(tree.sheet);
hasMouseDown = false;
console.log(tree.toString());
} else if (event.type === "mouseout" && hasMouseDown) {
hasMouseDown = false;
currentAtom = new AtomNode(currentAtom.identifier);
ctx.clearRect(0, 0, canvas.width, canvas.height);
redrawCut(tree.sheet);
}
}

/**
* Draws the given atomNode with the given color.
* @param thisAtom the atomnode to be drawn.
* @param color the color of the atom.
*/
function drawAtom(thisAtom: AtomNode, color: string) {
ctx.fillStyle = color;
ctx.strokeStyle = color;
const displayBox = thisAtom.rect;
ctx.beginPath();
ctx.fillText(thisAtom.identifier, thisAtom.origin.x, thisAtom.origin.y);
ctx.rect(
displayBox.startVertex.x,
displayBox.startVertex.y,
displayBox.width,
displayBox.height
);
ctx.stroke();
}
116 changes: 116 additions & 0 deletions src/CutMode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import {Point} from "./AEG/Point";
import {CutNode} from "./AEG/CutNode";
import {Ellipse} from "./AEG/Ellipse";
import {redrawCut} from "./index";
import {tree} from "./index";

const canvas: HTMLCanvasElement = <HTMLCanvasElement>document.getElementById("canvas");
const res: CanvasRenderingContext2D | null = canvas.getContext("2d");
const showRectElm: HTMLInputElement = <HTMLInputElement>document.getElementById("showRect");
const modeElm: HTMLSelectElement = <HTMLSelectElement>document.getElementById("mode");
if (res === null) {
throw Error("2d rendering context not supported");
}
const ctx: CanvasRenderingContext2D = res;

let hasMouseDown: Boolean = false;
let currentEllipse: Ellipse = new Ellipse();
let startingPoint: Point = new Point();

/**
* Will compare the event given with all possible events it could be.
* mousedown events will allocate the starting point and allow the later events to take place
* mousemove will call createEllipse starting and current points, if invalid place will color it.
* mouseup will add the cut to the tree if it is in a valid place, and set hasmousedown to false.
* mosueout will end drawing early.
* @param event The event that will be used
*/
export function cutHandler(event: MouseEvent) {
let newCut: CutNode = new CutNode();
const currentPoint: Point = new Point();

if (event.type === "mousedown") {
hasMouseDown = true;
startingPoint.x = event.clientX;
startingPoint.y = event.clientY;
} else if (event.type === "mousemove" && hasMouseDown) {
currentPoint.x = event.clientX;
currentPoint.y = event.clientY;
ctx.clearRect(0, 0, canvas.width, canvas.height);
redrawCut(tree.sheet);
currentEllipse = createEllipse(startingPoint, currentPoint);
newCut.ellipse = currentEllipse;

if (tree.canInsert(newCut) && currentEllipse.radiusX > 15 && currentEllipse.radiusY > 15) {
drawEllipse(newCut, "#00FF00");
} else {
drawEllipse(newCut, "#FF0000");
}
} else if (event.type === "mouseup" && hasMouseDown) {
newCut = new CutNode(currentEllipse);
if (tree.canInsert(newCut) && currentEllipse.radiusX > 15 && currentEllipse.radiusY > 15) {
tree.insert(newCut);
}
hasMouseDown = false;
startingPoint = new Point();
ctx.clearRect(0, 0, canvas.width, canvas.height);
redrawCut(tree.sheet);
console.log(tree.toString());
} else if (event.type === "mouseout" && hasMouseDown) {
hasMouseDown = false;
startingPoint = new Point();
ctx.clearRect(0, 0, canvas.width, canvas.height);
redrawCut(tree.sheet);
}
}

/**
* A function to draw an ellipse between two points designated by the user.
* @param original the point where the user originally clicked
* @param current the point where the user's mouse is currently located
*/
export function createEllipse(original: Point, current: Point): Ellipse {
const center: Point = new Point(
(current.x - original.x) / 2 + original.x,
(current.y - original.y) / 2 + original.y
);

const sdx = original.x - current.x;
const sdy = original.y - current.y;
const dx = Math.abs(sdx);
const dy = Math.abs(sdy);
let rx, ry: number;

if (modeElm.value === "circumscribed") {
//This inscribed ellipse solution is inspired by the discussion of radius ratios in
//https://stackoverflow.com/a/433426/6342516
const rv: number = Math.floor(center.distance(current));
ry = Math.floor(rv * (dy / dx));
rx = Math.floor(rv * (dx / dy));
} else {
rx = dx / 2;
ry = dy / 2;
}

if (showRectElm.checked) {
ctx.beginPath();
ctx.rect(original.x, original.y, -sdx, -sdy);
ctx.stroke();
}

return new Ellipse(center, rx, ry);
}

/**
* Draws the given cut onto the canvas.
* @param thisCut The cut containing the ellipse to be drawn
* @param color the line color of the ellipse
*/
function drawEllipse(thisCut: CutNode, color: string) {
ctx.strokeStyle = color;
const ellipse: Ellipse = <Ellipse>thisCut.ellipse;
const center: Point = ellipse.center;
ctx.beginPath();
ctx.ellipse(center.x, center.y, ellipse.radiusX, ellipse.radiusY, 0, 0, 2 * Math.PI);
ctx.stroke();
}
62 changes: 47 additions & 15 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {AEGTree} from "./AEG/AEGTree";
import {CutNode} from "./AEG/CutNode";
import {Ellipse} from "./AEG/Ellipse";
import {AtomNode} from "./AEG/AtomNode";
import {ellipseCreation, removeCutListener} from "./EllipseCreation";
import {atomCreation, removeAtomListener} from "./AtomCreation";
import {cutHandler} from "./CutMode";
import {atomHandler} from "./AtomMode";

//Extend the window interface to export functions without TS complaining
declare global {
Expand All @@ -32,8 +32,7 @@ ctx.font = "35pt arial";

//Global State
const cutDisplay = <HTMLParagraphElement>document.getElementById("graphString");
let inEllipseMode: Boolean = false;
let inAtomMode: Boolean = false;
let modeState: string;
export const tree: AEGTree = new AEGTree();

//Window Exports
Expand All @@ -51,24 +50,47 @@ declare global {
* If atomMode was previously active, remove the listener.
*/
function ellipseMode() {
inEllipseMode = true;
ellipseCreation();
if (inAtomMode) {
removeAtomListener();
inAtomMode = false;
if (modeState !== "ellipseMode") {
removeListeners();
modeState = "ellipseMode";
}
canvas.addEventListener("mousedown", cutHandler);
canvas.addEventListener("mousemove", cutHandler);
canvas.addEventListener("mouseup", cutHandler);
canvas.addEventListener("mouseout", cutHandler);
}

/**
* A function to begin atom creation.
* If ellipseMode was previously active, remove the listener.
*/
function atomMode() {
inAtomMode = true;
atomCreation();
if (inEllipseMode) {
removeCutListener();
inEllipseMode = false;
if (modeState !== "atomMode") {
removeListeners();
modeState = "atomMode";
}
window.addEventListener("keypress", atomHandler);
canvas.addEventListener("mousedown", atomHandler);
canvas.addEventListener("mousemove", atomHandler);
canvas.addEventListener("mouseup", atomHandler);
canvas.addEventListener("mouseout", atomHandler);
}

/**
* Removes all listeners added in a certain mode.
*/
function removeListeners() {
if (modeState === "ellipseMode") {
canvas.removeEventListener("mousedown", cutHandler);
canvas.removeEventListener("mousemove", cutHandler);
canvas.removeEventListener("mouseup", cutHandler);
canvas.removeEventListener("mouseout", cutHandler);
} else if (modeState === "atomMode") {
window.removeEventListener("keypress", atomHandler);
canvas.removeEventListener("mousedown", atomHandler);
canvas.removeEventListener("mousemove", atomHandler);
canvas.removeEventListener("mouseup", atomHandler);
canvas.removeEventListener("mouseout", atomHandler);
}
}

Expand Down Expand Up @@ -103,10 +125,20 @@ export function redrawCut(incomingNode: CutNode) {
}

/**
* Redraws the given atom.
* Redraws the given atom. Also redraws the the bounding box.
* @param incomingNode The Atom Node to be redrawn
*/
function redrawAtom(incomingNode: AtomNode) {
const displayBox = incomingNode.rect;
ctx.strokeStyle = "#000000";
ctx.fillStyle = "#000000";
ctx.beginPath();
ctx.rect(
displayBox.startVertex.x,
displayBox.startVertex.y,
displayBox.width,
displayBox.height
);
ctx.fillText(incomingNode.identifier, incomingNode.origin.x, incomingNode.origin.y);
ctx.stroke();
}

0 comments on commit 0bea196

Please sign in to comment.