Home Reference Source

scripts/widgets/ui_panel.js

//bind module to global var to get at it in console.
//
//note that require has an api for handling circular
//module refs, in such cases do not use these vars.

import {CSSFont} from "../core/ui_base.js";

var _ui = undefined;

import * as util from '../path-controller/util/util.js';
import * as vectormath from '../path-controller/util/vectormath.js';
import * as ui_base from '../core/ui_base.js';
import * as ui_widgets from './ui_widgets.js';
import * as toolprop from '../path-controller/toolsys/toolprop.js';
import '../path-controller/util/html5_fileapi.js';
import {ColumnFrame, RowFrame, Container} from "../core/ui.js";

let PropFlags = toolprop.PropFlags;
let PropSubTypes = toolprop.PropSubTypes;

let EnumProperty = toolprop.EnumProperty;

let Vector2   = vectormath.Vector2,
    UIBase    = ui_base.UIBase,
    PackFlags = ui_base.PackFlags,
    PropTypes = toolprop.PropTypes;


//methods to forward to this.contents
let forward_keys = new Set([
  "row", "col", "strip", "noteframe", "helppicker", "vecpopup",
  "tabs", "table", "menu", "listbox", "panel",
  "pathlabel", "label", "listenum", "check", "iconcheck",
  "button", "iconbutton", "colorPicker", "twocol",
  "treeview", "slider", "simpleslider", "curve1d",
  "noteframe", "vecpopup",
  "prop", "tool", "toolPanel", "textbox", "dynamicMenu",
  "add", "prepend", "useIcons", "noMarginsOrPadding", "wrap"
]);

export class PanelFrame extends ColumnFrame {
  constructor() {
    super();

    this.titleframe = super.row();

    this.contents = super.col();
    this.contents._remove = this.contents.remove;
    this.contents.remove = () => {
      this.remove();
    };

    this._panel = this; //compatibility alias

    this.contents._panel = this;
    this.iconcheck = UIBase.createElement("iconcheck-x");
    this.iconcheck.noEmboss = true;

    Object.defineProperty(this.contents, "closed", {
      get: () => {
        return this.closed;
      },

      set: (v) => {
        this.closed = v;
      }
    })

    Object.defineProperty(this.contents, "title", {
      get: () => this.titleframe.getAttribute("title"),
      set: (v) => this.setHeaderToolTip(v)
    });

    this.packflag = this.inherit_packflag = 0;

    this._closed = false;

    this.makeHeader();
  }

  get inherit_packflag() {
    if (!this.contents) return;
    return this.contents.inherit_packflag;
  }

  set inherit_packflag(val) {
    if (!this.contents) return;
    this.contents.inherit_packflag = val;
  }

  get packflag() {
    if (!this.contents) return;
    return this.contents.packflag;
  }

  set packflag(val) {
    if (!this.contents) return;
    this.contents.packflag = val;
  }

  appendChild(child) {
    return this.contents.shadow.appendChild(child);
  }

  get headerLabel() {
    return this.__label.text;
  }

  set headerLabel(v) {
    this.__label.text = v;
    this.__label._updateFont();

    if (this.hasAttribute("label")) {
      this.setAttribute("label", v);
    }

    if (this.ctx) {
      this.setCSS();
    }
  }

  get dataPrefix() {
    return this.contents ? this.contents.dataPrefix : "";
  }

  set dataPrefix(v) {
    if (this.contents) {
      this.contents.dataPrefix = v;
    }
  }

  get closed() {
    return this._closed;
  }

  set closed(val) {
    let update = !!val !== !!this.closed;
    this._closed = val;

    //console.log("closed set", update);
    if (update) {
      this._updateClosed(true);
    }
  }

  static define() {
    return {
      tagname            : "panelframe-x",
      style              : "panel",
      subclassChecksTheme: true
    };
  }

  setHeaderToolTip(tooltip) {
    this.titleframe.setAttribute("title", tooltip);

    this.titleframe._forEachChildWidget((child) => {
      child.setAttribute("title", tooltip);
    });
  }

  saveData() {
    let ret = {
      closed: this._closed
    };

    return Object.assign(super.saveData(), ret);
  }

  loadData(obj) {
    if (!("closed" in obj)) {
      this.closed = obj._closed;
    } else {
      this.closed = obj.closed;
    }
  }

  clear() {
    this.contents.clear();
    return this;
  }

  makeHeader() {
    let row = this.titleframe;

    let iconcheck = this.iconcheck;

    if (!iconcheck) {
      return;
    }

    iconcheck.overrideDefault("padding", 0);

    iconcheck.noMarginsOrPadding();

    iconcheck.overrideDefault("background-color", "rgba(0,0,0,0)");
    iconcheck.overrideDefault("BoxDepressed", "rgba(0,0,0,0)");
    iconcheck.overrideDefault("border-color", "rgba(0,0,0,0)");

    iconcheck.ctx = this.ctx;
    iconcheck._icon_pressed = ui_base.Icons.UI_EXPAND;
    iconcheck._icon = ui_base.Icons.UI_COLLAPSE;
    iconcheck.drawCheck = false;
    iconcheck.iconsheet = ui_base.IconSheets.SMALL;
    iconcheck.checked = this._closed;

    this.iconcheck.onchange = (e) => {
      this.closed = this.iconcheck.checked;
    };

    row._add(iconcheck);

    //stupid css, let's just hackishly put " " to create spacing2

    let onclick = (e) => {
      iconcheck.checked = !iconcheck.checked;

      /* ensure browser doesn't spawn its own (incompatible)
         touch->mouse emulation events}; */
      e.preventDefault();
    };

    let label = this.__label = row.label(this.getAttribute("label"));

    this.__label.overrideClass("panel");
    this.__label.font = "TitleText";

    label._updateFont();

    label.noMarginsOrPadding();
    label.addEventListener("mousedown", onclick);
    label.addEventListener("touchdown", onclick);

    let bs = this.getDefault("border-style");

    row.background = this.getDefault("TitleBackground");
    row.style["border-radius"] = this.getDefault("border-radius") + "px";
    row.style["border"] = `${this.getDefault("border-width")}px ${bs} ${this.getDefault("border-color")}`;

    row.style["padding-right"] = "20px";
    row.style["padding-left"] = "5px";
    row.style["padding-top"] = this.getDefault("padding-top") + "px";
    row.style["padding-bottom"] = this.getDefault("padding-bottom") + "px";
  }

  init() {
    super.init();

    this.background = this.getDefault("background-color");
    this.style["width"] = "100%";

    this.contents.ctx = this.ctx;
    if (!this._closed) {
      super.add(this.contents);
      this.contents.flushUpdate();
    }

    this.contents.dataPrefix = this.dataPrefix;

    //con.style["margin-left"] = "5px";

    this.setCSS();
  }

  setCSS() {
    super.setCSS();

    if (!this.titleframe || !this.__label) {
      return;
    }

    let getDefault = (key, defval) => {
      let val = this.getDefault(key);
      return val !== undefined ? val : defval;
    };

    let bs = this.getDefault("border-style");

    let header_radius = this.getDefault("HeaderRadius");
    if (header_radius === undefined) {
      header_radius = this.getDefault("border-radius");
    }

    let boxmargin = getDefault("padding", 0);

    let paddingleft = getDefault("padding-left", 0);
    let paddingright = getDefault("padding-right", 0);

    paddingleft += boxmargin;
    paddingright += boxmargin;

    this.titleframe.background = this.getDefault("TitleBackground");
    this.titleframe.style["border-radius"] = header_radius + "px";
    this.titleframe.style["border"] = `${this.getDefault("border-width")}px ${bs} ${this.getDefault("TitleBorder")}`;
    this.style["border"] = `${this.getDefault("border-width")}px ${bs} ${this.getDefault("border-color")}`;
    this.style["border-radius"] = this.getDefault("border-radius") + "px";

    this.titleframe.style["padding-top"] = this.getDefault("padding-top") + "px";
    this.titleframe.style["padding-bottom"] = this.getDefault("padding-bottom") + "px";
    this.titleframe.style["padding-left"] = paddingleft + "px";
    this.titleframe.style["padding-right"] = paddingright + "px";
    this.titleframe.style["margin-bottom"] = "0px";
    this.titleframe.style["margin-top"] = "0px";

    this.__label.style["border"] = "unset";
    this.__label.style["border-radius"] = "unset";

    let bg = this.getDefault("background-color");

    this.background = bg;
    this.contents.background = bg;
    this.contents.parentWidget = this;
    this.contents.style["background-color"] = bg;
    this.style["background-color"] = bg;

    let margintop, marginbottom;

    if (this._closed) {
      margintop = getDefault('margin-top-closed', 0);
      marginbottom = getDefault('margin-bottom-closed', 5);
    } else {
      margintop = getDefault('margin-top', 0);
      marginbottom = getDefault('margin-bottom', 0);
    }

    let marginleft = getDefault('margin-left', 0);
    let marginright = getDefault('margin-right', 0);

    this.style['margin-left'] = marginleft + "px";
    this.style['margin-right'] = marginright + "px";

    this.style['margin-top'] = margintop + "px";
    this.style['margin-bottom'] = marginbottom + "px";

    //this.style["padding-left"] = paddingleft + "px";
    //this.style["padding-right"] = paddingright + "px";

    this.__label._updateFont();
  }

  on_disabled() {
    super.on_disabled();

    this.__label._updateFont();
    this.setCSS();
  }

  on_enabled() {
    super.on_enabled();

    this.__label.setCSS();
    this.__label.style["color"] = this.style["color"];
    this.setCSS();
  }

  update() {
    let text = this.getAttribute("label");

    let update = text !== this.__label.text;

    if (this.checkThemeUpdate()) {
      update = true;
      this._setVisible(this.closed, true);
      this.setCSS();
      this.flushSetCSS();
    }

    if (update) {
      this.headerLabel = this.getAttribute("label")

      this.__label._updateFont();
      this.setCSS();
    }

    super.update();
  }

  _onchange(isClosed) {
    if (this.onchange) {
      this.onchange(isClosed);
    }

    if (this.contents.onchange) {
      this.contents.onchange(isClosed);
    }
  }

  //catch setAttribute so we can detect label update
  //even if close and updates disabled
  setAttribute(key, value) {
    let ret = super.setAttribute(key, value);

    if (this.ctx) {
      this.update();
      this.flushUpdate();
    }

    return ret;
  }

  get noUpdateClosedContents() {
    if (!this.hasAttribute("update-closed-contents")) {
      return false;
    }

    let ret = this.getAttribute("update-closed-contents");
    return ret === "true" || ret === "on";
  }

  set noUpdateClosedContents(v) {
    this.setAttribute("update-closed-contents", v ? "true" : "false");
  }

  _setVisible(isClosed, changed) {
    changed = changed || !!isClosed !== !!this._closed;

    this._state = isClosed;

    if (isClosed) {
      this.contents.style.display = "none";

      //this.contents._remove();
      //this.contents.hide(true);
      if (!this.noUpdateClosedContents) {
        this.contents.packflag |= PackFlags.NO_UPDATE;
      }
    } else {
      //this.contents.hide(false);
      this.contents.style.display = "flex";
      this.contents.packflag &= ~PackFlags.NO_UPDATE;
      this.contents.flushUpdate();
    }

    this.contents.hidden = isClosed;

    if (this.parentWidget) {
      this.parentWidget.flushUpdate();
    } else {
      this.flushUpdate();
    }

    if (changed) {
      this._onchange(isClosed);
    }

    /*
    for (let c of this.shadow.childNodes) {
      if (c !== this.titleframe) {
        c.hidden = state;
      }
    }*/
  }

  _updateClosed(changed) {
    this._setVisible(this._closed, changed);

    if (this.iconcheck) {
      this.iconcheck.checked = this._closed;
    }
  }
}


let makeForward = (k) => {
  return function() {
    return this.contents[k](...arguments);
  }
};

for (let k of forward_keys) {
  PanelFrame.prototype[k] = makeForward(k);
}

UIBase.internalRegister(PanelFrame);