Home Reference Source

scripts/widgets/ui_treeview.js

import '../util/ScreenOverdraw.js';
import {UIBase, Icons} from '../core/ui_base.js';
import {Container} from '../core/ui.js';
import {pushModalLight, popModalLight, keymap} from '../path-controller/util/simple_events.js';
import {parsepx} from '../core/ui_theme.js';
import {Vector2} from '../path-controller/util/vectormath.js';
import * as math from '../path-controller/util/math.js';

export class TreeItem extends Container {
  constructor() {
    super();

    this.treeParent = undefined;
    this.treeChildren = [];
    this.treeView = undefined;
    this.treeDepth = 0;

    this.header = this.row();

    this._icon1 = this.header.iconbutton(Icons.TREE_COLLAPSE);
    this._icon1.iconsheet = 0;
    this._icon1.drawButtonBG = false;

    this._icon2 = undefined;

    this._icon1.onclick = () => {
      if (this.opened) {
        this.close();
      }  else {
        this.open();
      }
    }

    this.opened = true;

    this._label = this.header.label("unlabeled");
    this._labelText = "unlabeled";
  }

  set icon(id) {
    if (this._icon2) {
      this._icon2 = id;
    } else {
      this._icon2 = UIBase.createElement("icon-label-x");
      this._icon2.icon = id;
      this._icon2.iconsheet = 0;


      this.header.insert(1, this._icon2);
    }
  }

  get icon() {
    if (this._icon2)
      return this._icon2.icon;
    else
      return -1;
  }

  open() {
    this._icon1.icon = Icons.TREE_COLLAPSE;
    this.opened = true;
    this.treeView._open(this);
  }

  close() {
    this._icon1.icon = Icons.TREE_EXPAND;
    this.opened = false;
    this.treeView._close(this);
  }

  set text(b) {
    if (typeof b === "string") {
      this._label.text = b;
      this._labelText = b;
    } else if (b instanceof HTMLElement) {
      this._label.remove();
      this.header.add(b);

      this._label = b;
      this._labelText = b;
    }
  }

  get text() {
    return this._labelText;
  }


  item(name, args={}) {
    args.treeParent = this;
    return this.parentWidget.item(name, args);
  }

  init() {
    super.init();
  }

  static define() {return {
    tagname : "tree-item-x",
    style   : "treeview"
  }}
}
UIBase.internalRegister(TreeItem);

export class TreeView extends Container {
  constructor() {
    super();

    this.items = [];
    this.strokes = [];
  }

  init() {
    super.init();

    this.style["display"] = "flex";
    this.style["flex-direction"] = "column";

    //this.shadow.appendChild(this.overdraw);

    this.overdraw = UIBase.createElement("overdraw-x");
    console.log(this.overdraw.startNode);
    this.overdraw.startNode(this);

    this.style["margin"] = this.style["padding"] = "0px";

    this.updateOverdraw();
  }

  _forAllChildren(item, cb) {
    let visit = (n) => {
      cb(n);

      for (let c of n.treeChildren) {
        visit(c);
      }
    }

    for (let c of item.treeChildren) {
      visit(c);
    }
  }

  _open(item) {
    this._forAllChildren(item, (c) => {
      if (c.opened) {
        c.unhide();
      }
    })

    this._makeStrokes();
  }

  _close(item) {
    this._forAllChildren(item, (c) => {
      c.hide();
    })

    this._makeStrokes();
  }

  _makeStrokes() {
    if (!this.overdraw) {
      //this.doOnce(this._makeStrokes);
      return;
    }

    for (let elem of this.strokes) {
      elem.remove();
    }
    this.strokes.length = 0;

    let hidden = (item) => {
      return item.hidden;
      let p = item;

      while (p) {
        if (!p.opened)
          return true;
        p = p.treeParent;
      }

      return false;
    }

    let items = this.items;
    if (items.length == 0) {
      return;
    }

    this.overdraw.clear();

    let next = (i) => {
      i++;

      while (i < items.length && hidden(items[i])) {
        i++;
        continue;
      }

      return i;
    }

    let i = 0;
    if (hidden(items[i]))
      i = next(i);

    let origin = this.overdraw.getBoundingClientRect();
    let overdraw = this.overdraw;

    let line = function(x1, y1, x2, y2) {
      let ox = origin.x, oy = origin.y;

      x1 -= ox; y1 -= oy;
      x2 -= ox; y2 -= oy;

      overdraw.line([x1, y1], [x2, y2]);
    }

    console.log("making lines", i);

    let indent = this.getDefault("itemIndent");
    let rowh = this.getDefault("rowHeight");

    let getx = (depth) => {
      return (depth+2.2)*indent + origin.x;
    }

    this.overdraw.style["z-index"] = "0";

    let prev = undefined;

    for (; i<items.length; i = next(i)) {
      let item = this.items[i];
      let item2 = next(i);
      item2 = item2 < items.length ? items[item2] : undefined;

      let r = item._icon1.getBoundingClientRect();

      if (!r) continue;

      let x1 = getx(item.treeDepth);
      let y1 = origin.y + (i+1)*rowh - rowh*0.25;


      if (item2 && item2.treeDepth > item.treeDepth) {//} && (!prev || prev.treeDepth !== item.treeDepth)) {
        let y2 = y1 + rowh*0.75;

        line(x1, y1, x1, y2)
        line(x1, y2, getx(item2.treeDepth)-3, y2);

      } else if (item2 && item2.treeDepth === item.treeDepth) {
        line(x1, y1, x1, y1+rowh*0.5)
      }

      prev = item;
    }
  }

  updateOverdraw() {
    let mm = new math.MinMax(2);
    let ok = false;

    for (let item of this.items) {
      if (item.hidden) {
        //continue;
      }

      for (let r of item.getClientRects()) {
        //console.log(r.y);
        mm.minmax([r.x, r.y]);
        mm.minmax([r.x+r.width, r.y+r.height]);
        ok = true;
      }
    }

    if (!ok) {
      return;
    }

    let r = this.getClientRects()[0];
    if (!r) return;

    let x = r.left;
    let y = r.top;// - r.y;

    let od = this.overdraw;
    let w = mm.max[0] - mm.min[0];
    let h = mm.max[1] - mm.min[1];

    od.style["margin"] = "0px";
    od.style["padding"] = "0px";
    od.svg.style["margin"] = "0px";

    od.style["position"] = UIBase.PositionKey;

    od.style["width"] = (r.width-1) + "px";
    od.style["height"] = (r.height-1) + "px";

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

    //od.style["background-color"] = "rgba(50, 50, 50, 0.25)";
    //od.svg.style["background-color"] = "rgba(50, 50, 50, 0.25)";

  }

  update() {
    super.update();

    this.updateOverdraw();
  }

  item(name, args={icon : undefined}) {
    let ret = UIBase.createElement("tree-item-x");
    this.add(ret);
    ret._init();

    ret.text = name;

    if (args.icon) {
      ret.icon = args.icon;
    }

    ret.treeParent = args.treeParent;
    ret.treeView = this;

    //ret.style["margin-bottom"] = ret.style["margin-top"] = "0px";
    //ret.style["padding-bottom"] = ret.style["padding-top"] = "0px";
    ret.style["max-height"] = this.getDefault("rowHeight") + "px";

    if (ret.treeParent) {
      ret.treeParent.treeChildren.push(ret);
      ret.treeDepth = ret.treeParent.treeDepth + 1;
    }

    let p = ret.treeParent;
    let i = 1;
    while (p) {
      p = p.treeParent;
      i++;
    }

    ret.style["margin-left"] = (i*this.getDefault("itemIndent")) + "px";
    this.items.push(ret);

    this.doOnce(() => {
      this._makeStrokes();
    });

    return ret;
  }

  static define() {return {
    tagname : "tree-view-x",
    style   : "treeview"
  }}
}
UIBase.internalRegister(TreeView);