Skip to content

Commit

Permalink
Merge branch 'master' into 22-prevent-text-from-being-highlighted-whe…
Browse files Browse the repository at this point in the history
…n-mouse-draged-outside-of-ui-1
  • Loading branch information
James-Oswald authored Oct 9, 2023
2 parents e9d4a81 + 0bea196 commit b936810
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 35 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
1 change: 0 additions & 1 deletion src/AEG/Ellipse.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {Point} from "./Point";
import {Rectangle} from "./Rectangle";
//import {Polynomial, polynomialRoots} from "nomial";

/**
* Class that defines an Ellipse.
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
68 changes: 53 additions & 15 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 Expand Up @@ -79,18 +79,18 @@ export class Rectangle {
*/
public overlaps(otherShape: Rectangle | Ellipse): boolean {
if (otherShape instanceof Rectangle) {
const thisCorners = this.getCorners();
const otherCorners = otherShape.getCorners();

//Overlap occurs if either of the corners of either shape are within the other
for (let i = 0; i < 4; i++) {
if (
this.containsPoint(otherCorners[i]) ||
otherShape.containsPoint(thisCorners[i])
) {
return true;
}
if (
this.checkHorizontalEdgeOverlap(otherShape) &&
otherShape.checkVerticalEdgeOverlap(this)
) {
return true;
} else if (
this.checkVerticalEdgeOverlap(otherShape) &&
otherShape.checkHorizontalEdgeOverlap(this)
) {
return true;
}

return false;
} else {
for (let i = 0; i < 4; i++) {
Expand All @@ -102,6 +102,44 @@ export class Rectangle {
}
}

/**
* Checks if any of the horizontal edges of the other rectangle lie within the horizontal
* boundaries of this rectangle
* @param otherRect The other rectangle
* @returns True, if the other edges lie within this boundary. Else, false
*/
private checkHorizontalEdgeOverlap(otherRect: Rectangle): boolean {
const thisCorners = this.getCorners();
const otherCorners = otherRect.getCorners();

if (thisCorners[0].y <= otherCorners[0].y && thisCorners[2].y >= otherCorners[0].y) {
return true;
} else if (thisCorners[0].y <= otherCorners[2].y && thisCorners[2].y >= otherCorners[2].y) {
return true;
}

return false;
}

/**
* Checks if any of the vertical edges of the other rectangle lie within the vertical
* boundaries of this rectangle
* @param otherRect The other rectangle
* @returns True, if the other edges lie within this boundary. Else, false
*/
private checkVerticalEdgeOverlap(otherRect: Rectangle): boolean {
const thisCorners = this.getCorners();
const otherCorners = otherRect.getCorners();

if (thisCorners[0].x <= otherCorners[0].x && thisCorners[1].x >= otherCorners[0].x) {
return true;
} else if (thisCorners[0].x <= otherCorners[1].x && thisCorners[1].x >= otherCorners[1].x) {
return true;
}

return false;
}

/**
* Method that checks whether another shape is within this rectangle.
* @param otherShape The shape that might be within this rectangle.
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();
}
Loading

0 comments on commit b936810

Please sign in to comment.