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; }