271 lines
5.6 KiB
JavaScript
271 lines
5.6 KiB
JavaScript
import { Ok, Error } from "./gleam.mjs";
|
|
|
|
class PaintCanvas extends HTMLElement {
|
|
// Open an issue if you are in need of any other attributes :)
|
|
static observedAttributes = ["width", "height", "style", "picture"];
|
|
|
|
constructor() {
|
|
super();
|
|
// Create a canvas
|
|
this.canvas = document.createElement("canvas");
|
|
const style = document.createElement("style");
|
|
style.textContent = `
|
|
:host {
|
|
display: inline-block;
|
|
}
|
|
`;
|
|
this.shadow = this.attachShadow({ mode: "open" });
|
|
this.shadow.appendChild(style);
|
|
this.shadow.appendChild(this.canvas);
|
|
this.ctx = this.canvas.getContext("2d");
|
|
}
|
|
|
|
attributeChangedCallback(name, _oldValue, newValue) {
|
|
if (name === "picture") {
|
|
this.picture = newValue;
|
|
return;
|
|
} else if (name === "width") {
|
|
this.width = newValue;
|
|
} else if (name === "height") {
|
|
this.height = newValue;
|
|
}
|
|
}
|
|
|
|
drawPicture() {
|
|
if (!this.pictureString) {
|
|
return;
|
|
}
|
|
|
|
this.ctx.reset();
|
|
const display =
|
|
window.PAINT_STATE[
|
|
"display_on_rendering_context_with_default_drawing_state"
|
|
];
|
|
|
|
display(this.pictureString, this.ctx);
|
|
}
|
|
|
|
set picture(value) {
|
|
this.pictureString = value;
|
|
this.drawPicture();
|
|
}
|
|
|
|
set width(value) {
|
|
this.canvas.width = value;
|
|
this.drawPicture();
|
|
}
|
|
|
|
set height(value) {
|
|
this.canvas.height = value;
|
|
this.drawPicture();
|
|
}
|
|
|
|
get width() {
|
|
return this.canvas.width;
|
|
}
|
|
|
|
get height() {
|
|
return this.canvas.height;
|
|
}
|
|
}
|
|
|
|
export function define_web_component() {
|
|
window.customElements.define("paint-canvas", PaintCanvas);
|
|
}
|
|
|
|
export function get_rendering_context(selector) {
|
|
// TODO: Handle the case where the canvas element is not found.
|
|
return document.querySelector(selector).getContext("2d");
|
|
}
|
|
|
|
export function setup_request_animation_frame(callback) {
|
|
window.requestAnimationFrame((time) => {
|
|
callback(time);
|
|
});
|
|
}
|
|
|
|
export function setup_input_handler(event_name, callback) {
|
|
window.addEventListener(event_name, callback);
|
|
}
|
|
|
|
export function get_key_code(event) {
|
|
return event.keyCode;
|
|
}
|
|
|
|
export function set_global(state, id) {
|
|
if (typeof window.PAINT_STATE == "undefined") {
|
|
window.PAINT_STATE = {};
|
|
}
|
|
window.PAINT_STATE[id] = state;
|
|
}
|
|
|
|
export function get_global(id) {
|
|
if (!window.PAINT_STATE) {
|
|
return new Error(undefined);
|
|
}
|
|
if (!(id in window.PAINT_STATE)) {
|
|
return new Error(undefined);
|
|
}
|
|
return new Ok(window.PAINT_STATE[id]);
|
|
}
|
|
|
|
export function get_width(ctx) {
|
|
return ctx.canvas.clientWidth;
|
|
}
|
|
|
|
export function get_height(ctx) {
|
|
return ctx.canvas.clientHeight;
|
|
}
|
|
|
|
// Based on https://stackoverflow.com/questions/17130395/real-mouse-position-in-canvas
|
|
export function mouse_pos(ctx, event) {
|
|
// Calculate the scaling of the canvas vs its content
|
|
const rect = ctx.canvas.getBoundingClientRect();
|
|
const scaleX = ctx.canvas.width / rect.width;
|
|
const scaleY = ctx.canvas.height / rect.height;
|
|
|
|
return [
|
|
(event.clientX - rect.left) * scaleX,
|
|
(event.clientY - rect.top) * scaleY,
|
|
];
|
|
}
|
|
|
|
// if check_pressed is true, the function will return true if the button was pressed
|
|
// if check_pressed is false, the function will return true if the button was released
|
|
export function check_mouse_button(
|
|
event,
|
|
previous_event,
|
|
button_index,
|
|
check_pressed,
|
|
) {
|
|
let previous_buttons = previous_event?.buttons ?? 0;
|
|
let current_buttons = event.buttons;
|
|
|
|
// ~001 &&
|
|
// 011
|
|
// -----
|
|
// 010 found the newly pressed!
|
|
//
|
|
// 011 &&
|
|
// ~001
|
|
// -----
|
|
// 010 found the newly released!
|
|
if (check_pressed) {
|
|
previous_buttons = ~previous_buttons;
|
|
} else {
|
|
current_buttons = ~current_buttons;
|
|
}
|
|
|
|
let button = previous_buttons & current_buttons & (1 << button_index);
|
|
return !!button;
|
|
}
|
|
|
|
export function reset(ctx) {
|
|
ctx.reset();
|
|
}
|
|
|
|
export function arc(ctx, radius, start, end, fill, stroke) {
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, radius, start, end);
|
|
if (fill) {
|
|
ctx.fill();
|
|
}
|
|
if (stroke) {
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
export function polygon(ctx, points, closed, fill, stroke) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 0);
|
|
let started = false;
|
|
for (const point of points) {
|
|
let x = point[0];
|
|
let y = point[1];
|
|
if (started) {
|
|
ctx.lineTo(x, y);
|
|
} else {
|
|
ctx.moveTo(x, y);
|
|
started = true;
|
|
}
|
|
}
|
|
|
|
if (closed) {
|
|
ctx.closePath();
|
|
}
|
|
|
|
if (fill && closed) {
|
|
ctx.fill();
|
|
}
|
|
|
|
if (stroke) {
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
|
|
export function text(ctx, text, style) {
|
|
ctx.font = style;
|
|
ctx.fillText(text, 0, 0);
|
|
}
|
|
|
|
export function save(ctx) {
|
|
ctx.save();
|
|
}
|
|
|
|
export function restore(ctx) {
|
|
ctx.restore();
|
|
}
|
|
|
|
export function set_fill_colour(ctx, css_colour) {
|
|
ctx.fillStyle = css_colour;
|
|
}
|
|
|
|
export function set_stroke_color(ctx, css_color) {
|
|
ctx.strokeStyle = css_color;
|
|
}
|
|
|
|
export function set_line_width(ctx, width) {
|
|
ctx.lineWidth = width;
|
|
}
|
|
|
|
export function translate(ctx, x, y) {
|
|
ctx.translate(x, y);
|
|
}
|
|
|
|
export function scale(ctx, x, y) {
|
|
ctx.scale(x, y);
|
|
}
|
|
|
|
export function rotate(ctx, radians) {
|
|
ctx.rotate(radians);
|
|
}
|
|
|
|
export function reset_transform(ctx) {
|
|
ctx.resetTransform();
|
|
}
|
|
|
|
export function draw_image(ctx, image, width_px, height_px) {
|
|
ctx.drawImage(image, 0, 0, width_px, height_px);
|
|
}
|
|
|
|
export function image_from_query(selector) {
|
|
return document.querySelector(selector);
|
|
}
|
|
|
|
export function image_from_src(src) {
|
|
const image = new Image();
|
|
image.src = src;
|
|
return image;
|
|
}
|
|
|
|
export function on_image_load(image, callback) {
|
|
if (image.complete) {
|
|
callback();
|
|
} else {
|
|
image.addEventListener("load", callback);
|
|
}
|
|
}
|
|
|
|
export function set_image_smoothing_enabled(ctx, value) {
|
|
ctx.imageSmoothingEnabled = value;
|
|
}
|