Home Reference Source

scripts/core/ui.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.

var _ui = undefined;

import * as util from '../path-controller/util/util.js';
import * as units from '../core/units.js';
import * as vectormath from '../path-controller/util/vectormath.js';
import * as ui_base from './ui_base.js';
import * as ui_widgets from '../widgets/ui_widgets.js';
import * as toolprop from '../path-controller/toolsys/toolprop.js';
import '../path-controller/util/html5_fileapi.js';
import {HotKey} from '../path-controller/util/simple_events.js';
import {CSSFont} from './ui_theme.js';
import {theme, iconSheetFromPackFlag} from './ui_base.js';

import {createMenu, startMenu} from "../widgets/ui_menu.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;

import {DataPathError} from '../path-controller/controller/controller_base.js';

import cconst from '../config/const.js';

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

    this._label = "";

    this._lastText = "";

    this.dom = document.createElement("div");
    this.dom.setAttribute("class", "_labelx");

    let style = document.createElement("style");
    style.textContent = `
      div._labelx::selection {
        color: none;
        background: none;
         -webkit-user-select:none;
         user-select:none;
      }
    `;

    this.shadow.appendChild(style);
    this.shadow.appendChild(this.dom);

    this.font = "LabelText";
  }

  get font() {
    return this._font;
  }

  /**Set a font defined in ui_base.defaults
   e.g. DefaultText*/
  set font(fontDefaultName) {
    if (typeof fontDefaultName === "string") {
      this._font = this.getDefault(fontDefaultName);
      if (!this._font) {
        console.warn("Invalid font", fontDefaultName);
      }
    } else if (typeof fontDefaultName === "object" && fontDefaultName instanceof CSSFont) {
      this._font = fontDefaultName;
    } else {
      console.warn("Invalid font", fontDefaultName);
    }

    this._updateFont();
  }

  get text() {
    return this._label;
    //return this.dom.innerText;
  }

  set text(text) {
    this._label = text;

    if (!this.hasAttribute("datapath")) {
      this.dom.innerText = text;
    }
  }

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

  init() {
    this.dom.style["width"] = "max-content";
  }

  setCSS() {
    super.setCSS(false);
    this.setBoxCSS();
  }

  on_disabled() {
    super.on_disabled();
    this._enabled_font = this.font;
    this.font = "DefaultText";
    this._updateFont();
  }

  on_enabled() {
    super.on_enabled();
    this.font = this._enabled_font;
    this._updateFont();
  }

  _updateFont() {
    let font = this._font;
    if (!font) return;

    this.dom.style["font"] = font.genCSS();
    this.dom.style["color"] = font.color;
  }

  updateDataPath() {
    if (this.ctx === undefined) {
      return;
    }

    let path = this.getAttribute("datapath");
    let prop = this.getPathMeta(this.ctx, path);
    let val = this.getPathValue(this.ctx, path);

    if (val === undefined) {
      return;
    }

    //console.log(path);
    if (prop.type & (PropTypes.INT | PropTypes.FLOAT)) {
      val = units.buildString(val, prop.baseUnit, prop.decimalPlaces, prop.displayUnit);
    }

    val = "" + this._label + " " + val;

    if (val !== this._lastText) {
      this._lastText = val;
      this.dom.innerText = val;
    }
  }

  update() {
    let key = "";

    if (this._font !== undefined && this._font instanceof CSSFont) {
      key += this._font.genKey();
    }

    if (key !== this._last_font) {
      this._last_font = key;
      this._updateFont();
    }

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

    if (this.hasAttribute("datapath")) {
      this.updateDataPath();
    }
  }
}

ui_base.UIBase.internalRegister(Label);

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

    this.dataPrefix = '';
    this.massSetPrefix = '';

    this.inherit_packflag = 0;

    let style = this.styletag = document.createElement("style")
    style.textContent = `
    `;

    this.shadow.appendChild(style);
    this.reversed = false;

    this._prefixstack = [];
    this._mass_prefixstack = [];
  }

  set background(bg) {
    this.__background = bg;

    this.styletag.textContent = `div.containerx {
        background-color : ${bg};
      }
    `;
    this.style["background-color"] = bg;
  }

  get children() {
    let list = [];

    this._forEachChildWidget((n) => {
      list.push(n);
    });

    return list
  }

  static define() {
    return {
      tagname: "container-x"
    };
  }

  /** recursively change path prefix for all children*/
  changePathPrefix(newprefix) {
    let prefix = this.dataPrefix.trim();
    this.dataPrefix = newprefix;

    if (prefix.length > 0) {
      prefix += ".";
    }

    let rec = (n, con) => {
      if (n instanceof Container && n !== this) {
        if (n.dataPrefix.startsWith(prefix)) {
          n.dataPrefix = n.dataPath.slice(prefix.length, n.dataPrefix.length);
          n.dataPrefix = con._joinPrefix(n.dataPrefix);
          con = n;
        }
      }

      if (n.hasAttribute("datapath")) {
        let path = n.getAttribute("datapath");

        if (path.startsWith(prefix)) {
          path = path.slice(prefix.length, path.length);
          path = con._joinPrefix(path);
          n.setAttribute("datapath", path);

          //update helper tooltips
          n.description = n.description;
        }
      }

      n._forEachChildWidget((n2) => {
        rec(n2, con);
      });
    }

    rec(this, this);
  }

  reverse() {
    this.reversed ^= true;
    return this;
  }

  pushMassSetPrefix(val) {
    this._mass_prefixstack.push(this.massSetPrefix);
    this.massSetPrefix = val;
    return this;
  }

  pushDataPrefix(val) {
    this._prefixstack.push(this.dataPrefix);
    this.dataPrefix = val;
    return this;
  }

  popDataPrefix() {
    this.dataPrefix = this._prefixstack.pop();
    return this;
  }

  popMassSetPrefix() {
    this.massSetPrefix = this._mass_prefixstack.pop();
    return this;
  }

  saveData() {
    if (this.scrollTop || this.scrollLeft) {
      return {
        scrollTop : this.scrollTop,
        scrollLeft: this.scrollLeft
      }
    } else {
      return {};
    }
  }

  loadData(obj) {
    if (!obj) return;

    let x = obj.scrollLeft || 0;
    let y = obj.scrollTop || 0;

    this.doOnce(() => {
      this.scrollTo(x, y);
    }, 12);
  }

  init() {
    this.style["display"] = "flex";
    this.style["flex-direction"] = this.reversed ? "column-reverse" : "column";
    this.style["flex-wrap"] = "nowrap";
    this.style["flex-grow"] = "" + this.getDefault("flex-grow", undefined, "1");

    this.setCSS();

    super.init();

    this.setAttribute("class", "containerx");
  }

  /** Returns previous icon flags */
  useIcons(enabled_or_sheet = true) {
    let enabled = !!enabled_or_sheet;

    let mask = PackFlags.USE_ICONS | PackFlags.SMALL_ICON | PackFlags.LARGE_ICON;
    mask = mask | PackFlags.CUSTOM_ICON_SHEET;
    mask = mask | (255<<PackFlags.CUSTOM_ICON_SHEET_START);

    let previous = this.packflag & mask;

    if (!enabled) {
      this.packflag &= ~PackFlags.USE_ICONS;
      this.inherit_packflag &= ~PackFlags.USE_ICONS;

      return previous;
    }

    let sheet = enabled_or_sheet;

    if (sheet === true) {
      sheet = PackFlags.SMALL_ICON;
    } else if (sheet === 1) {
      sheet = PackFlags.LARGE_ICON;
    } else {
      sheet = PackFlags.CUSTOM_ICON_SHEET | (sheet<<(PackFlags.CUSTOM_ICON_SHEET_START));
    }

    //clear any existing sizing flags
    this.packflag &= ~(PackFlags.SMALL_ICON | PackFlags.LARGE_ICON | PackFlags.CUSTOM_ICON_SHEET);
    this.packflag &= ~(255<<PackFlags.CUSTOM_ICON_SHEET_START);

    this.packflag |= PackFlags.USE_ICONS | sheet;
    this.inherit_packflag |= PackFlags.USE_ICONS | sheet;

    return previous;
  }

  /**
   *
   * @param mode: flexbox wrap mode, can be wrap, nowrap, or wrap-reverse
   * @returns {Container}
   */
  wrap(mode = "wrap") {
    this.style["flex-wrap"] = mode;
    return this;
  }

  noMarginsOrPadding() {
    super.noMarginsOrPadding();

    let keys = ["margin", "padding", "margin-block-start", "margin-block-end"];
    keys = keys.concat(["padding-block-start", "padding-block-end"]);

    for (let k of keys) {
      this.style[k] = "0px";
    }

    return this;
  }

  setCSS() {
    let rest = '';

    let add = (style) => {
      if (!this.hasDefault(style)) {
        return;
      }

      let val = this.getDefault(style);

      if (val !== undefined) {
        rest += `  ${style} = ${val};\n`;
        this.style[style] = val;
      }
    }

    add("border-radius");
    add("border-width");
    add("border-top");
    add("border-bottom");
    add("border-left");
    add("border-right");

    this.styletag.textContent = `div.containerx {
        background-color : ${this.getDefault("background-color")};
        ${rest}
      }
      `;
  }

  overrideDefault(key, val) {
    super.overrideDefault(key, val);
    this.setCSS();

    return this;
  }

  /*
  * shorthand for:
  *
  * .row().noMarginsOrPadding().oneAxisPadding()
  * */
  strip(themeClass = "strip", margin1 = this.getDefault("oneAxisPadding"), margin2 = 1, horiz = undefined) {
    if (horiz === undefined) {
      horiz = this instanceof RowFrame;
      horiz = horiz || this.style["flex-direction"] === "row";
    }

    let flag = horiz ? PackFlags.STRIP_HORIZ : PackFlags.STRIP_VERT;

    let strip = (horiz ? this.row() : this.col());

    if (typeof margin1 !== "number") {
      throw new Error("margin1 was not a number");
    }
    if (typeof margin2 !== "number") {
      throw new Error("margin2 was not a number");
    }

    strip.packflag |= flag;
    strip.dataPrefix = this.dataPrefix;
    strip.massSetPrefix = this.massSetPrefix;

    if (themeClass in theme) {
      strip.overrideClass(themeClass);
      strip.background = strip.getDefault("background-color");
      strip.setCSS();
      strip.overrideClass(themeClass);

      let lastkey;

      strip.update.after(function () {
        let bradius = strip.getDefault("border-radius");
        let bline = strip.getDefault("border-width");
        let bstyle = strip.getDefault("border-style") || 'solid';
        let padding = strip.getDefault("padding");
        let bcolor = strip.getDefault("border-color") || "rgba(0,0,0,0)";
        let margin = strip.getDefault("margin") || 0;

        bline = bline === undefined ? 0 : bline;
        bradius = bradius === undefined ? 0 : bradius;
        padding = padding === undefined ? 5 : padding;

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

        let key = "" + bradius + ":" + bline + ":" + bg + ":" + padding + ":";
        key += bstyle + ":" + padding + ":" + bcolor + ":" + margin;

        if (key !== lastkey) {
          lastkey = key;

          strip.oneAxisPadding(margin1 + padding, margin2 + padding);
          strip.setCSS();

          strip.background = bg;

          strip.style["margin"] = "" + margin + "px";
          strip.style["border"] = `${bline}px ${bstyle} ${bcolor}`;
          strip.style["border-radius"] = "" + bradius + "px";
        }
      })
    } else {
      console.warn(this.constructor.name + ".strip(): unknown theme class " + themeClass);
    }

    /*
    let prev = strip.previousElementSibling;
    if (prev !== undefined && (prev.packflag & flag)) {
      if (horiz) {
        prev.style["padding-right"] = "0px";
      } else {
        prev.style["padding-top"] = "0px";
      }
    }//*/

    return strip;
  }

  /**
   * tries to set margin along one axis only in smart manner
   * */
  oneAxisMargin(m = this.getDefault("oneAxisMargin"), m2 = 0) {
    this.style["margin-top"] = this.style["margin-bottom"] = "" + m + "px";
    this.style["margin-left"] = this.style["margin-right"] = "" + m2 + "px";

    return this;
  }

  /**
   * tries to set padding along one axis only in smart manner
   * */
  oneAxisPadding(axisPadding = this.getDefault("oneAxisPadding"), otherPadding = 0) {
    this.style["padding-top"] = this.style["padding-bottom"] = "" + axisPadding + "px";
    this.style["padding-left"] = this.style["padding-right"] = "" + otherPadding + "px";

    return this;
  }

  setMargin(m) {
    this.style["margin"] = m + "px";

    return this;
  }

  setPadding(m) {
    this.style["padding"] = m + "px";

    return this;
  }

  setSize(width, height) {
    if (width !== undefined) {
      if (typeof width == "number")
        this.style["width"] = this.div.style["width"] = ~~width + "px";
      else
        this.style["width"] = this.div.style["width"] = width;
    }

    if (height !== undefined) {
      if (typeof height == "number")
        this.style["height"] = this.div.style["height"] = ~~height + "px";
      else
        this.style["height"] = this.div.style["height"] = height;
    }

    return this;
  }

  save() {
  }

  load() {
  }

  saveVisibility() {
    localStorage[this.storagePrefix + "_settings"] = JSON.stringify(this);
    return this;
  }

  loadVisibility() {
    let key = this.storagePrefix + "_settings";
    let ok = true;

    if (key in localStorage) {
      console.log("loading UI visibility state. . .");

      try {
        this.loadJSON(JSON.parse(localStorage[key]));
      } catch (error) {
        util.print_stack(error);
        ok = false;
      }
    }

    return ok;
  }

  toJSON() {
    let ret = {
      opened: !this.closed
    };

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

  _ondestroy() {
    this._forEachChildWidget((n) => {
      n._ondestroy();
    });

    super._ondestroy();
  }

  loadJSON(obj) {
    //console.error("ui.js:Container.loadJSON: implement me!");

    return this;
  }

  redrawCurves() {
    throw new Error("Implement me (properly!)");

    if (this.closed)
      return;

    for (let cw of this.curve_widgets) {
      cw.draw();
    }
  }

  listen() {
    window.setInterval(() => {
      this.update();
    }, 150);
  }

  update() {
    super.update();
  }

  appendChild(child) {
    if (child instanceof ui_base.UIBase) {
      child.ctx = this.ctx;
      child.parentWidget = this;
      this.shadow.appendChild(child);

      if (child.onadd) {
        child.onadd();
      }

      return;
    }

    return super.appendChild(child);
  }

  clear(trigger_on_destroy = true) {
    for (let child of this.children) {
      if (child instanceof ui_base.UIBase) {
        child.remove(trigger_on_destroy);
      }
    }
  }

  removeChild(child, trigger_on_destroy = true) {
    let ret = super.removeChild(child);

    if (child.on_remove) {
      child.on_remove();
    }

    if (trigger_on_destroy && child.on_destroy) {
      child.on_destroy();
    }

    child.parentWidget = undefined;

    return ret;
  }

  prepend(child) {
    if (child instanceof UIBase) {
      this._prepend(child);
    } else {
      super.prepend(child);
    }
  }

  //*
  _prepend(child) {
    return this._add(child, true);
  }//*/

  add(child) {
    return this._add(child);
  }

  insert(i, ch) {
    ch.parentWidget = this;
    ch.ctx = this;

    if (i >= this.shadow.childNodes.length) {
      this.add(ch);
    } else {
      this.shadow.insertBefore(ch, util.list(this.children)[i]);
    }

    if (ch.onadd) {
      ch.onadd();
    }
  }

  _add(child, prepend = false) {
    //paranoia check for if we accidentally got a DOM NodeList
    if (child instanceof NodeList) {
      throw new Error("eek!");
    }

    child.ctx = this.ctx;
    child.parentWidget = this;
    child._useDataPathUndo = this._useDataPathUndo;

    if (!child._themeOverride && this._themeOverride) {
      child.overrideTheme(this._themeOverride);
    }

    if (prepend) {
      this.shadow.prepend(child);
    } else {
      this.shadow.appendChild(child);
    }

    /*
    if (child._ctx) {
      child._init();
    }//*/

    if (child.onadd)
      child.onadd();

    return child;
  }

  //TODO: make sure this works on Electron?
  dynamicMenu(title, list, packflag = 0) {
    //actually, .menu works for now
    return this.menu(title, list, packflag);
  }

  /**example usage:

   .menu([
   "some_tool_path.tool()|CustomLabel",
   ui_widgets.Menu.SEP,
   "some_tool_path.another_tool()",
   ["Name", () => {console.log("do something")}]
   ])

   **/
  menu(title, list, packflag = 0) {
    let dbox = UIBase.createElement("dropbox-x");

    dbox._name = title;
    dbox.setAttribute("simple", true);
    dbox.setAttribute("name", title);

    if (list instanceof HTMLElement && list.constructor.name === "Menu") {
      dbox._build_menu = function () {
        if (this._menu !== undefined && this._menu.parentNode !== undefined) {
          this._menu.remove();
        }

        this._menu = createMenu(this.ctx, title, templ);
        return this._menu;
      }
    } else if (list) {
      dbox.template = list;
    }

    this._container_inherit(dbox, packflag);

    this._add(dbox);
    return dbox;
  }

  toolPanel(path_or_cls, args = {}) {
    let tdef;
    let cls;

    if (typeof path_or_cls === "string") {
      cls = this.ctx.api.parseToolPath(path_or_cls);
    } else {
      cls = path_or_cls;
    }

    tdef = cls._getFinalToolDef();

    let packflag = args.packflag || 0;
    let label = args.label || tdef.uiname;
    let createCb = args.createCb || args.create_cb;
    let container = args.container || this.panel(label);
    let defaultsPath = args.defaultsPath || "toolDefaults";

    if (defaultsPath.length > 0 && !defaultsPath.endsWith(".")) {
      defaultsPath += ".";
    }

    let path = defaultsPath + tdef.toolpath;

    container.useIcons(false);

    let inputs = tdef.inputs || {};
    for (let k in inputs) {
      let prop = inputs[k];

      if (prop.flag & PropFlags.PRIVATE) {
        continue;
      }

      let apiname = prop.apiname || k;
      let path2 = path + "." + apiname;

      container.prop(path2);
    }

    container.tool(path_or_cls, packflag, createCb, label);

    return container;
  }

  tool(path_or_cls, packflag_or_args = {}, createCb = undefined, label = undefined) {
    let cls;
    let packflag;

    if (typeof packflag_or_args === "object") {
      let args = packflag_or_args;

      packflag = args.packflag;
      createCb = args.createCb;
      label = args.label;
    } else {
      packflag = packflag_or_args || 0;
    }

    if (typeof path_or_cls == "string") {
      if (path_or_cls.search(/\|/) >= 0) {
        path_or_cls = path_or_cls.split("|");

        if (label === undefined && path_or_cls.length > 1) {
          label = path_or_cls[1].trim();
        }

        path_or_cls = path_or_cls[0].trim();
      }

      if (this.ctx === undefined) {
        console.warn("this.ctx was undefined in tool()");
        return;
      }

      cls = this.ctx.api.parseToolPath(path_or_cls);

      if (cls === undefined) {
        console.warn("Unknown tool for toolpath \"" + path_or_cls + "\"");
        return;
      }
    } else {
      cls = path_or_cls;
    }

    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;
    let hotkey;

    if (createCb === undefined) {
      createCb = (cls) => {
        return this.ctx.api.createTool(this.ctx, path_or_cls);
      }
    }

    let cb = () => {
      console.log("tool run");

      let toolob = createCb(cls);
      this.ctx.api.execTool(this.ctx, toolob);
    }

    let def = typeof path_or_cls === "string" ? this.ctx.api.getToolDef(path_or_cls) : cls.tooldef();
    let tooltip = def.description === undefined ? def.uiname : def.description;

    //is there a hotkey hardcoded in the class?
    if (def.hotkey !== undefined) {
      tooltip += "\n\t" + def.hotkey;
      hotkey = def.hotkey;
    } else { //if not, use getToolPathHotkey api
      let path = path_or_cls;

      if (typeof path != "string") {
        path = def.toolpath;
      }

      let hotkey = this.ctx.api.getToolPathHotkey(this.ctx, path);
      if (hotkey) {
        tooltip += "\n\tHotkey: " + hotkey;
      }
    }

    let ret;

    if (def.icon !== undefined && (packflag & PackFlags.USE_ICONS)) {
      label = label === undefined ? tooltip : label;

      ret = this.iconbutton(def.icon, label, cb);

      ret.iconsheet = iconSheetFromPackFlag(packflag);
      ret.packflag |= packflag;
    } else {
      label = label === undefined ? def.uiname : label;

      ret = this.button(label, cb);
      ret.description = tooltip;
      ret.packflag |= packflag;
    }

    return ret;
  }

  //supports number types
  textbox(inpath, text, cb = undefined, packflag = 0) {
    let path;

    if (inpath) {
      path = this._joinPrefix(inpath);
    }

    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    let ret = UIBase.createElement("textbox-x");

    if (path !== undefined) {
      ret.setAttribute("datapath", path);
    }

    ret.ctx = this.ctx;
    ret.parentWidget = this;
    ret._init();
    this._add(ret);

    ret.setCSS();
    ret.update();

    ret.packflag |= packflag;
    ret.onchange = cb;
    ret.text = text;

    return ret;
  }

  pathlabel(inpath, label = "", packflag = 0) {
    let path;

    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    if (inpath) {
      path = this._joinPrefix(inpath);
    }

    let ret = UIBase.createElement("label-x");

    ret.text = label;
    ret.packflag = packflag;
    ret.setAttribute("datapath", path);

    this._add(ret);

    return ret;
  }

  label(text) {
    let ret = UIBase.createElement("label-x");
    ret.text = text;

    this._add(ret);

    return ret;
  }

  /**
   *
   * makes a button for a help picker tool
   * to view tooltips on mobile devices
   * */
  helppicker() {
    let ret = this.iconbutton(ui_base.Icons.HELP, "Help Picker", () => {
      this.getScreen().hintPickerTool();
    })

    if (util.isMobile()) {
      //ret.iconsheet = 2;
      //XXX look up in mobile theme properly
    }

    if (ret.ctx) {
      ret._init();
      ret.setCSS();
    }

    return ret;
  }

  iconbutton(icon, description, cb, thisvar, packflag = 0) {
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    let ret = UIBase.createElement("iconbutton-x")

    ret.packflag |= packflag;

    ret.setAttribute("icon", icon);
    ret.description = description;
    ret.icon = icon;

    ret.iconsheet = iconSheetFromPackFlag(packflag);

    ret.onclick = cb;

    this._add(ret);

    return ret;
  }

  button(label, cb, thisvar, id, packflag = 0) {
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    let ret = UIBase.createElement("button-x")

    ret.packflag |= packflag;

    ret.setAttribute("name", label);
    ret.setAttribute("buttonid", id); //XXX no longer used?
    ret.onclick = cb;

    this._add(ret);

    return ret;
  }

  _joinPrefix(path, prefix = this.dataPrefix.trim()) {
    if (path === undefined) {
      return undefined;
    }

    path = path.trim();
    if (path[0] === "/") {
      return path;
    }

    if (prefix.length > 0 && path.length > 0 && !prefix.endsWith(".") && !path.startsWith(".")) {
      path = "." + path;
    }

    return prefix + path;
  }

  colorbutton(inpath, packflag, mass_set_path = undefined) {
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    mass_set_path = this._getMassPath(this.ctx, inpath, mass_set_path);

    let ret = UIBase.createElement("color-picker-button-x");

    if (inpath !== undefined) {
      inpath = this._joinPrefix(inpath);
      ret.setAttribute("datapath", inpath);
    }

    if (mass_set_path !== undefined) {
      ret.setAttribute("mass_set_path", mass_set_path);
    }

    ret.packflag |= packflag;

    this._add(ret);
    return ret;
  }

  noteframe(packflag = 0) {
    let ret = UIBase.createElement("noteframe-x");

    ret.packflag |= (this.inherit_packflag & ~PackFlags.NO_UPDATE) | packflag;

    this._add(ret);
    return ret;
  }

  curve1d(inpath, packflag = 0, mass_set_path = undefined) {
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    mass_set_path = this._getMassPath(this.ctx, inpath, mass_set_path);

    let ret = UIBase.createElement("curve-widget-x");

    ret.ctx = this.ctx;
    ret.packflag |= packflag;

    if (inpath) {
      inpath = this._joinPrefix(inpath);
      ret.setAttribute("datapath", inpath);
    }

    if (mass_set_path)
      ret.setAttribute("mass_set_path", mass_set_path);

    this.add(ret);

    return ret;
  }

  vecpopup(inpath, packflag = 0, mass_set_path = undefined) {
    let button = UIBase.createElement("vector-popup-button-x");

    mass_set_path = this._getMassPath(this.ctx, inpath, mass_set_path);

    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;
    let name = "vector";

    if (inpath) {
      inpath = this._joinPrefix(inpath);

      button.setAttribute("datapath", inpath);
      if (mass_set_path) {
        button.setAttribute("mass_set_path", mass_set_path);
      }

      let rdef = this.ctx.api.resolvePath(this.ctx, inpath);
      if (rdef && rdef.prop) {
        name = rdef.prop.uiname || rdef.prop.name;
      }
    }

    button.setAttribute("name", name);
    button.packflag |= packflag;

    this.add(button);
    return button;
  }

  _getMassPath(ctx, inpath, mass_set_path) {
    if (mass_set_path === undefined && this.massSetPrefix.length > 0) {
      mass_set_path = ctx.api.getPropName(ctx, inpath);
    }

    if (mass_set_path === undefined) {
      return undefined;
    }

    return this._joinPrefix(mass_set_path, this.massSetPrefix);
  }

  prop(inpath, packflag = 0, mass_set_path = undefined) {
    if (!this.ctx) {
      console.warn(this.id + ".ctx was undefined");
      let p = this.parentWidget;

      while (p) {
        if (p.ctx) {
          console.warn("Fetched this.ctx from parent");
          this.ctx = p.ctx;
          break;
        }

        p = p.parentWidget;
      }

      if (!this.ctx) {
        throw new Error("ui.Container.prototype.prop(): this.ctx was undefined");
      }
    }

    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    let rdef = this.ctx.api.resolvePath(this.ctx, this._joinPrefix(inpath), true);

    if (rdef === undefined || rdef.prop === undefined) {
      console.warn("Unknown property at path", this._joinPrefix(inpath), this.ctx.api.resolvePath(this.ctx, this._joinPrefix(inpath), true));
      return;
    }
    //slider(path, name, defaultval, min, max, step, is_int, do_redraw, callback, packflag=0) {
    let prop = rdef.prop;

    let useDataPathUndo = !(prop.flag & PropFlags.NO_UNDO);

    //console.log(prop, PropTypes, PropSubTypes);

    function makeUIName(name) {
      if (typeof name === "number" && isNaN(name)) {
        console.warn("Subkey error in data api", inpath);
        return "" + name;
      }

      name = "" + name;
      name = name[0].toUpperCase() + name.slice(1, name.length).toLowerCase();
      name = name.replace(/_/g, " ");
      return name;
    }

    if (prop.type === PropTypes.REPORT) {
      return this.pathlabel(inpath, prop.uiname);
    } else if (prop.type === PropTypes.STRING) {
      let ret;

      if (prop.flag & PropFlags.READ_ONLY) {
        ret = this.pathlabel(inpath, prop.uiname);
      } else if (prop.multiLine) {
        ret = this.textarea(inpath, rdef.value, packflag, mass_set_path);
        ret.useDataPathUndo = useDataPathUndo;
      } else {
        let strip = this.strip();

        let uiname = prop.uiname !== undefined ? prop.uiname : ToolProperty.makeUIName(prop.apiname);

        strip.label(prop.uiname);

        ret = strip.textbox(inpath);
        ret.useDataPathUndo = useDataPathUndo;

        if (mass_set_path) {
          ret.setAttribute("mass_set_path", mass_set_path);
        }
      }

      ret.packflag |= packflag;
      return ret;
    } else if (prop.type === PropTypes.CURVE) {
      let ret = this.curve1d(inpath, packflag, mass_set_path);
      ret.useDataPathUndo = useDataPathUndo;
      return ret;
    } else if (prop.type === PropTypes.INT || prop.type === PropTypes.FLOAT) {
      let ret;
      if (packflag & PackFlags.SIMPLE_NUMSLIDERS) {
        ret = this.simpleslider(inpath, {packflag: packflag});
      } else {
        ret = this.slider(inpath, {packflag: packflag});
      }

      ret.useDataPathUndo = useDataPathUndo;
      ret.packflag |= packflag;

      if (mass_set_path) {
        ret.setAttribute("mass_set_path", mass_set_path);
      }

      return ret;
    } else if (prop.type === PropTypes.BOOL) {
      let ret = this.check(inpath, prop.uiname, packflag, mass_set_path);
      ret.useDataPathUndo = useDataPathUndo;
      return ret;
    } else if (prop.type === PropTypes.ENUM) {
      if (rdef.subkey !== undefined) {
        let subkey = rdef.subkey;
        let name = rdef.prop.ui_value_names[rdef.subkey];

        if (name === undefined) {
          name = makeUIName(rdef.subkey);
        }

        let check = this.check(inpath, name, packflag, mass_set_path);
        let tooltip = rdef.prop.descriptions[subkey];

        check.useDataPathUndo = useDataPathUndo;

        check.description = tooltip === undefined ? rdef.prop.ui_value_names[subkey] : tooltip;
        check.icon = rdef.prop.iconmap[rdef.subkey];

        return check;
      }

      if (!(packflag & PackFlags.USE_ICONS)) {
        if (packflag & PackFlags.FORCE_PROP_LABELS) {
          let strip = this.strip();
          strip.label(prop.uiname);

          return strip.listenum(inpath, {packflag, mass_set_path}).setUndo(useDataPathUndo);
        } else {
          return this.listenum(inpath, {packflag, mass_set_path}).setUndo(useDataPathUndo);
        }

      } else {
        if (packflag & PackFlags.FORCE_PROP_LABELS) {
          let strip = thdis.strip();
          strip.label(prop.uiname);

          return strip.checkenum(inpath, undefined, packflag).setUndo(useDataPathUndo);
        } else {
          return this.checkenum(inpath, undefined, packflag).setUndo(useDataPathUndo);
        }
      }
    } else if (prop.type & (PropTypes.VEC2 | PropTypes.VEC3 | PropTypes.VEC4)) {
      if (rdef.subkey !== undefined) {
        let ret;

        if (packflag & PackFlags.SIMPLE_NUMSLIDERS)
          ret = this.simpleslider(inpath, {packflag: packflag});
        else
          ret = this.slider(inpath, {packflag: packflag});

        ret.packflag |= packflag;
        return ret.setUndo(useDataPathUndo);
      } else if (prop.subtype === PropSubTypes.COLOR) {
        return this.colorbutton(inpath, packflag, mass_set_path).setUndo(useDataPathUndo);
        //return this.colorPicker(inpath, packflag, mass_set_path);
      } else {
        let ret = UIBase.createElement("vector-panel-x");

        mass_set_path = this._getMassPath(this.ctx, inpath, mass_set_path);

        ret.packflag |= packflag | (this.inherit_packflag & ~PackFlags.NO_UPDATE);
        ret.inherit_packflag |= packflag | (this.inherit_packflag & ~PackFlags.NO_UPDATE);

        if (inpath) {
          ret.setAttribute("datapath", this._joinPrefix(inpath));
        }

        if (mass_set_path) {
          ret.setAttribute("mass_set_path", mass_set_path);
        }

        this.add(ret);

        return ret.setUndo(useDataPathUndo);
      }
    } else if (prop.type === PropTypes.FLAG) {
      if (rdef.subkey !== undefined) {
        let tooltip = rdef.prop.descriptions[rdef.subkey];
        let name = rdef.prop.ui_value_names[rdef.subkey];

        if (typeof rdef.subkey === "number") {
          name = rdef.prop.keys[rdef.subkey];
          if (name && name in rdef.prop.ui_value_names) {
            name = rdef.prop.ui_value_names[name];
          } else {
            name = makeUIName(name ? name : "(error)");
          }
        }

        if (name === undefined) {
          name = "(error)";
        }

        let ret = this.check(inpath, name, packflag, mass_set_path);

        ret.icon = rdef.prop.iconmap[rdef.subkey];

        if (tooltip) {
          ret.description = tooltip;
        }

        return ret.setUndo(useDataPathUndo);
      } else {
        let con = this;

        if (packflag & PackFlags.FORCE_PROP_LABELS) {
          con = this.strip();
          con.label(prop.uiname);
        }

        if (packflag & PackFlags.PUT_FLAG_CHECKS_IN_COLUMNS) {
          let i = 0;
          let row = con.row();
          let col1 = row.col();
          let col2 = row.col();

          for (let k in prop.values) {
            let name = prop.ui_value_names[k];
            let tooltip = prop.descriptions[k];

            if (name === undefined) {
              name = makeUIName(k);
            }

            let con2 = i & 1 ? col1 : col2;
            let check = con2.check(`${inpath}[${k}]`, name, packflag, mass_set_path);

            if (tooltip) {
              check.description = tooltip;
            }

            check.setUndo(useDataPathUndo);

            i++;
          }

          return row;
        }

        if (packflag & PackFlags.WRAP_CHECKBOXES) {
          let isrow = this.style["flex-direction"] === "row";
          isrow = isrow || this.style["flex-direction"] === "row-reverse";

          let wrapChars;

          let strip, con;

          if (isrow) {
            wrapChars = this.getDefault("checkRowWrapLimit", undefined, 24);
            strip = this.col().strip();
            strip.packflag |= packflag;
            strip.inherit_packflag |= packflag;

            con = strip.row();
          } else {
            wrapChars = this.getDefault("checkColWrapLimit", undefined, 5);
            strip = this.row().strip();
            strip.packflag |= packflag;
            strip.inherit_packflag |= packflag;

            con = strip.col();
          }

          let x = 0;
          let y = 0;

          for (let k in prop.values) {
            let name = prop.ui_value_names[k];
            let tooltip = prop.descriptions[k];

            if (name === undefined) {
              name = makeUIName(k);
            }

            let check = con.check(`${inpath}[${k}]`, name, packflag, mass_set_path);

            if (tooltip) {
              check.description = tooltip;
            }

            x += name.length;
            y += 1;

            if (isrow && x > wrapChars) {
              x = 0;
              con = strip.row();
            } else if (!isrow && y > wrapChars) {
              y = 0;
              con = strip.col();
            }
          }

          return strip;
        }

        if (con === this) {
          con = this.strip();
        }

        let rebuild = () => {
          con.clear();

          for (let k in prop.values) {
            let name = prop.ui_value_names[k];
            let tooltip = prop.descriptions[k];

            if (name === undefined) {
              name = makeUIName(k);
            }

            let check = con.check(`${inpath}[${k}]`, name, packflag, mass_set_path);
            check.useDataPathUndo = useDataPathUndo;

            if (tooltip) {
              check.description = tooltip;
            }

            check.setUndo(useDataPathUndo);
          }
        }

        rebuild();
        let last_hash = prop.calcHash();

        con.update.after(() => {
          let hash = prop.calcHash();

          if (last_hash !== hash) {
            last_hash = hash;
            console.error("Property definition update");
            rebuild();
          }
        });

        return con;
      }
    }
  }

  iconcheck(inpath, icon, name, mass_set_path) {
    let ret = UIBase.createElement("iconcheck-x");
    ret.icon = icon;
    ret.description = name;

    if (inpath) {
      ret.setAttribute("datapath", inpath);
    }

    if (mass_set_path) {
      ret.setAttribute("mass_set_path", mass_set_path);
    }

    this.add(ret);

    return ret;
  }

  check(inpath, name, packflag = 0, mass_set_path = undefined) {
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    let path = inpath !== undefined ? this._joinPrefix(inpath) : undefined;

    //let prop = this.ctx.getProp(path);
    let ret;
    if (packflag & PackFlags.USE_ICONS) {
      ret = UIBase.createElement("iconcheck-x");

      ret.iconsheet = iconSheetFromPackFlag(packflag);
    } else {
      ret = UIBase.createElement("check-x");
    }

    mass_set_path = this._getMassPath(this.ctx, inpath, mass_set_path);

    ret.packflag |= packflag;
    ret.label = name;
    ret.noMarginsOrPadding();

    if (inpath) {
      ret.setAttribute("datapath", path);
    }

    if (mass_set_path) {
      ret.setAttribute("mass_set_path", mass_set_path);
    }

    this._add(ret);

    //ret.update();

    return ret;
  }

  /*
  *
  * new (optional) form: checkenum(inpath, args)
  * */
  checkenum(inpath, name, packflag, enummap, defaultval, callback, iconmap, mass_set_path) {
    if (typeof name === "object" && name !== null) {
      let args = name;

      name = args.name;
      packflag = args.packflag;
      enummap = args.enummap;
      defaultval = args.defaultval;
      callback = args.callback;
      iconmap = args.iconmap;
      mass_set_path = args.mass_set_path;
    }

    mass_set_path = this._getMassPath(this.ctx, inpath, mass_set_path);

    packflag = packflag === undefined ? 0 : packflag;
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    let path = this._joinPrefix(inpath);

    let has_path = path !== undefined;
    let prop;
    let frame;

    if (path !== undefined) {
      prop = this.ctx.api.resolvePath(this.ctx, path, true);

      if (prop !== undefined)
        prop = prop.prop;
    }

    if (path !== undefined) {
      if (prop === undefined) {
        console.warn("Bad path in checkenum", path);
        return;
      }

      frame = this.strip();
      frame.oneAxisPadding();

      if (packflag & PackFlags.USE_ICONS) {
        for (let key in prop.values) {
          let check = frame.check(inpath + "[" + key + "]", "", packflag);

          check.packflag |= PackFlags.HIDE_CHECK_MARKS;

          check.icon = prop.iconmap[key];
          check.drawCheck = false;

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

          check.dom.style["padding"] = "0px";
          check.dom.style["margin"] = "0px";

          check.description = prop.descriptions[key];
          //console.log(check.description, key, prop.keys[key], prop.descriptions, prop.keys);
        }
      } else {
        if (name === undefined) {
          name = prop.uiname;
        }

        frame.label(name).font = "TitleText";

        let checks = {};

        let ignorecb = false;

        function makecb(key) {
          return () => {
            if (ignorecb) return;

            ignorecb = true;
            for (let k in checks) {
              if (k !== key) {
                checks[k].checked = false;
              }
            }
            ignorecb = false;

            if (callback) {
              callback(key);
            }
          }
        }

        for (let key in prop.values) {
          let check = frame.check(inpath + " = " + prop.values[key], prop.ui_value_names[key]);
          checks[key] = check;

          if (mass_set_path) {
            check.setAttribute("mass_set_path", mass_set_path);
          }


          check.description = prop.descriptions[prop.keys[key]];
          if (!check.description) {
            check.description = "" + prop.ui_value_names[key];
          }
          check.onchange = makecb(key);
          //console.log("PATH", path);
        }
      }
    }

    return frame;
  }

  checkenum_panel(inpath, name, packflag = 0, callback = undefined, mass_set_path = undefined, prop = undefined) {
    packflag = packflag === undefined ? 0 : packflag;
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    let path = this._joinPrefix(inpath);
    let frame;

    let has_path = path !== undefined;

    if (path !== undefined && prop === undefined) {
      prop = this.ctx.api.resolvePath(this.ctx, path, true);

      if (prop !== undefined)
        prop = prop.prop;
    }

    if (!name && prop) {
      name = prop.uiname;
    }

    if (path !== undefined) {
      if (prop === undefined) {
        console.warn("Bad path in checkenum", path);
        return;
      }

      frame = this.panel(name, name, packflag);

      frame.oneAxisPadding();
      frame.setCSS.after(frame.background = this.getDefault("BoxSub2BG"));

      if (packflag & PackFlags.USE_ICONS) {
        for (let key in prop.values) {
          let check = frame.check(inpath + " == " + prop.values[key], "", packflag);

          check.icon = prop.iconmap[key];
          check.drawCheck = false;

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

          check.dom.style["padding"] = "0px";
          check.dom.style["margin"] = "0px";

          check.description = prop.descriptions[key];
          //console.log(check.description, key, prop.keys[key], prop.descriptions, prop.keys);
        }
      } else {
        if (name === undefined) {
          name = prop.uiname;
        }

        frame.label(name).font = "TitleText";

        let checks = {};

        let ignorecb = false;

        function makecb(key) {
          return () => {
            if (ignorecb) return;

            ignorecb = true;
            for (let k in checks) {
              if (k !== key) {
                checks[k].checked = false;
              }
            }
            ignorecb = false;

            if (callback) {
              callback(key);
            }
          }
        }

        for (let key in prop.values) {
          let check = frame.check(inpath + " = " + prop.values[key], prop.ui_value_names[key]);
          checks[key] = check;

          if (mass_set_path) {
            check.setAttribute("mass_set_path", mass_set_path);
          }


          check.description = prop.descriptions[prop.keys[key]];
          if (!check.description) {
            check.description = "" + prop.ui_value_names[key];
          }
          check.onchange = makecb(key);
          //console.log("PATH", path);
        }
      }
    }

    return frame;
  }

  /*
    enummap is an object that maps
    ui names to keys, e.g.:

    ui.listenum("color", "Color", {
      RED   : 0,
      GREEN : 1,
      BLUE  : 2
    });

    path can be undefined, in which case, use callback,
    which gets the current enum as an argument

    defaultval cannot be undefined
  */
  listenum(inpath, name, enumDef, defaultval, callback, iconmap, packflag = 0) {
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;
    let mass_set_path;

    if (name && typeof name === "object") {
      let args = name;

      name = args.name;
      enumDef = args.enumDef;
      defaultval = args.defaultval;
      callback = args.callback;
      iconmap = args.iconmap;
      packflag = args.packflag || 0;
      mass_set_path = args.mass_set_path;
    }

    mass_set_path = this._getMassPath(this.ctx, inpath, mass_set_path);

    let path;

    if (inpath !== undefined) {
      path = this._joinPrefix(inpath);
    }

    let ret = UIBase.createElement("dropbox-x")
    if (enumDef !== undefined) {
      if (enumDef instanceof toolprop.EnumProperty) {
        ret.prop = enumDef;
      } else {
        ret.prop = new toolprop.EnumProperty(defaultval, enumDef, path, name);
      }

      if (iconmap !== undefined) {
        ret.prop.addIcons(iconmap)
      }
    } else {
      let res = this.ctx.api.resolvePath(this.ctx, path, true);

      if (res !== undefined) {
        ret.prop = res.prop;

        name = name === undefined ? res.prop.uiname : name;
      }
    }

    mass_set_path = this._getMassPath(this.ctx, inpath, mass_set_path);

    if (path !== undefined) {
      ret.setAttribute("datapath", path);
    }
    if (mass_set_path !== undefined) {
      ret.setAttribute("mass_set_path", mass_set_path);
    }

    ret.setAttribute("name", name);

    if (defaultval) {
      ret.setValue(defaultval);
    }

    ret.onchange = callback;
    ret.onselect = callback;

    ret.packflag |= packflag;

    this._add(ret);
    return ret;
  }

  getroot() {
    let p = this;

    while (p.parent !== undefined) {
      p = p.parent;
    }

    return p;
  }

  simpleslider(inpath, name, defaultval, min, max, step, is_int, do_redraw, callback, packflag = 0) {
    if (arguments.length === 2 || typeof name === "object") {
      let args = Object.assign({}, name);

      args.packflag = (args.packflag || 0) | PackFlags.SIMPLE_NUMSLIDERS;
      return this.slider(inpath, args);
      //new-style api call
    } else {
      return this.slider(inpath, name, defaultval, min, max, step, is_int, do_redraw, callback, packflag | PackFlags.SIMPLE_NUMSLIDERS);
    }
  }

  /**
   *
   * usage: .slider(inpath, {
   *  name : bleh,
   *  defaultval : number,
   *  etc...
   * });
   * */
  slider(inpath, name, defaultval, min, max, step, is_int, do_redraw, callback, packflag = 0) {
    if (arguments.length === 2 || typeof name === "object") {
      //new-style api call

      let args = name;

      name = args.name;
      defaultval = args.defaultval;
      min = args.min;
      max = args.max;
      step = args.step;
      is_int = args.is_int || args.isInt;
      do_redraw = args.do_redraw;
      callback = args.callback;
      packflag = args.packflag || 0;
    }

    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;
    let ret;

    if (inpath) {
      inpath = this._joinPrefix(inpath);

      let rdef = this.ctx.api.resolvePath(this.ctx, inpath, true);
      if (rdef && rdef.prop && (rdef.prop.flag & PropFlags.SIMPLE_SLIDER)) {
        packflag |= PackFlags.SIMPLE_NUMSLIDERS;
      }
      if (rdef && rdef.prop && (rdef.prop.flag & PropFlags.FORCE_ROLLER_SLIDER)) {
        packflag |= PackFlags.FORCE_ROLLER_SLIDER;
      }
    }

    let simple = (packflag & PackFlags.SIMPLE_NUMSLIDERS) || cconst.simpleNumSliders;
    simple = simple && !(packflag & PackFlags.FORCE_ROLLER_SLIDER);

    let extraTextBox = cconst.useNumSliderTextboxes && !(packflag & PackFlags.NO_NUMSLIDER_TEXTBOX);

    if (extraTextBox) {
      if (simple) {
        ret = UIBase.createElement("numslider-simple-x");
      } else {
        ret = UIBase.createElement("numslider-textbox-x");
      }
    } else {
      if (simple) {
        ret = UIBase.createElement("numslider-simple-x");
      } else {
        ret = UIBase.createElement("numslider-x");
      }
    }

    ret.packflag |= packflag;

    let decimals;

    if (inpath) {
      ret.setAttribute("datapath", inpath);
    }

    if (name) {
      ret.setAttribute("name", name);
    }

    if (min !== undefined) {
      ret.setAttribute("min", min);
    }
    if (max !== undefined) {
      ret.setAttribute("max", max);
    }

    if (defaultval !== undefined) {
      ret.setValue(defaultval);
    }

    if (is_int) {
      ret.setAttribute("integer", is_int);
    }

    if (decimals !== undefined) {
      ret.decimalPlaces = decimals;
    }

    if (callback) {
      ret.onchange = callback;
    }

    this._add(ret);

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

    return ret;
  }

  _container_inherit(elem, packflag = 0) {
    //don't inherit NO_UPDATE

    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    elem.packflag |= packflag;
    elem.inherit_packflag |= packflag;
    elem.dataPrefix = this.dataPrefix;
    elem.massSetPrefix = this.massSetPrefix;
  }

  treeview() {
    let ret = UIBase.createElement("tree-view-x");
    ret.ctx = this.ctx;
    this.add(ret);

    this._container_inherit(ret);

    return ret;
  }

  panel(name, id, packflag = 0, tooltip = undefined) {
    id = id === undefined ? name : id;

    let ret = UIBase.createElement("panelframe-x");

    this._container_inherit(ret, packflag);

    if (tooltip) {
      ret.setHeaderToolTip(tooltip);
    }

    ret.setAttribute("label", name);
    ret.setAttribute("id", id);

    this._add(ret);


    if (this.ctx) {//check init was called
      ret.ctx = this.ctx;
      ret._init();
      //ret.headerLabel = name;

      ret.contents.ctx = ret.ctx;
    }

    ret.contents.dataPrefix = this.dataPrefix;
    ret.contents.massSetPrefix = this.massSetPrefix;

    return ret;
  }

  row(packflag = 0) {
    let ret = UIBase.createElement("rowframe-x");

    this._container_inherit(ret, packflag);

    this._add(ret);

    ret.ctx = this.ctx;

    return ret;
  }

  listbox(packflag = 0) {
    let ret = UIBase.createElement("listbox-x");

    this._container_inherit(ret, packflag);

    this._add(ret);
    return ret;
  }

  table(packflag = 0) {
    let ret = UIBase.createElement("tableframe-x");

    this._container_inherit(ret, packflag);

    this._add(ret);
    return ret;
  }

  twocol(parentDepth = 1, packflag = 0) {
    let ret = UIBase.createElement("two-column-x");

    ret.parentDepth = parentDepth;

    this._container_inherit(ret, packflag);

    this._add(ret);
    return ret;
  }

  col(packflag = 0) {
    let ret = UIBase.createElement("colframe-x");

    this._container_inherit(ret, packflag);

    this._add(ret);
    return ret;
  }

  colorPicker(inpath, packflag_or_args = 0, mass_set_path = undefined, themeOverride = undefined) {
    let packflag;

    if (typeof packflag_or_args === "object") {
      let args = packflag_or_args;

      packflag = args.packflag !== undefined ? args.packflag : 0;
      mass_set_path = args.massSetPath;
      themeOverride = args.themeOverride;
    }

    let path;

    if (inpath) {
      path = this._joinPrefix(inpath);
    }

    let ret = UIBase.createElement("colorpicker-x");

    if (themeOverride) {
      ret.overrideClass(themeOverride);
    }

    packflag |= PackFlags.SIMPLE_NUMSLIDERS;

    this._container_inherit(ret, packflag);

    ret.ctx = this.ctx;
    ret.parentWidget = this;
    ret._init();
    ret.packflag |= packflag;
    ret.inherit_packflag |= packflag;
    ret.constructor.setDefault(ret);

    if (path !== undefined) {
      ret.setAttribute("datapath", path);
    }

    console.warn("mass_set_path", mass_set_path);
    if (mass_set_path) {
      ret.setAttribute("mass_set_path", mass_set_path);
    }

    //XXX
    window.colorpicker = ret;

    this._add(ret);
    return ret;
  }

  textarea(datapath = undefined, value = "", packflag = 0, mass_set_path = undefined) {
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    mass_set_path = this._getMassPath(this.ctx, datapath, mass_set_path);

    let ret = UIBase.createElement("rich-text-editor-x");
    ret.ctx = this.ctx;

    ret.packflag |= packflag;

    if (value !== undefined) {
      ret.value = value;
    }

    if (datapath)
      ret.setAttribute("datapath", datapath)
    if (mass_set_path)
      ret.setAttribute("mass_set_path", mass_set_path)

    this.add(ret);
    return ret;
  }

  /**
   * html5 viewer
   * */
  viewer(datapath = undefined, value = "", packflag = 0, mass_set_path = undefined) {
    packflag |= this.inherit_packflag & ~PackFlags.NO_UPDATE;

    mass_set_path = this._getMassPath(this.ctx, datapath, mass_set_path);

    let ret = UIBase.createElement("html-viewer-x");
    ret.ctx = this.ctx;

    ret.packflag |= packflag;

    if (value !== undefined) {
      ret.value = value;
    }

    if (datapath)
      ret.setAttribute("datapath", datapath)
    if (mass_set_path)
      ret.setAttribute("mass_set_path", mass_set_path)

    this.add(ret);
    return ret;
  }

  //
  tabs(position = "top", packflag = 0) {

    let ret = UIBase.createElement("tabcontainer-x");

    ret.constructor.setDefault(ret);
    ret.setAttribute("bar_pos", position);

    this._container_inherit(ret, packflag);

    this._add(ret);
    return ret;
  }

  asDialogFooter() {
    this.style['margin-top'] = '15px';
    this.style['justify-content'] = 'flex-end';

    return this;
  }

};

ui_base.UIBase.internalRegister(Container, "div");


export class RowFrame extends Container {
  constructor() {
    super();
  }

  static define() {
    return {
      tagname: 'rowframe-x'
    };
  }

  //try to set styling as early as possible
  connectedCallback() {
    super.connectedCallback();

    this.style['display'] = 'flex';
    this.style['flex-direction'] = this.reversed ? 'row-reverse' : 'row';
  }

  init() {
    super.init();

    this.style['display'] = 'flex';
    this.style['flex-direction'] = this.reversed ? 'row-reverse' : 'row';

    if (!this.style['align-items'] || this.style['align-items'] == '') {
      this.style['align-items'] = 'center';
    }

    if (this.getDefault("slider-style") === "simple") {
      this.packflag |= PackFlags.SIMPLE_NUMSLIDERS;
      this.inherit_packflag |= PackFlags.SIMPLE_NUMSLIDERS;
    }
  }

  oneAxisMargin(m = this.getDefault('oneAxisMargin'), m2 = 0) {
    this.style['margin-left'] = this.style['margin-right'] = m + 'px';
    this.style['margin-top'] = this.style['margin-bottom'] = '' + m2 + 'px';

    return this;
  }

  oneAxisPadding(m = this.getDefault('oneAxisPadding'), m2 = 0) {
    this.style['padding-left'] = this.style['padding-right'] = '' + m + 'px';
    this.style['padding-top'] = this.style['padding-bottom'] = '' + m2 + 'px';

    return this;
  }

  update() {
    super.update();
  }
}

UIBase.internalRegister(RowFrame);

export class ColumnFrame extends Container {
  constructor() {
    super();
  }

  static define() {
    return {
      tagname: "colframe-x"
    };
  }

  init() {
    super.init();

    this.style["display"] = "flex";
    this.style["flex-direction"] = "column";
    this.style["justify-content"] = "right";
  }

  update() {
    super.update();
  }

  oneAxisMargin(m = this.getDefault('oneAxisMargin'), m2 = 0) {
    this.style['margin-top'] = this.style['margin-bottom'] = '' + m + 'px';
    this.style['margin-left'] = this.style['margin-right'] = m2 + 'px';

    return this;
  }

  oneAxisPadding(m = this.getDefault('oneAxisPadding'), m2 = 0) {
    this.style['padding-top'] = this.style['padding-bottom'] = '' + m + 'px';
    this.style['padding-left'] = this.style['padding-right'] = '' + m2 + 'px';

    return this;
  }
}

UIBase.internalRegister(ColumnFrame);


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

    this._colWidth = 256;
    this.parentDepth = 1;
  }

  get colWidth() {
    if (this.hasAttribute("colWidth")) {
      return parsepx(this.getAttribute("colWidth"));
    }

    return this._colWidth;
  }

  set colWidth(v) {
    if (this.hasAttribute("colWidth")) {
      this.setAttribute("colWidth", "" + v);
    } else {
      this._colWidth = v;
    }
  }

  static define() {
    return {
      tagname: "two-column-x"
    }
  }

  init() {
    super.init();

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

  update() {
    super.update();

    let p = this;

    for (let i = 0; i < this.parentDepth; i++) {
      p = p.parentWidget ? p.parentWidget : p;
    }

    if (!p) {
      return;
    }

    let r = p.getBoundingClientRect();

    if (!r) {
      return;
    }

    let style = r.width > this.colWidth*2.0 ? 'row' : 'column';

    if (this.style["flex-direction"] !== style) {
      this.style["flex-direction"] = style;
    }
  }
}

UIBase.internalRegister(TwoColumnFrame);