Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Homunculus Event Handlers with a splash of #48 #93

Merged
merged 8 commits into from
Oct 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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();
}