lively.ide_studio_controls_popups.cp.js

import { TilingLayout, without, Icon, Morph, ShadowObject, Label, component, add, ViewModel, part } from 'lively.morphic';
import { Color, Point, rect, Rectangle, pt } from 'lively.graphics';
import { PropertyLabel, PropertyLabelLight, PropertyLabelActiveLight, PropertyLabelHoveredLight, DarkNumberIconWidget, DarkPopupWindow, DarkThemeList, EnumSelector, PropertyLabelActive, PropertyLabelHovered } from '../shared.cp.js';
import { ColorInput } from '../../styling/color-picker.cp.js';
import { num, string, arr } from 'lively.lang';
import { signal } from 'lively.bindings';
import { DarkColorPicker } from '../dark-color-picker.cp.js';
import { PopupWindow } from '../../styling/shared.cp.js';
import { InputLineDefault } from 'lively.components/inputs.cp.js';
import { DefaultNumberWidget, DarkNumberWidget } from '../../value-widgets.cp.js';
import { LabeledCheckboxOnDark, LabeledCheckboxLight } from 'lively.components';

export class PopupModel extends ViewModel {
  static get properties () {
    return {
      liveStyleClasses: {
        defaultValues: ['Popup']
      },
      bindings: {
        get () {
          return [{ target: 'close button', signal: 'onMouseDown', handler: 'close' }];
        }
      },
      expose: { get () { return ['close']; } }
    };
  }

  alignAtButton (btn) {
    // FIXME: Hack that is necessary due to the fact that otherwise,
    // a wrong initial extent propagates through the layout computation leading to a visible hickup.
    this.view.height = 25;
    this.view.topRight = btn.globalBounds().bottomRight().addXY(0, 5);
    this.view.topLeft = this.world().visibleBounds().translateForInclusion(this.view.globalBounds()).topLeft();
  }

  close () {
    this.view.remove();
  }
}

export class ShadowPopupModel extends PopupModel {
  static get properties () {
    return {
      fastShadow: { defaultValue: false },
      insetShadow: { defaultValue: false },
      isHaloItem: { defaultValue: true },
      shadowValue: {
        initialize () {
          this.shadowValue = new ShadowObject({});
        }
      },
      expose: { get () { return ['isHaloItem', 'isPropertiesPanelPopup', 'close', 'shadowValue']; } },
      bindings: {
        get () {
          return [
            { target: 'close button', signal: 'onMouseDown', handler: 'close' },
            { target: 'fast shadow checkbox', signal: 'checked', handler: 'toggleFastShadow' },
            { target: 'x offset', signal: 'number', handler: 'confirm' },
            { target: 'y offset', signal: 'number', handler: 'confirm' },
            { target: 'blur input', signal: 'number', handler: 'confirm' },
            { target: 'spread input', signal: 'number', handler: 'confirm' },
            { target: 'shadow color input', signal: 'color', handler: 'confirm' }
          ];
        }
      }
    };
  }

  get isPropertiesPanelPopup () {
    return true;
  }

  attach (view) {
    super.attach(view);
    this.update();
  }

  onRefresh (prop) {
    if (!this.view) return;
    if (prop === 'fastShadow') this.update();
    if (prop === 'insetShadow') this.update();
  }

  /**
   * Initializes the shadow popup from an initial shadow value.
   */
  initFrom (shadowValue) {
    if (shadowValue) this.fastShadow = shadowValue.fast;
    this.shadowValue = shadowValue;
    this.update();
  }

  /**
   * Toggles wether or not the shadow should be rendered behind
   * transparent areas (slow) or not (fast)
   */
  toggleFastShadow () {
    this.fastShadow = !this.fastShadow;
    this.confirm();
  }

  /**
   * Refresh the UI to reflect the currently stored shadow value.
   */
  update () {
    this.withoutBindingsDo(() => {
      const {
        fastShadowCheckbox, spreadInput,
        blurInput, xOffset, yOffset, buffer,
        shadowColorInput, title, footer
      } = this.ui;

      fastShadowCheckbox.checked = !this.fastShadow; // makes sense due to the text of the label in the control

      const p = Point.polar(this.shadowValue.distance, num.toRadians(this.shadowValue.rotation));
      xOffset.number = p.x;
      yOffset.number = p.y;
      spreadInput.number = this.shadowValue.spread;
      blurInput.number = this.shadowValue.blur;
      shadowColorInput.setColor(this.shadowValue.color);

      title.textString = this.insetShadow ? 'Inner Shadow' : 'Drop Shadow';
      footer.visible = !this.insetShadow;
      buffer.height = !this.insetShadow ? 10 : 20;
    });
  }

  /**
   * Update the current shadow value based on the inputs in the UI.
   * This is invoked in response to user interactions.
   */
  confirm () {
    const { shadowColorInput, spreadInput, blurInput, xOffset, yOffset } = this.ui;
    const polar = pt(xOffset.number, yOffset.number);
    const distance = polar.r();
    const rotation = num.toDegrees(polar.theta()) % 360;
    this.shadowValue = new ShadowObject({
      fast: this.fastShadow,
      inset: this.insetShadow,
      color: shadowColorInput.colorValue,
      blur: blurInput.number,
      spread: spreadInput.number,
      distance,
      rotation
    });
    signal(this.view, 'value', this.shadowValue);
  }
}

/**
  General control for all properties that can be configured
  by a single number input such as opacity or blur.
*/
export class SingleNumberModel extends PopupModel {
  static get properties () {
    return {
      value: {},
      isHaloItem: { defaultValue: true },
      expose: { get () { return ['isHaloItem', 'value', 'isPropertiesPanelPopup', 'close']; } },
      bindings: {
        get () {
          return [
            { target: 'close button', signal: 'onMouseDown', handler: 'close' },
            { target: 'value input', signal: 'number', handler: 'onValueChanged' }
          ];
        }
      }
    };
  }

  get isPropertiesPanelPopup () {
    return true;
  }

  attach (view) {
    super.attach(view);
    this.ui.valueInput.number = this.value;
  }

  onValueChanged () {
    this.value = this.ui.valueInput.number;
    signal(this.view, 'value', this.value);
  }
}

/**
  General control for all properties that can be configured
  by a single selection input such as the native cursor of a morph.
*/
export class SingleSelectionModel extends PopupModel {
  static get properties () {
    return {
      selection: {},
      isHaloItem: { defaultValue: true },
      expose: { get () { return ['isHaloItem', 'isPropertiesPanelPopup', 'close']; } },
      bindings: {
        get () {
          return [
            { target: 'close button', signal: 'onMouseDown', handler: 'close' },
            { target: 'selection input', signal: 'selection', handler: 'onSelectionChanged' }
          ];
        }
      }
    };
  }

  get isPropertiesPanelPopup () {
    return true;
  }

  attach (view) {
    super.attach(view);
    this.ui.selectionInput.selection = this.selection;
  }

  onSelectionChanged () {
    this.selection = this.ui.selectionInput.selection;
    signal(this.view, 'value', this.selection);
  }
}

export class PaddingControlsModel extends ViewModel {
  static get properties () {
    return {
      showAllSidesControl: {
        defaultValue: false
      },
      expose: {
        get () {
          return ['paddingChanged', 'startPadding'];
        }
      },
      bindings: {
        get () {
          return [
            { target: 'independent padding toggle', signal: 'onMouseDown', handler: 'toggleAllSidesPadding' },
            { target: 'padding top', signal: 'onMouseDown', handler: 'focusField', converter: () => 'top' },
            { target: 'padding right', signal: 'onMouseDown', handler: 'focusField', converter: () => 'right' },
            { target: 'padding bottom', signal: 'onMouseDown', handler: 'focusField', converter: () => 'bottom' },
            { target: 'padding left', signal: 'onMouseDown', handler: 'focusField', converter: () => 'left' },

            { target: 'padding top', signal: 'numberChanged', handler: 'confirm' },
            { target: 'padding right', signal: 'numberChanged', handler: 'confirm' },
            { target: 'padding bottom', signal: 'numberChanged', handler: 'confirm' },
            { target: 'padding left', signal: 'numberChanged', handler: 'confirm' },
            { target: 'padding all', signal: 'numberChanged', handler: 'confirm' }
          ];
        }
      }
    };
  }

  startPadding (pad) {
    this.withoutBindingsDo(() => {
      const left = pad.left();
      const right = pad.right();
      const bottom = pad.bottom();
      const top = pad.top();
      this.showAllSidesControl = !(arr.uniq([left, right, top, bottom]).length === 1);
      this.ui.paddingBottom.number = bottom;
      this.ui.paddingLeft.number = left;
      this.ui.paddingRight.number = right;
      this.ui.paddingTop.number = top;

      if (!this.showAllSidesControl) this.ui.paddingAll.number = left;
      else this.ui.paddingAll.setMixed();
    });
  }

  viewDidLoad () {
    this.update();
  }

  onRefresh (prop) {
    if (prop === 'showAllSidesControl') {
      this.update();
    }
  }

  update () {
    const { paddingAll, multiPaddingControl, independentPaddingToggle } = this.ui;
    paddingAll.visible = !this.showAllSidesControl;
    multiPaddingControl.visible = this.showAllSidesControl;
    independentPaddingToggle.master.setState(this.showAllSidesControl ? 'active' : null);
  }

  focusField (focusedField) {
    const { paddingIndicator } = this.ui;
    switch (focusedField) {
      case 'top':
        paddingIndicator.rotation = 0;
        break;
      case 'right':
        paddingIndicator.rotation = Math.PI / 2;
        break;
      case 'bottom':
        paddingIndicator.rotation = Math.PI;
        break;
      case 'left':
        paddingIndicator.rotation = -Math.PI / 2;
        break;
    }
  }

  confirm () {
    const {
      paddingAll, multiPaddingControl,
      paddingTop, paddingRight, paddingBottom, paddingLeft
    } = this.ui;
    if (paddingAll.visible) {
      signal(this, 'paddingChanged', rect(paddingAll.number, paddingAll.number, 0, 0));

      paddingLeft.number = paddingRight.number = paddingTop.number = paddingBottom.number = paddingAll.number;
    }

    if (multiPaddingControl.visible) {
      signal(this, 'paddingChanged', Rectangle.inset(paddingLeft.number, paddingTop.number, paddingRight.number, paddingBottom.number));

      if (paddingTop.number === paddingLeft.number && paddingTop.number === paddingRight.number && paddingTop.number === paddingBottom.number) paddingAll.number = paddingLeft.number;
      else paddingAll.setMixed();
    }
  }

  toggleAllSidesPadding () {
    this.showAllSidesControl = !this.showAllSidesControl;
  }
}

export class PaddingPopupModel extends PopupModel {
  static get properties () {
    return {
      expose: {
        get () {
          return ['isPropertiesPanelPopup', 'close', 'startPadding', 'paddingChanged'];
        }
      },
      bindings: {
        get () {
          return [
            { target: 'padding controls', signal: 'paddingChanged', handler: 'paddingChanged' },
            { target: 'close button', signal: 'onMouseDown', handler: 'close' }
          ];
        }
      }
    };
  }

  startPadding (pad) {
    this.ui.paddingControls.startPadding(pad);
  }

  get isPropertiesPanelPopup () {
    return true;
  }

  paddingChanged (padding) {
    signal(this, 'paddingChanged', padding);
  }
}

export class PositionPopupModel extends PopupModel {
  static get properties () {
    return {
      expose: {
        get () {
          return ['setPoint', 'isPropertiesPanelPopup', 'close'];
        }
      },
      bindings: {
        get () {
          return [
            { target: 'close button', signal: 'onMouseDown', handler: 'close' },
            {
              target: 'position x input',
              signal: 'numberChanged',
              handler: 'confirm'
            }, {
              target: 'position y input',
              signal: 'numberChanged',
              handler: 'confirm'
            }];
        }
      }
    };
  }

  get isPropertiesPanelPopup () {
    return true;
  }

  setPoint (point) {
    const { positionYInput, positionXInput } = this.ui;
    positionXInput.number = point.x;
    positionYInput.number = point.y;
  }

  confirm () {
    const { positionYInput, positionXInput } = this.ui;
    signal(this.view, 'value', pt(positionXInput.number, positionYInput.number));
  }
}

const ShadowPopup = component(DarkPopupWindow, {
  defaultViewModel: ShadowPopupModel,
  name: 'shadow popup',
  layout: new TilingLayout({
    axis: 'column',
    axisAlign: 'center',
    hugContentsHorizontally: true,
    hugContentsVertically: true,
    orderByIndex: true,
    resizePolicies: [['header menu', {
      height: 'fixed',
      width: 'fill'
    }], ['footer', {
      height: 'fixed',
      width: 'fill'
    }]]
  }),
  extent: pt(241.4, 191),
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['Shadow', null]
    }]
  }, add({
    name: 'shadow controls',
    borderColor: Color.rgb(23, 160, 251),
    borderWidth: 0,
    extent: pt(241, 76.6),
    layout: new TilingLayout({
      align: 'right',
      hugContentsVertically: true,
      justifySubmorphs: 'spaced',
      orderByIndex: true,
      padding: rect(48, 10, -15, -2),
      spacing: 10,
      wrapSubmorphs: true
    }),
    fill: Color.rgba(0, 0, 0, 0),
    submorphs: [part(DarkNumberIconWidget, {
      name: 'x offset',
      width: 70,
      submorphs: [{
        name: 'interactive label',
        fontFamily: 'IBM Plex Mono',
        fontSize: 13,
        padding: rect(8, 0, -1, 0),
        textAndAttributes: ['X', null]
      }],
      tooltip: 'X offset'
    }),
    part(DarkNumberIconWidget, {
      name: 'blur input',
      extent: pt(80, 22),
      viewModel: { min: 0 },
      submorphs: [{
        name: 'interactive label',
        fontSize: 14,
        fontFamily: 'Material Icons',
        textAndAttributes: ['\ue3a5', null]
      }],
      tooltip: 'Shadow blur'
    }),
    part(DarkNumberIconWidget, {
      name: 'y offset',
      width: 70,
      submorphs: [{
        name: 'interactive label',
        fontFamily: 'IBM Plex Mono',
        fontSize: 13,
        padding: rect(7, 0, 0, 0),
        textAndAttributes: ['Y', null]
      }],
      tooltip: 'Y offset'
    }),
    part(DarkNumberIconWidget, {
      name: 'spread input',
      extent: pt(80, 22),
      viewModel: { min: 0 },
      submorphs: [{
        name: 'interactive label',
        fontSize: 14,
        fontFamily: 'Material Icons',
        textAndAttributes: ['\ue3e0', null]
      }],
      tooltip: 'Shadow spread'
    })]
  }), add(part(ColorInput, {
    name: 'shadow color input',
    height: 27,
    viewModel: {
      colorPickerComponent: DarkColorPicker
    },
    submorphs: [{
      name: 'hex input',
      extent: pt(70, 23),
      textAndAttributes: ['FFFFFF', null]
    }, {
      name: 'opacity input',
      width: 80
    }]
  })), add({ name: 'buffer', height: 10, fill: Color.transparent }), add({
    name: 'footer',
    borderColor: Color.rgbHex('616A6B'),
    borderWidth: 1,
    extent: pt(241, 50.3),
    fill: Color.rgba(0, 0, 0, 0),
    submorphs: [{
      name: 'h wrapper',
      borderColor: Color.rgb(23, 160, 251),
      extent: pt(228, 31),
      fill: Color.rgba(0, 0, 0, 0),
      layout: new TilingLayout({
        axisAlign: 'center',
        orderByIndex: true,
        padding: rect(15, 0, -10, 0)
      }),
      position: pt(6.2, 9.5),
      submorphs: [part(LabeledCheckboxOnDark, {
        name: 'fast shadow checkbox',
        viewModel: { label: 'Show behind transparent areas' }
      })]
    }]
  })]
});

const NumberWidgetLight = component(DefaultNumberWidget, {
  name: 'number widget/light',
  layout: new TilingLayout({
    align: 'center',
    axisAlign: 'center',
    justifySubmorphs: 'spaced',
    padding: rect(0, 0, 5, 0),
    resizePolicies: [['value', {
      height: 'fixed',
      width: 'fixed'
    }]]
  }),
  borderColor: Color.rgbHex('CCCCCC'),
  borderRadius: 2,
  borderWidth: 1,
  dropShadow: null,
  submorphs: [
    { name: 'value', fontColor: Color.black, cursorColor: Color.gray, fontSize: 14 },
    without('button holder')
  ]
});

const NumberPopupLight = component(PopupWindow, {
  defaultViewModel: SingleNumberModel,
  name: 'number popup',
  layout: new TilingLayout({
    axis: 'column',
    hugContentsHorizontally: true,
    hugContentsVertically: true
  }),
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['d', null]
    }]
  }, add({
    name: 'footer',
    borderColor: Color.rgb(97, 106, 107),
    borderWidth: 0,
    extent: pt(70, 50.3),
    fill: Color.rgba(0, 0, 0, 0),
    submorphs: [part(NumberWidgetLight, {
      name: 'value input',
      viewModel: { min: 0 },
      position: pt(11.3, 14),
      submorphs: [{
        name: 'interactive label',
        fontSize: 14,
        fontFamily: 'Material Icons',
        textAndAttributes: ['', null]
      }],
      tooltip: 'Object Blur'
    })]
  })]
});

export function parameterizedNumberPopupLight (spec) {
  let { title, tooltip, value, min, max, baseFactor, floatingPoint } = spec;
  title = string.tokens(string.decamelize(title)).map(m => string.capitalize(m)).join(' ');
  return component(NumberPopupLight, {
    viewModel: { value },
    hasFixedPosition: true,
    submorphs: [{
      name: 'header menu',
      submorphs: [{
        name: 'title',
        textAndAttributes: [title, null]
      }]
    }, {
      name: 'footer',
      submorphs: [{
        name: 'value input',
        viewModel: {
          min,
          max,
          baseFactor,
          floatingPoint
        }
      }],
      tooltip: tooltip
    }]
  });
}

const PositionPopupLight = component(PopupWindow, {
  name: 'position popup/light',
  defaultViewModel: PositionPopupModel,
  submorphs: [
    {
      name: 'header menu',
      submorphs: [{
        name: 'title',
        textAndAttributes: ['Position', null]
      }]
    },
    add({
      name: 'position controls',
      extent: pt(241, 43.6),
      layout: new TilingLayout({
        orderByIndex: true,
        padding: rect(10, 10, 0, 0),
        spacing: 20
      }),
      submorphs: [
        part(DarkNumberIconWidget, {
          name: 'position x input',
          master: NumberWidgetLight,
          submorphs: [{
            name: 'interactive label',
            fontSize: 13,
            fontFamily: 'IBM Plex Mono',
            padding: rect(8, 0, -1, 0),
            textAndAttributes: ['X', null],
            fontColor: Color.rgba(101, 135, 139, .5)
          }]
        }),
        part(DarkNumberIconWidget, {
          name: 'position y input',
          master: NumberWidgetLight,
          submorphs: [{
            name: 'interactive label',
            fontSize: 13,
            fontFamily: 'IBM Plex Mono',
            padding: rect(8, 0, -1, 0),
            textAndAttributes: ['Y', null],
            fontColor: Color.rgba(101, 135, 139, .5)
          }]
        })
      ]
    })
  ]
});

const ShadowPopupLight = component(ShadowPopup, {
  name: 'shadow popup/light',
  layout: new TilingLayout({
    axis: 'column',
    axisAlign: 'center',
    hugContentsHorizontally: true,
    hugContentsVertically: true,
    orderByIndex: true,
    resizePolicies: [['header menu', {
      height: 'fixed',
      width: 'fill'
    }], ['footer', {
      height: 'fixed',
      width: 'fill'
    }]],
    wrapSubmorphs: false
  }),
  master: PopupWindow,
  submorphs: [
    {
      name: 'shadow controls',
      submorphs: [
        {
          name: 'x offset',
          master: NumberWidgetLight,
          submorphs: [{
            name: 'interactive label',
            fontColor: Color.rgba(101, 135, 139, .5)
          }]
        },
        {
          name: 'blur input',
          master: NumberWidgetLight,
          submorphs: [{
            name: 'interactive label',
            fontColor: Color.rgba(101, 135, 139, .5)
          }]
        },
        {
          name: 'y offset',
          master: NumberWidgetLight,
          submorphs: [{
            name: 'interactive label',
            fontColor: Color.rgba(101, 135, 139, .5)
          }]
        },
        {
          name: 'spread input',
          master: NumberWidgetLight,
          submorphs: [{
            name: 'interactive label',
            fontColor: Color.rgba(101, 135, 139, .5)
          }]
        }
      ]
    },
    {
      name: 'shadow color input',
      submorphs: [
        {
          name: 'hex input',
          borderRadius: 2,
          borderWidth: 1,
          dropShadow: null,
          fontSize: 14,
          master: InputLineDefault
        },
        {
          name: 'opacity input',
          master: NumberWidgetLight,
          submorphs: [{
            name: 'interactive label',
            fontColor: Color.rgba(101, 135, 139, .5)
          }]
        }
      ]
    },
    {
      name: 'footer',
      borderColor: Color.rgbHex('D7DBDD'),
      borderWidthLeft: 0,
      submorphs: [
        {
          name: 'h wrapper',
          submorphs: [
            {
              name: 'fast shadow checkbox',
              master: LabeledCheckboxLight
            }
          ]
        }
      ]
    }
  ]
});

const InsetShadowPopup = component(ShadowPopup, {
  name: 'inset shadow popup',
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['Inset Shadow', null]
    }]
  }]
});

export const PaddingControlsLight = component({
  name: 'padding controls',
  defaultViewModel: PaddingControlsModel,
  layout: new TilingLayout({
    align: 'right',
    axisAlign: 'center',
    orderByIndex: true,
    padding: rect(0, 0, 27, 0),
    spacing: 5
  }),
  extent: pt(250, 30),
  fill: Color.rgba(0, 0, 0, 0),
  submorphs: [
    part(NumberWidgetLight, {
      name: 'padding all',
      viewModel: {
        min: 0,
        number: 0
      },
      extent: pt(60, 22),
      position: pt(9.7, 6.6),
      tooltip: 'Padding',
      borderRadius: 2,
      submorphs: [
        add({
          type: Label,
          name: 'interactive label',
          fontFamily: 'Material Icons',
          fontColor: Color.rgba(101, 135, 139),
          padding: rect(8, 0, -1, 0),
          lineHeight: 1,
          fontSize: 16,
          fontFamily: 'Material Icons',
          textAndAttributes: ['\ue22f', null]
        }, 'value'),
        { name: 'value', fontSize: 14, width: 60 }]
    }),
    {
      name: 'multi padding control',
      position: pt(7.6, 33.4),
      layout: new TilingLayout({
        align: 'center',
        axisAlign: 'center',
        orderByIndex: true,
        spacing: 1
      }),
      fill: Color.transparent,
      extent: pt(175, 30),
      clipMode: 'hidden',
      submorphs: [
        {
          name: 'centering wrapper',
          fill: Color.transparent,
          clipMode: 'hidden',
          layout: new TilingLayout({
            align: 'center',
            axisAlign: 'center'
          }),
          extent: pt(30, 30),
          submorphs: [
            {
              type: Label,
              name: 'padding indicator',
              fill: Color.rgba(229, 231, 233, 0),
              fontColor: Color.rgb(101, 135, 139),
              fontFamily: 'Material Icons',
              padding: rect(7, 5, 0, 0),
              fontSize: 16,
              fontFamily: 'Material Icons',
              textAndAttributes: ['\ue25a', null]
            }
          ]
        },
        part(NumberWidgetLight, {
          name: 'padding left',
          viewModel: { min: 0 },
          extent: pt(35, 22),
          tooltip: 'Leftside Padding',
          borderRadiusTopRight: 0,
          borderRadiusBottomRight: 0,
          borderRadiusTopleft: 2,
          borderRadiusBottomLeft: 2,
          submorphs: [
            { name: 'value', fontSize: 14, width: 35 },
            without('interactive label')
          ]
        }),
        part(NumberWidgetLight, {
          name: 'padding top',
          viewModel: { min: 0 },
          borderRadius: 0,
          extent: pt(35, 22),
          tooltip: 'Topside Padding',
          submorphs: [
            { name: 'value', fontSize: 14, width: 35 },
            without('interactive label')
          ]
        }),
        part(NumberWidgetLight, {
          name: 'padding right',
          viewModel: { min: 0 },
          borderRadius: 0,
          extent: pt(35, 22),
          tooltip: 'Rightside Padding',
          submorphs: [
            { name: 'value', fontSize: 14, width: 35 },
            without('interactive label')]
        }),
        part(NumberWidgetLight, {
          name: 'padding bottom',
          viewModel: { min: 0 },
          borderRadiusTopLeft: 0,
          borderRadiusBottomLeft: 0,
          borderRadiusTopRight: 2,
          borderRadiusBottomRight: 2,
          extent: pt(35, 22),
          tooltip: 'Bottomside Padding',
          submorphs: [
            { name: 'value', fontSize: 14, width: 35 },
            without('interactive label')]
        })
      ]
    }, part(PropertyLabel, {
      name: 'independent padding toggle',
      padding: rect(2, 2),
      lineHeight: 1,
      fontSize: 14,
      master: {
        auto: PropertyLabelLight,
        states: { active: PropertyLabelActiveLight },
        hover: PropertyLabelHoveredLight
      },
      tooltip: 'Toggle independent Fields per Direction',
      fontFamily: 'Material Icons',
      fontSize: 18,
      textAndAttributes: ['', null]
    })
  ]
}
);

export const PaddingControlsDark = component(PaddingControlsLight, {
  submorphs: [
    {
      name: 'padding all',
      layout: new TilingLayout({
        axisAlign: 'center',
        justifySubmorphs: 'spaced',
        resizePolicies: [['value', {
          height: 'fixed',
          width: 'fill'
        }]]
      }),
      borderWidth: 0,
      master: DarkNumberWidget,
      dropShadow: null,
      submorphs: [{
        name: 'interactive label',
        lineHeight: 1,
        fontColor: Color.rgba(178, 235, 242, 0.4976)
      },
      {
        name: 'value',
        fixedWidth: true,
        fontSize: 14
      }]
    },
    {
      name: 'multi padding control',
      submorphs: [
        {
          name: 'centering wrapper',
          submorphs: [{
            name: 'padding indicator',
            fontColor: Color.rgb(178, 235, 242),
            borderWidth: 0
          }]
        },
        {
          name: 'padding left',
          master: DarkNumberWidget,
          dropShadow: null,
          borderWidth: 0,
          submorphs: [
            { name: 'value', fontSize: 14 },
            without('button holder')
          ]
        },
        {
          name: 'padding top',
          master: DarkNumberWidget,
          borderRadius: 0,
          dropShadow: null,
          borderWidth: 0,
          submorphs: [
            { name: 'value', fontSize: 14 },
            without('button holder')
          ]
        },
        {
          name: 'padding right',
          master: DarkNumberWidget,
          borderRadius: 0,
          dropShadow: null,
          borderWidth: 0,
          submorphs: [
            { name: 'value', fontSize: 14 },
            without('button holder')
          ]
        },
        {
          name: 'padding bottom',
          master: DarkNumberWidget,
          dropShadow: null,
          borderWidth: 0,
          submorphs: [
            { name: 'value', fontSize: 14 },
            without('button holder')
          ]
        }
      ]
    },
    {
      name: 'independent padding toggle',
      padding: rect(2, 2),
      fontSize: 18, // FIXME: this is incorrectly removed when reconciling, although there are local masters
      master: {
        auto: PropertyLabel,
        hover: PropertyLabelHovered,
        states: { active: PropertyLabelActive }
      }
    }
  ]
}
);

// m = part(PaddingPopup).openInWorld()
// m.startPadding(rect(5,5,0,0))
// m.openInWorld()
const PaddingPopup = component(PopupWindow, {
  name: 'padding popup',
  defaultViewModel: PaddingPopupModel,
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['Padding', null]
    }]
  }, add(part(PaddingControlsLight))]
});

const BlurPopup = component(DarkPopupWindow, {
  defaultViewModel: SingleNumberModel,
  name: 'blur popup',
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['Blur', null]
    }]
  }, add({
    name: 'footer',
    borderColor: Color.rgb(97, 106, 107),
    borderWidth: 0,
    extent: pt(241, 50.3),
    fill: Color.rgba(0, 0, 0, 0),
    submorphs: [part(DarkNumberIconWidget, {
      name: 'value input',
      extent: pt(75.5,22),
      viewModel: {
        min: 0,
        scaleFactor: 100,
        unit: '%'
      },
      position: pt(11.3, 14),
      submorphs: [{
        name: 'interactive label',
        fontSize: 14,
        fontFamily: 'Material Icons',
        textAndAttributes: ['', null]
      }],
      tooltip: 'Object Blur'
    })]
  })]
});

const OpacityPopup = component(DarkPopupWindow, {
  defaultViewModel: SingleNumberModel,
  name: 'opacity popup',
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['Opacity', null]
    }]
  }, add({
    name: 'footer',
    borderColor: Color.rgb(97, 106, 107),
    borderWidth: 0,
    extent: pt(241, 50.3),
    fill: Color.rgba(0, 0, 0, 0),
    submorphs: [part(DarkNumberIconWidget, {
      name: 'value input',
      position: pt(11.3, 14),
      viewModel: {
        min: 0,
        max: 1,
        scaleFactor: 100,
        unit: '%'
      },
      tooltip: 'Object opacity',
      submorphs: [{
        name: 'interactive label',
        fontSize: 14,
        fontFamily: 'Material Icons',
        textAndAttributes: ['\ue91c', null]
      }]
    })]
  })]
});

const CursorPopup = component(DarkPopupWindow, {
  defaultViewModel: SingleSelectionModel,
  name: 'cursor popup',
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['Cursor', null]
    }]
  }, add({
    name: 'footer',
    borderColor: Color.rgb(97, 106, 107),
    borderWidth: 0,
    extent: pt(241, 50.3),
    fill: Color.rgba(0, 0, 0, 0),
    submorphs: [part(EnumSelector, {
      name: 'selection input',
      tooltip: 'Selector Cursor',
      viewModel: {
        openListInWorld: true,
        listAlign: 'selection',
        items: Morph.properties.nativeCursor.values,
        listMaster: DarkThemeList,
        listHeight: 1000
      },
      extent: pt(210.4, 23.3),
      master: EnumSelector,
      position: pt(15.4, 13.5),
      submorphs: [add({
        type: Label,
        name: 'interactive label',
        fill: Color.rgba(229, 231, 233, 0),
        fontColor: Color.rgb(255, 255, 255),
        fontFamily: 'Material Icons',
        nativeCursor: 'pointer',
        padding: rect(6, 0, -6, 0),
        reactsToPointer: false,
        textAndAttributes: Icon.textAttribute('mouse-pointer', { fontSize: 16 })
      }, 'label')]
    })]
  })]
});

const TiltPopup = component(DarkPopupWindow, {
  defaultViewModel: SingleNumberModel,
  name: 'tilt popup',
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['Tilt ', null, '(Rotate along X-Axis)', { fontWeight: '400' }]
    }]
  }, add({
    name: 'footer',
    borderColor: Color.rgb(97, 106, 107),
    borderWidth: 0,
    extent: pt(241, 50.3),
    fill: Color.rgba(0, 0, 0, 0),
    submorphs: [part(DarkNumberIconWidget, {
      name: 'value input',
      extent: pt(90, 22),
      viewModel: {
        unit: '°',
        min: 0,
        scaleFactor: 180,
        max: 1
      },
      position: pt(11.3, 14),
      tooltip: 'Object Tilt',
      submorphs: [{
        name: 'interactive label',
        textAndAttributes: ['X ', { fontSize: 16, fontFamily: 'IBM Plex Mono' }, ...Icon.textAttribute('undo', { fontSize: 14, paddingTop: '3px' })]
      }]
    })]
  })]
});

const FlipPopup = component(DarkPopupWindow, {
  defaultViewModel: SingleNumberModel,
  name: 'flip popup',
  submorphs: [{
    name: 'header menu',
    submorphs: [{
      name: 'title',
      textAndAttributes: ['Flip ', null, '(Rotate along Y-Axis)', { fontWeight: '400' }]
    }]
  }, add({
    name: 'footer',
    borderColor: Color.rgb(97, 106, 107),
    borderWidth: 0,
    extent: pt(241, 50.3),
    fill: Color.rgba(0, 0, 0, 0),
    submorphs: [part(DarkNumberIconWidget, {
      name: 'value input',
      extent: pt(90, 22),
      viewModel: {
        unit: '°',
        min: 0,
        scaleFactor: 180,
        max: 1
      },
      position: pt(11.3, 14),
      tooltip: 'Object Flip',
      submorphs: [{
        name: 'interactive label',
        textAndAttributes: [
          'Y ', { fontSize: 16, fontFamily: 'IBM Plex Mono' },
          ...Icon.textAttribute('undo', { fontSize: 14, paddingTop: '3px' })]
      }]
    })]
  })]
});

export { BlurPopup, ShadowPopup, ShadowPopupLight, InsetShadowPopup, OpacityPopup, CursorPopup, TiltPopup, NumberWidgetLight, NumberPopupLight, PaddingPopup, PositionPopupLight, FlipPopup };