Home Reference Source

scripts/util/ScreenOverdraw.js

"use strict";
export const SVG_URL = 'http://www.w3.org/2000/svg';

import * as util from './util.js';
import * as vectormath from './vectormath.js';
import * as ui_base from '../core/ui_base.js';
import * as ui from '../core/ui.js';
import * as math from './math.js';

const Vector2 = vectormath.Vector2;

export class CanvasOverdraw extends ui_base.UIBase {
  constructor() {
    super();

    this.canvas = document.createElement("canvas");
    this.shadow.appendChild(this.canvas);
    this.g = this.canvas.getContext("2d");

    this.screen = undefined;
    this.shapes = [];
    this.otherChildren = []; //non-svg elements
    this.font = undefined;

    let style = document.createElement("style")
    style.textContent = `
      .overdrawx {
        pointer-events : none;
      }
    `;

    this.shadow.appendChild(style);
  }

  static define() {
    return {
      tagname : 'screen-overdraw-canvas-x'
    }
  }

  startNode(node, screen) {
    if (screen) {
      this.screen = screen;
      this.ctx = screen.ctx;
    }

    if (!this.parentNode) {
      node.appendChild(this);
    }

    this.style["display"] = "float";
    this.style["z-index"] = this.zindex_base;

    this.style["position"] = "absolute";
    this.style["left"] = "0px";
    this.style["top"] = "0px";

    this.style["width"] = "100%" //screen.size[0] + "px";
    this.style["height"] = "100%" //screen.size[1] + "px";

    this.style["pointer-events"] = "none";

    this.svg = document.createElementNS(SVG_URL, "svg");
    this.svg.style["width"] = "100%";
    this.svg.style["height"] = "100%";

    this.svg.style["pointer-events"] = "none";

    this.shadow.appendChild(this.svg);
    //this.style["background-color"] = "green";
  }

  start(screen) {
    this.screen = screen;
    this.ctx = screen.ctx;

    screen.parentNode.appendChild(this);

    this.style["display"] = "float";
    this.style["z-index"] = this.zindex_base;

    this.style["position"] = "absolute";
    this.style["left"] = "0px";
    this.style["top"] = "0px";

    this.style["width"] = screen.size[0] + "px";
    this.style["height"] = screen.size[1] + "px";

    this.style["pointer-events"] = "none";

    this.svg = document.createElementNS(SVG_URL, "svg");
    this.svg.style["width"] = "100%";
    this.svg.style["height"] = "100%";

    this.shadow.appendChild(this.svg);

    //this.style["background-color"] = "green";
  }
}

export class Overdraw extends ui_base.UIBase {
  constructor() {
    super();

    this.visibleToPick = false;

    this.screen = undefined;
    this.shapes = [];
    this.otherChildren = []; //non-svg elements
    this.font = undefined;

    let style = document.createElement("style")
    style.textContent = `
      .overdrawx {
        pointer-events : none;
      }
    `;

    this.shadow.appendChild(style);

    this.zindex_base = 1000;
  }

  startNode(node, screen) {
    if (screen) {
      this.screen = screen;
      this.ctx = screen.ctx;
    }

    if (!this.parentNode) {
      node.appendChild(this);
    }

    this.style["z-index"] = this.zindex_base;

    this.style["position"] = "relative";
    //this.style["left"] = "0px";
    //this.style["top"] = "0px";
    this.style["margin"] = this.style["padding"] = "0px";

    this.style["width"] = "100%" //screen.size[0] + "px";
    this.style["height"] = "100%" //screen.size[1] + "px";

    this.style["pointer-events"] = "none";

    this.svg = document.createElementNS(SVG_URL, "svg");
    this.svg.style["width"] = "100%";
    this.svg.style["height"] = "100%";

    this.svg.style["pointer-events"] = "none";

    this.shadow.appendChild(this.svg);
    //this.style["background-color"] = "green";
  }

  start(screen) {
    this.screen = screen;
    this.ctx = screen.ctx;

    screen.parentNode.appendChild(this);

    this.style["display"] = "float";
    this.style["z-index"] = this.zindex_base;

    this.style["position"] = "absolute";
    this.style["left"] = "0px";
    this.style["top"] = "0px";

    this.style["width"] = screen.size[0] + "px";
    this.style["height"] = screen.size[1] + "px";

    this.style["pointer-events"] = "none";

    this.svg = document.createElementNS(SVG_URL, "svg");
    this.svg.style["width"] = "100%";
    this.svg.style["height"] = "100%";

    this.shadow.appendChild(this.svg);

    //this.style["background-color"] = "green";
  }

  clear() {
    for (let child of util.list(this.svg.childNodes)) {
      child.remove();
    }

    for (let child of this.otherChildren) {
      child.remove();
    }

    this.otherChildren.length = 0;
  }

  drawTextBubbles(texts, cos, colors) {
    let boxes = [];
    let elems = [];

    let cent = new Vector2();

    for (let i=0; i<texts.length; i++) {
      let co = cos[i];
      let text = texts[i];
      let color;

      if (colors !== undefined) {
        color = colors[i];
      }

      cent.add(co);
      let box = this.text(texts[i], co[0], co[1], {color : color});

      boxes.push(box);
      let font = box.style["font"];
      let pat = /[0-9]+px/;
      let size = font.match(pat)[0];

      //console.log("size", size);

      if (size === undefined) {
        size = this.getDefault("DefaultText").size;
      } else {
        size = ui_base.parsepx(size);
      }

      //console.log(size);
      let tsize = ui_base.measureTextBlock(this, text, undefined, undefined, size, font);

      box.minsize = [
        ~~tsize.width,
        ~~tsize.height
      ];

      let pad = ui_base.parsepx(box.style["padding"]);

      box.minsize[0] += pad*2;
      box.minsize[1] += pad*2;

      let x = ui_base.parsepx(box.style["left"]);
      let y = ui_base.parsepx(box.style["top"]);

      box.grads = new Array(4);
      box.params = [x, y, box.minsize[0], box.minsize[1]];
      box.startpos = new Vector2([x, y]);

      box.setCSS = function() {
        this.style["padding"] = "0px";
        this.style["margin"] = "0px";
        this.style["left"] = ~~this.params[0] + "px";
        this.style["top"] = ~~this.params[1] + "px";
        this.style["width"] = ~~this.params[2] + "px";
        this.style["height"] = ~~this.params[3] + "px";
      };

      box.setCSS();
      //console.log(box.params);
      elems.push(box);
    }

    if (boxes.length === 0) {
      return;
    }

    cent.mulScalar(1.0 / boxes.length);

    function error() {
      let p1 = [0, 0], p2 = [0, 0];
      let s1 = [0, 0], s2 = [0, 0];

      let ret = 0.0;

      for (let box1 of boxes) {
        for (let box2 of boxes) {
          if (box2 === box1) {
            continue;
          }

          s1[0] = box1.params[2];
          s1[1] = box1.params[3];
          s2[0] = box2.params[2];
          s2[1] = box2.params[3];

          let overlap = math.aabb_overlap_area(box1.params, s1, box2.params, s2);
          ret += overlap;
        }

        ret += box1.startpos.vectorDistance(box1.params)*0.25;
      }

      return ret;
    }

    function solve() {
      //console.log("ERROR", error());
      let r1 = error();
      if (r1 === 0.0) {
        return;
      }

      let df = 0.0001;
      let totgs = 0.0;

      for (let box of boxes) {
        for (let i=0; i<box.params.length; i++) {
          let orig = box.params[i];
          box.params[i] += df;
          let r2 = error();
          box.params[i] = orig;

          box.grads[i] = (r2 - r1) / df;
          totgs += box.grads[i]**2;
        }
      }

      if (totgs === 0.0) {
        return;
      }

      r1 /= totgs;
      let k = 0.4;

      for (let box of boxes) {
        for (let i = 0; i < box.params.length; i++) {
          box.params[i] += -r1*box.grads[i]*k;
        }

        box.params[2] = Math.max(box.params[2], box.minsize[0]);
        box.params[3] = Math.max(box.params[3], box.minsize[1]);

        box.setCSS();
      }
    }

    for (let i=0; i<15; i++) {
      solve();
    }

    for (let box of boxes) {
      elems.push(this.line(box.startpos, box.params));
    }

    return elems;
  }

  text(text, x, y, args={}) {
    args = Object.assign({}, args);

    if (args.font === undefined) {
      if (this.font !== undefined)
        args.font = this.font;
      else
        args.font = this.getDefault("DefaultText").genCSS();
    }

    if (!args["background-color"]) {
      args["background-color"] = "rgba(75, 75, 75, 0.75)";
    }

    args.color = args.color ? args.color : "white";
    if (typeof args.color === "object") {
      args.color = ui_base.color2css(args.color);
    }

    args["padding"] = args["padding"] === undefined ? "5px" : args["padding"];
    args["border-color"] = args["border-color"] ? args["border-color"] : "grey";
    args["border-radius"] = args["border-radius"] ? args["border-radius"] : "25px";
    args["border-width"] = args["border-width"] !== undefined ? args["border-width"] : "2px";

    if (typeof args["border-width"] === "number") {
      args["border-width"] = "" + args["border-width"] + "px";
    }
    if (typeof args["border-radius"] === "number") {
      args["border-radius"] = "" + args["border-radius"] + "px";
    }

    //not sure I need SVG for this. . .
    let box = document.createElement("div")

    box.setAttribute("class", "overdrawx");

    box.style["position"] = "fixed";
    box.style["width"] = "min-contents";
    box.style["height"] = "min-contents";
    box.style["border-width"] = args["border-width"]
    box.style["border-radius"] = "25px";
    box.style["pointer-events"] = "none";
    box.style["z-index"] = this.zindex_base + 1;
    box.style["background-color"] = args["background-color"];
    box.style["padding"] = args["padding"];

    box.style["left"] = x + "px";
    box.style["top"] = y + "px";

    box.style["display"] = "flex";
    box.style["justify-content"] = "center";
    box.style["align-items"] = "center";

    box.innerText = text;
    box.style["font"] = args.font;
    box.style["color"] = args.color;

    this.otherChildren.push(box);
    this.shadow.appendChild(box);

    return box;
  }

  circle(p, r, stroke="black", fill="none") {
    let circle = document.createElementNS(SVG_URL, "circle");
    circle.setAttribute("cx", p[0]);
    circle.setAttribute("cy", p[1]);
    circle.setAttribute("r", r);

    if (fill) {
      circle.setAttribute("style", `stroke:${stroke};stroke-width:2;fill:${fill}`);
    } else {
      circle.setAttribute("style", `stroke:${stroke};stroke-width:2`);
    }

    this.svg.appendChild(circle);

    return circle;
  }

  line(v1, v2, color="black") {
    let line = document.createElementNS(SVG_URL, "line");
    line.setAttribute("x1", v1[0]);
    line.setAttribute("y1", v1[1]);
    line.setAttribute("x2", v2[0]);
    line.setAttribute("y2", v2[1]);
    line.setAttribute("style", `stroke:${color};stroke-width:2`);

    this.svg.appendChild(line);
    return line;
  }

  rect(p, size, color="black") {
    let line = document.createElementNS(SVG_URL, "rect");
    line.setAttribute("x", p[0]);
    line.setAttribute("y", p[1]);
    line.setAttribute("width", size[0]);
    line.setAttribute("height", size[1]);
    line.setAttribute("style", `fill:${color};stroke-width:2`);

    line.setColor = (color) => {
      line.setAttribute("style", `fill:${color};stroke-width:2`);
    };

    this.svg.appendChild(line);
    return line;
  }

  end() {
    this.clear();
    this.remove();
  }

  static define() {return {
    tagname : "overdraw-x",
    style   : "overdraw"
  };}
}

ui_base.UIBase.internalRegister(Overdraw);