Added some visualization features and documentation
This commit is contained in:
@ -237,6 +237,7 @@
|
||||
<div class="flex flex-col">
|
||||
<a rel="noopener noreferrer" id="docs_synchronisation" class="doc_subheader">Synchronisation</a>
|
||||
<a rel="noopener noreferrer" id="docs_oscilloscope" class="doc_subheader">Oscilloscope</a>
|
||||
<a rel="noopener noreferrer" id="docs_visualization" class="doc_header">Visualization</a>
|
||||
<a rel="noopener noreferrer" id="docs_bonus" class="doc_header">Bonus/Trivia</a>
|
||||
<a rel="noopener noreferrer" id="docs_about" class="doc_header">About Topos</a>
|
||||
</div>
|
||||
|
||||
561
src/API.ts
561
src/API.ts
@ -73,6 +73,36 @@ export async function loadSamples() {
|
||||
]);
|
||||
}
|
||||
|
||||
export type ShapeObject = {
|
||||
x: number,
|
||||
y: number,
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
radius: number,
|
||||
width: number,
|
||||
height: number,
|
||||
fillStyle: string,
|
||||
secondary: string,
|
||||
strokeStyle: string,
|
||||
rotate: number,
|
||||
points: number,
|
||||
outerRadius: number,
|
||||
rotation: number,
|
||||
eyeSize: number,
|
||||
happiness: number,
|
||||
slices: number,
|
||||
gap: number,
|
||||
font: string,
|
||||
fontSize: number,
|
||||
text: string,
|
||||
filter: string,
|
||||
url: string,
|
||||
curve: number,
|
||||
curves: number,
|
||||
}
|
||||
|
||||
export class UserAPI {
|
||||
/**
|
||||
* The UserAPI class is the interface between the user's code and the backend. It provides
|
||||
@ -2191,7 +2221,7 @@ export class UserAPI {
|
||||
// Canvas Functions
|
||||
// =============================================================
|
||||
|
||||
public clear = (): void => {
|
||||
public clear = (): boolean => {
|
||||
/**
|
||||
* Clears the canvas after a given timeout.
|
||||
* @param timeout - The timeout in seconds
|
||||
@ -2199,27 +2229,44 @@ export class UserAPI {
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
public width = (): number => {
|
||||
public w = (): number => {
|
||||
/**
|
||||
* Returns the width of the canvas.
|
||||
* @returns The width of the canvas
|
||||
*/
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
return canvas.width;
|
||||
return canvas.clientWidth;
|
||||
}
|
||||
|
||||
public height = (): number => {
|
||||
public h = (): number => {
|
||||
/**
|
||||
* Returns the height of the canvas.
|
||||
* @returns The height of the canvas
|
||||
*/
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
return canvas.height;
|
||||
return canvas.clientHeight;
|
||||
}
|
||||
|
||||
public background = (color: string|number, ...gb:number[]): void => {
|
||||
public hc = (): number => {
|
||||
/**
|
||||
* Returns the center y-coordinate of the canvas.
|
||||
* @returns The center y-coordinate of the canvas
|
||||
*/
|
||||
return this.h() / 2;
|
||||
}
|
||||
|
||||
public wc = (): number => {
|
||||
/**
|
||||
* Returns the center x-coordinate of the canvas.
|
||||
* @returns The center x-coordinate of the canvas
|
||||
*/
|
||||
return this.w() / 2;
|
||||
}
|
||||
|
||||
public background = (color: string|number, ...gb:number[]): boolean => {
|
||||
/**
|
||||
* Set background color of the canvas.
|
||||
* @param color - The color to set. String or 3 numbers representing RGB values.
|
||||
@ -2229,7 +2276,9 @@ export class UserAPI {
|
||||
if(typeof color === "number") color = `rgb(${color},${gb[0]},${gb[1]})`;
|
||||
ctx.fillStyle = color;
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
return true;
|
||||
}
|
||||
bg = this.background;
|
||||
|
||||
public linearGradient = (x1: number, y1: number, x2: number, y2: number, ...stops: (number|string)[]) => {
|
||||
/**
|
||||
@ -2293,37 +2342,112 @@ export class UserAPI {
|
||||
return gradient;
|
||||
}
|
||||
|
||||
public draw = (func: Function): void => {
|
||||
public draw = (func: Function): boolean => {
|
||||
/**
|
||||
* Draws on the canvas.
|
||||
* @param func - The function to execute
|
||||
*/
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
func(ctx);
|
||||
if(typeof func === "string") {
|
||||
this.drawText (func);
|
||||
} else {
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
func(ctx);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public circle = (
|
||||
x: number,
|
||||
y: number,
|
||||
radius: number,
|
||||
fillStyle: string,
|
||||
): void => {
|
||||
public balloid = (
|
||||
curves: number|ShapeObject = 6,
|
||||
radius: number = this.hc()/2,
|
||||
curve: number = 1.5,
|
||||
fillStyle: string = "white",
|
||||
secondary: string = "black",
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
): boolean => {
|
||||
if(typeof curves === "object") {
|
||||
fillStyle = curves.fillStyle || "white";
|
||||
x = curves.x || this.wc();
|
||||
y = curves.y || this.hc();
|
||||
curve = curves.curve || 1.5;
|
||||
radius = curves.radius || this.hc()/2;
|
||||
curves = curves.curves || 6;
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
|
||||
// Draw the shape using quadratic Bézier curves
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fill();
|
||||
};
|
||||
|
||||
if (curves === 0) {
|
||||
// Draw a circle if curves = 0
|
||||
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
} else if (curves === 1) {
|
||||
// Draw a single curve (ellipse) if curves = 1
|
||||
ctx.ellipse(x, y, radius*0.8, (radius* curve)*0.7, 0, 0, 2 * Math.PI);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
} else if (curves === 2) {
|
||||
// Draw a shape with two symmetric curves starting from the top and meeting at the bottom
|
||||
ctx.moveTo(x, y - radius);
|
||||
|
||||
public triangular = (
|
||||
x: number,
|
||||
y: number,
|
||||
radius: number,
|
||||
fillStyle: string,
|
||||
rotate: number
|
||||
): void => {
|
||||
// First curve
|
||||
ctx.quadraticCurveTo(x + radius * curve, y, x, y + radius);
|
||||
|
||||
// Second symmetric curve
|
||||
ctx.quadraticCurveTo(x - radius * curve, y, x, y - radius);
|
||||
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
} else {
|
||||
// Draw the curved shape with the specified number of curves
|
||||
ctx.moveTo(x, y - radius);
|
||||
let points = [];
|
||||
for (let i = 0; i < curves; i++) {
|
||||
const startAngle = (i / curves) * 2 * Math.PI;
|
||||
const endAngle = startAngle + (2 * Math.PI) / curves;
|
||||
|
||||
const controlX = x + radius * curve * Math.cos(startAngle + Math.PI / curves);
|
||||
const controlY = y + radius * curve * Math.sin(startAngle + Math.PI / curves);
|
||||
points.push([x + radius * Math.cos(startAngle), y + radius * Math.sin(startAngle)]);
|
||||
ctx.moveTo(x + radius * Math.cos(startAngle), y + radius * Math.sin(startAngle));
|
||||
ctx.quadraticCurveTo(controlX, controlY, x + radius * Math.cos(endAngle), y + radius * Math.sin(endAngle));
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.fillStyle = secondary;
|
||||
// Form the shape from points with straight lines and fill it
|
||||
ctx.moveTo(points[0][0], points[0][1]);
|
||||
for(let point of points) ctx.lineTo(point[0], point[1]);
|
||||
// Close and fill
|
||||
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
public equilateral = (
|
||||
radius: number|ShapeObject = this.hc()/3,
|
||||
fillStyle: string = "white",
|
||||
rotate: number = 0,
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
): boolean => {
|
||||
if(typeof radius === "object") {
|
||||
fillStyle = radius.fillStyle || "white";
|
||||
x = radius.x || this.wc();
|
||||
y = radius.y || this.hc();
|
||||
rotate = radius.rotate || 0;
|
||||
radius = radius.radius || this.hc()/3;
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
@ -2337,20 +2461,205 @@ export class UserAPI {
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
public star = (
|
||||
x: number,
|
||||
y: number,
|
||||
radius: number,
|
||||
points: number = 5,
|
||||
public triangular = (
|
||||
width: number|ShapeObject = this.hc()/3,
|
||||
height: number = this.hc()/3,
|
||||
fillStyle: string = "white",
|
||||
outerRadius: number = 1.0,
|
||||
rotate: number = 0,
|
||||
): void => {
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
): boolean => {
|
||||
if(typeof width === "object") {
|
||||
fillStyle = width.fillStyle || "white";
|
||||
x = width.x || this.wc();
|
||||
y = width.y || this.hc();
|
||||
rotate = width.rotate || 0;
|
||||
height = width.height || this.hc()/3;
|
||||
width = width.width || this.hc()/3;
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, -height);
|
||||
ctx.lineTo(width, height);
|
||||
ctx.lineTo(-width, height);
|
||||
ctx.closePath();
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
return true;
|
||||
}
|
||||
pointy = this.triangular;
|
||||
|
||||
public ball = (
|
||||
radius: number|ShapeObject = this.hc()/3,
|
||||
fillStyle: string = "white",
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
): boolean => {
|
||||
if(typeof radius === "object") {
|
||||
fillStyle = radius.fillStyle || "white";
|
||||
x = radius.x || this.wc();
|
||||
y = radius.y || this.hc();
|
||||
radius = radius.radius || this.hc()/3;
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fill();
|
||||
return true;
|
||||
}
|
||||
circle = this.ball;
|
||||
|
||||
public donut = (
|
||||
slices: number = 3,
|
||||
eaten: number = 0,
|
||||
radius: number | ShapeObject = this.hc() / 3,
|
||||
hole: number = this.hc() / 12,
|
||||
fillStyle: string = "white",
|
||||
secondary: string = "black",
|
||||
stroke: string = "black",
|
||||
rotate: number = 0,
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
): boolean => {
|
||||
if (typeof radius === "object") {
|
||||
fillStyle = radius.fillStyle || "white";
|
||||
x = radius.x || this.wc();
|
||||
y = radius.y || this.hc();
|
||||
rotate = radius.rotate || 0;
|
||||
slices = radius.slices || 3;
|
||||
radius = radius.radius || this.hc() / 3;
|
||||
}
|
||||
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
|
||||
// Draw slices as arcs
|
||||
const totalSlices = slices;
|
||||
const sliceAngle = (2 * Math.PI) / totalSlices;
|
||||
for (let i = 0; i < totalSlices; i++) {
|
||||
const startAngle = i * sliceAngle;
|
||||
const endAngle = (i + 1) * sliceAngle;
|
||||
|
||||
// Calculate the position of the outer arc
|
||||
const outerStartX = hole * Math.cos(startAngle);
|
||||
const outerStartY = hole * Math.sin(startAngle);
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(outerStartX, outerStartY);
|
||||
ctx.arc(0, 0, radius, startAngle, endAngle);
|
||||
ctx.arc(0, 0, hole, endAngle, startAngle, true);
|
||||
ctx.closePath();
|
||||
|
||||
// Fill and stroke the slices with the specified fill style
|
||||
if (i < slices - eaten) {
|
||||
// Regular slices are white
|
||||
ctx.fillStyle = fillStyle;
|
||||
} else {
|
||||
// Missing slices are black
|
||||
ctx.fillStyle = secondary;
|
||||
}
|
||||
ctx.lineWidth = 2;
|
||||
ctx.fill();
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
return true;
|
||||
};
|
||||
|
||||
public pie = (
|
||||
slices: number = 3,
|
||||
eaten: number = 0,
|
||||
radius: number | ShapeObject = this.hc() / 3,
|
||||
fillStyle: string = "white",
|
||||
secondary: string = "black",
|
||||
stroke: string = "black",
|
||||
rotate: number = 0,
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
): boolean => {
|
||||
if (typeof radius === "object") {
|
||||
fillStyle = radius.fillStyle || "white";
|
||||
x = radius.x || this.wc();
|
||||
y = radius.y || this.hc();
|
||||
rotate = radius.rotate || 0;
|
||||
slices = radius.slices || 3;
|
||||
radius = radius.radius || this.hc() / 3;
|
||||
}
|
||||
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
|
||||
// Draw slices as arcs
|
||||
const totalSlices = slices;
|
||||
const sliceAngle = ((2 * Math.PI) / totalSlices);
|
||||
for (let i = 0; i < totalSlices; i++) {
|
||||
const startAngle = i * sliceAngle;
|
||||
const endAngle = (i + 1) * sliceAngle;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.arc(0, 0, radius, startAngle, endAngle);
|
||||
ctx.closePath();
|
||||
|
||||
// Fill and stroke the slices with the specified fill style
|
||||
if (i < slices - eaten) {
|
||||
// Regular slices are white
|
||||
ctx.fillStyle = fillStyle;
|
||||
} else {
|
||||
// Missing slices are black
|
||||
ctx.fillStyle = secondary;
|
||||
}
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeStyle = stroke;
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
return true;
|
||||
};
|
||||
|
||||
|
||||
public star = (
|
||||
points: number|ShapeObject = 5,
|
||||
radius: number = this.hc()/3,
|
||||
fillStyle: string = "white",
|
||||
rotate: number = 0,
|
||||
outerRadius: number = radius/100,
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
): boolean => {
|
||||
if(typeof points === "object") {
|
||||
radius = points.radius || this.hc()/3;
|
||||
fillStyle = points.fillStyle || "white";
|
||||
x = points.x || this.wc();
|
||||
y = points.y || this.hc();
|
||||
rotate = points.rotate || 0;
|
||||
outerRadius = points.outerRadius || radius/100;
|
||||
points = points.points || 5;
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
if(points<1) return this.circle(x, y, radius+outerRadius, fillStyle);
|
||||
if(points==1) return this.triangular(x, y, radius, fillStyle, 0);
|
||||
if(points<1) return this.ball(radius, fillStyle, x, y);
|
||||
if(points==1) return this.equilateral(radius, fillStyle, 0, x, y);
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
@ -2367,34 +2676,59 @@ export class UserAPI {
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
return true;
|
||||
};
|
||||
|
||||
public stroke = (
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
fillStyle: string,
|
||||
width: number = 1,
|
||||
): void => {
|
||||
width: number|ShapeObject = 1,
|
||||
strokeStyle: string = "white",
|
||||
rotate: number = 0,
|
||||
x1: number = this.wc()-this.wc()/10,
|
||||
y1: number = this.hc(),
|
||||
x2: number = this.wc()+this.wc()/5,
|
||||
y2: number = this.hc(),
|
||||
): boolean => {
|
||||
if(typeof width === "object") {
|
||||
strokeStyle = width.strokeStyle || "white";
|
||||
x1 = width.x1 || this.wc()-this.wc()/10;
|
||||
y1 = width.y1 || this.hc();
|
||||
x2 = width.x2 || this.wc()+this.wc()/5;
|
||||
y2 = width.y2 || this.hc();
|
||||
rotate = width.rotate || 0;
|
||||
width = width.width || 1;
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
ctx.translate(x1, y1);
|
||||
ctx.rotate((rotate * Math.PI) / 180);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(x1, y1);
|
||||
ctx.lineTo(x2, y2);
|
||||
ctx.strokeStyle = fillStyle;
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(x2-x1, y2-y1);
|
||||
ctx.lineWidth = width;
|
||||
ctx.strokeStyle = strokeStyle;
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
return true;
|
||||
};
|
||||
|
||||
public rectangle = (
|
||||
x: number,
|
||||
y: number,
|
||||
width: number,
|
||||
height: number,
|
||||
fillStyle: string,
|
||||
public box = (
|
||||
width: number|ShapeObject = this.wc()/4,
|
||||
height: number = this.wc()/4,
|
||||
fillStyle: string = "white",
|
||||
rotate: number = 0,
|
||||
): void => {
|
||||
x: number = this.wc()-this.wc()/8,
|
||||
y: number = this.hc()-this.hc()/8,
|
||||
): boolean => {
|
||||
if(typeof width === "object") {
|
||||
fillStyle = width.fillStyle || "white";
|
||||
x = width.x || this.wc()-this.wc()/4;
|
||||
y = width.y || this.hc()-this.hc()/2;
|
||||
rotate = width.rotate || 0;
|
||||
height = width.height || this.wc()/4;
|
||||
width = width.width || this.wc()/4;
|
||||
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
@ -2403,17 +2737,27 @@ export class UserAPI {
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fillRect(0, 0, width, height);
|
||||
ctx.restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
public smiley = (
|
||||
x: number,
|
||||
y: number,
|
||||
radius: number,
|
||||
fillStyle: string,
|
||||
eyeSize: number = 1.0,
|
||||
happiness: number = 0.0,
|
||||
rotation: number = 0
|
||||
): void => {
|
||||
happiness: number|ShapeObject = 2.0,
|
||||
radius: number = this.hc()/3,
|
||||
eyeSize: number = 3.0,
|
||||
fillStyle: string = "yellow",
|
||||
rotation: number = 0,
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
): boolean => {
|
||||
if(typeof happiness === "object") {
|
||||
fillStyle = happiness.fillStyle || "yellow";
|
||||
x = happiness.x || this.wc();
|
||||
y = happiness.y || this.hc();
|
||||
rotation = happiness.rotation || 0;
|
||||
eyeSize = happiness.eyeSize || 3.0;
|
||||
radius = happiness.radius || this.hc()/3;
|
||||
happiness = happiness.happiness || 2.0;
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
// Map the rotation value to an angle within the range of -PI to PI
|
||||
@ -2468,9 +2812,104 @@ export class UserAPI {
|
||||
ctx.strokeStyle = "black";
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
drawText = (
|
||||
text: string|ShapeObject,
|
||||
fontSize: number = 24,
|
||||
rotation: number = 0,
|
||||
font: string = "Arial",
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
fillStyle: string = "white",
|
||||
filter: string = "none",
|
||||
): boolean => {
|
||||
if(typeof text === "object") {
|
||||
fillStyle = text.fillStyle || "white";
|
||||
x = text.x || this.wc();
|
||||
y = text.y || this.hc();
|
||||
rotation = text.rotation || 0;
|
||||
font = text.font || "Arial";
|
||||
fontSize = text.fontSize || 24;
|
||||
filter = text.filter || "none";
|
||||
text = text.text || "";
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate((rotation * Math.PI) / 180);
|
||||
ctx.filter = filter;
|
||||
ctx.font = `${fontSize}px ${font}`;
|
||||
ctx.fillStyle = fillStyle;
|
||||
ctx.fillText(text, 0, 0);
|
||||
ctx.restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
image = (
|
||||
url: string|ShapeObject,
|
||||
width: number = this.wc()/2,
|
||||
height: number = this.hc()/2,
|
||||
rotation: number = 0,
|
||||
x: number = this.wc(),
|
||||
y: number = this.hc(),
|
||||
filter: string = "none",
|
||||
): boolean => {
|
||||
if(typeof url === "object") {
|
||||
if(!url.url) return true;
|
||||
x = url.x || this.wc();
|
||||
y = url.y || this.hc();
|
||||
rotation = url.rotation || 0;
|
||||
width = url.width || 100;
|
||||
height = url.height || 100;
|
||||
filter = url.filter || "none";
|
||||
url = url.url || "";
|
||||
}
|
||||
const canvas: HTMLCanvasElement = this.app.interface.drawings as HTMLCanvasElement;
|
||||
const ctx = canvas.getContext("2d")!;
|
||||
ctx.save();
|
||||
ctx.translate(x, y);
|
||||
ctx.rotate((rotation * Math.PI) / 180);
|
||||
ctx.filter = filter;
|
||||
const image = new Image();
|
||||
image.src = url;
|
||||
ctx.drawImage(image, -width/2, -height/2, width, height);
|
||||
ctx.restore();
|
||||
return true;
|
||||
}
|
||||
|
||||
randomChar = (length: number= 1, min: number = 0, max: number = 65536): string => {
|
||||
return Array.from(
|
||||
|
||||
{ length }, () => String.fromCodePoint(Math.floor(Math.random() * (max - min) + min))
|
||||
).join('');
|
||||
}
|
||||
|
||||
randomFromRange = (min: number, max: number): string => {
|
||||
const codePoint = Math.floor(Math.random() * (max - min) + min);
|
||||
return String.fromCodePoint(codePoint);
|
||||
};
|
||||
|
||||
emoji = (n: number = 1): string => {
|
||||
return this.randomChar(n, 0x1f600, 0x1f64f);
|
||||
};
|
||||
|
||||
food = (n: number = 1): string => {
|
||||
return this.randomChar(n, 0x1f32d, 0x1f37f);
|
||||
};
|
||||
|
||||
animals = (n: number = 1): string => {
|
||||
return this.randomChar(n, 0x1f400, 0x1f4d3);
|
||||
};
|
||||
|
||||
expressions = (n: number = 1): string => {
|
||||
return this.randomChar(n, 0x1f910, 0x1f92f);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// =============================================================
|
||||
// OSC Functions
|
||||
|
||||
@ -17,6 +17,7 @@ import { oscilloscope } from "./documentation/more/oscilloscope";
|
||||
import { synchronisation } from "./documentation/more/synchronisation";
|
||||
import { about } from "./documentation/more/about";
|
||||
import { bonus } from "./documentation/more/bonus";
|
||||
import { visualization } from "./documentation/more/visualization";
|
||||
import { chaining } from "./documentation/patterns/chaining";
|
||||
import { interaction } from "./documentation/basics/interaction";
|
||||
import { time } from "./documentation/learning/time/time";
|
||||
@ -117,6 +118,7 @@ export const documentation_factory = (application: Editor) => {
|
||||
audio_basics: audio_basics(application),
|
||||
synchronisation: synchronisation(application),
|
||||
bonus: bonus(application),
|
||||
visualization: visualization(application),
|
||||
sample_list: sample_list(application),
|
||||
sample_banks: sample_banks(application),
|
||||
loading_samples: loading_samples(application),
|
||||
|
||||
@ -559,6 +559,7 @@ export const installInterfaceLogic = (app: Editor) => {
|
||||
"oscilloscope",
|
||||
"sample_list",
|
||||
"loading_samples",
|
||||
"visualization",
|
||||
].forEach((e) => {
|
||||
let name = `docs_` + e;
|
||||
|
||||
|
||||
197
src/documentation/more/visualization.ts
Normal file
197
src/documentation/more/visualization.ts
Normal file
@ -0,0 +1,197 @@
|
||||
import { type Editor } from "../../main";
|
||||
import { key_shortcut, makeExampleFactory } from "../../Documentation";
|
||||
|
||||
export const visualization = (application: Editor): string => {
|
||||
const makeExample = makeExampleFactory(application);
|
||||
|
||||
return `
|
||||
# Vizualisation
|
||||
|
||||
While Topos is mainly being developed as a live coding environment for algorithmic music composition, it also includes some features for live code visualizatoins. This section will introduce you to these features.
|
||||
|
||||
## Hydra Visual Live Coding
|
||||
|
||||
<div class="mx-12 bg-neutral-600 rounded-lg flex flex-col items-center justify-center">
|
||||
<warning>⚠️ This feature can generate flashing images that could trigger photosensitivity or epileptic seizures. ⚠️ </warning>
|
||||
</div>
|
||||
|
||||
[Hydra](https://hydra.ojack.xyz/?sketch_id=mahalia_1) is a popular live-codable video synthesizer developed by [Olivia Jack](https://ojack.xyz/) and other contributors. It follows an analog synthesizer patching metaphor to encourage live coding complex shaders. Being very easy to use, extremely powerful and also very rewarding to use, Hydra has become a popular choice for adding visuals into a live code performance.
|
||||
|
||||
${makeExample(
|
||||
"Hydra integration",
|
||||
`beat(4) :: hydra.osc(3, 0.5, 2).out()`,
|
||||
false,
|
||||
)}
|
||||
|
||||
Close the documentation to see the effect: ${key_shortcut(
|
||||
"Ctrl+D",
|
||||
)}! **Boom, all shiny!**
|
||||
|
||||
Be careful not to call <ic>hydra</ic> too often as it can impact performances. You can use any rhythmical function like <ic>beat()</ic> function to limit the number of function calls. You can write any Topos code like <ic>[1,2,3].beat()</ic> to bring some life and movement in your Hydra sketches.
|
||||
|
||||
Stopping **Hydra** is simple:
|
||||
|
||||
${makeExample(
|
||||
"Stopping Hydra",
|
||||
`
|
||||
beat(4) :: stop_hydra() // this one
|
||||
beat(4) :: hydra.hush() // or this one
|
||||
`,
|
||||
false,
|
||||
)}
|
||||
|
||||
### Changing the resolution
|
||||
|
||||
You can change Hydra resolution using this simple method:
|
||||
|
||||
${makeExample(
|
||||
"Changing Hydra resolution",
|
||||
`hydra.setResolution(1024, 768)`,
|
||||
false,
|
||||
)}
|
||||
|
||||
### Documentation
|
||||
|
||||
I won't teach Hydra. You can find some great resources directly on the [Hydra website](https://hydra.ojack.xyz/):
|
||||
- [Hydra interactive documentation](https://hydra.ojack.xyz/docs/)
|
||||
- [List of Hydra Functions](https://hydra.ojack.xyz/api/)
|
||||
- [Source code on GitHub](https://github.com/hydra-synth/hydra)
|
||||
|
||||
### The Hydra namespace
|
||||
|
||||
In comparison with the basic Hydra editor, please note that you have to prefix all Hydra functions with <ic>hydra.</ic> to avoid conflicts with Topos functions. For example, <ic>osc()</ic> becomes <ic>hydra.osc()</ic>.
|
||||
|
||||
${makeExample("Hydra namespace", `hydra.voronoi(20).out()`, true)}
|
||||
|
||||
## GIF player
|
||||
|
||||
Topos embeds a small <ic>.gif</ic> picture player with a small API. GIFs are automatically fading out after the given duration. Look at the following example:
|
||||
|
||||
${makeExample(
|
||||
"Playing many gifs",
|
||||
`
|
||||
beat(0.25)::gif({
|
||||
url:v('gif')[$(1)%6], // Any URL will do!
|
||||
opacity: r(0.5, 1), // Opacity (0-1)
|
||||
size:"300px", // CSS size property
|
||||
center:false, // Centering on the screen?
|
||||
filter:'none', // CSS Filter
|
||||
dur: 2, // In beats (Topos unit)
|
||||
rotation: ir(1, 360), // Rotation (in degrees)
|
||||
posX: ir(1,1200), // CSS Horizontal Position
|
||||
posY: ir(1, 800), // CSS Vertical Position
|
||||
`,
|
||||
false,
|
||||
)}
|
||||
|
||||
|
||||
## Canvas live coding
|
||||
|
||||
Documentation in progress! Copy the example and run it separately (Showing sualization examples in the documentation not implemented yet).
|
||||
|
||||
* <ic>draw(f: Function)</ic> - Draws to a canvas with the given function.
|
||||
|
||||
${makeExample(
|
||||
"Drawing to canvas",
|
||||
`
|
||||
beat(0.5) && clear() && draw(context => {
|
||||
context.fillStyle = 'red';
|
||||
|
||||
// Begin the path for the heart shape
|
||||
context.beginPath();
|
||||
const x = wc();
|
||||
const y = hc();
|
||||
context.fillStyle = 'red';
|
||||
|
||||
// Begin the path for the heart shape
|
||||
context.beginPath();
|
||||
|
||||
context.moveTo(x + 125, y + 50);
|
||||
context.bezierCurveTo(x + 75, y, x, y + 75, x + 125, y + 175);
|
||||
context.bezierCurveTo(x + 250, y + 75, x + 175, y, x + 125, y + 50);
|
||||
|
||||
// Fill the heart with red color
|
||||
context.fill();
|
||||
})
|
||||
`,
|
||||
false,
|
||||
)}
|
||||
|
||||
* <ic<image(url, x, y, width, height, rotation)</ic> - Draws an image to a canvas.
|
||||
|
||||
${makeExample(
|
||||
"Image to canvas",
|
||||
`
|
||||
beat(0.5) && clear() && image("http://localhost:8000/topos_frog.svg",200,200+epulse()%15)
|
||||
`,
|
||||
false,
|
||||
)}
|
||||
|
||||
* <ic>clear()</ic> - Clears the canvas.
|
||||
* <ic>background(fill: string)</ic> - Sets the background color, image or gradient.
|
||||
* <ic>w()</ic> - Returns the canvas width.
|
||||
* <ic>h()</ic> - Returns the canvas height.
|
||||
* <ic>wc()</ic> - Returns the center of the canvas width.
|
||||
* <ic>hc()</ic> - Returns the center of the canvas height.
|
||||
|
||||
### Text to canvas
|
||||
|
||||
* <ic>drawText(text, fontSize, rotation, font, x, y)</ic> - Draws text to a canvas.
|
||||
|
||||
${makeExample(
|
||||
"Writing to canvas",
|
||||
`
|
||||
beat(0.5) && clear() && drawText("Hello world!", 100, 0, "Arial", 100, 100)
|
||||
`,
|
||||
false,
|
||||
)}
|
||||
|
||||
* <ic>randomChar(number, min, max)</ic> - Returns a number of random characters from given unicode range.
|
||||
|
||||
${makeExample(
|
||||
"Drawing random characters to canvas",
|
||||
`
|
||||
beat(0.5) && clear() && drawText(randomChar(10,1000,2000),30)
|
||||
`,
|
||||
false,
|
||||
)}
|
||||
|
||||
* <ic>emoji(size)</ic> - Returns a random emojis as text.
|
||||
|
||||
* <ic>animals(size)</ic> - Returns a random animal emojis as text.
|
||||
|
||||
* <ic>food(size)</ic> - Returns a random food emojis as text.
|
||||
|
||||
${makeExample(
|
||||
"Drawing food emojis to canvas",
|
||||
`
|
||||
beat(0.5) && clear() && drawText({x: 10, y: epulse()%700, text: food(50)})
|
||||
`,
|
||||
false,
|
||||
)}
|
||||
|
||||
* <ic>expression(size)</ic> - Returns a random expression emojis as text.
|
||||
|
||||
### Shapes
|
||||
|
||||
In addition to supporting drawing to canvas directly, Topos also include some pre-defined shapes for convenience. The predefined shapes are:
|
||||
* <ic>smiley(happiness, radius, eyes, fill, rotate, x, y)</ic>
|
||||
* <ic>ball(radius,fill,x,y)</ic>
|
||||
* <ic>box(width, height, fill, rotate)</ic>
|
||||
* <ic>pointy(width, height, fill, rotate, x, y)</ic>
|
||||
* <ic>equilateral(radius, fill, rotate, x, y)</ic>
|
||||
* <ic>star(points, radius, fill rotate, outerRadius, x, y</ic>
|
||||
* <ic>pie(slices, eaten, radius, fill, secondary, stroke, rotate, x, y</ic>
|
||||
* <ic>donut(slices, eaten, radius, hole, fill, secondary, stroke, rotate, x, y</ic>
|
||||
* <ic>balloid(petals, radius, curve, fill, secondary, x, y)</ic>
|
||||
* <ic>stroke(width, stroke, rotate, x1, y1, x2, y2)</ic>
|
||||
|
||||
### Gradients
|
||||
|
||||
* <ic>linearGradient(x1, y1, x2, y2, ...stops)</ic> - Creates a <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createLinearGradient">linear gradient</a>.
|
||||
* <ic>radialGradient(x1, y1, r1, x2, y2, r2, ...stops)</ic> - Creates a <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createRadialGradient">radial gradient</a>.
|
||||
* <ic>conicGradient(x, y, angle, ...stops)</ic> - Creates a <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/createConicGradient">conic gradient</a>.
|
||||
|
||||
|
||||
`;
|
||||
};
|
||||
Reference in New Issue
Block a user