| /**
 * CropRect.js
 *
 * Released under LGPL License.
 * Copyright (c) 1999-2017 Ephox Corp. All rights reserved
 *
 * License: http://www.tinymce.com/license
 * Contributing: http://www.tinymce.com/contributing
 */
define(
  'tinymce.plugins.imagetools.ui.CropRect',
  [
    'tinymce.core.dom.DomQuery',
    'tinymce.core.geom.Rect',
    'tinymce.core.ui.Factory',
    'tinymce.core.util.Observable',
    'tinymce.core.util.Tools',
    'tinymce.core.util.VK'
  ],
  function (DomQuery, Rect, Factory, Observable, Tools, VK) {
    var count = 0;
    return function (currentRect, viewPortRect, clampRect, containerElm, action) {
      var instance, handles, dragHelpers, blockers, prefix = 'mce-', id = prefix + 'crid-' + (count++);
      handles = [
        { name: 'move', xMul: 0, yMul: 0, deltaX: 1, deltaY: 1, deltaW: 0, deltaH: 0, label: 'Crop Mask' },
        { name: 'nw', xMul: 0, yMul: 0, deltaX: 1, deltaY: 1, deltaW: -1, deltaH: -1, label: 'Top Left Crop Handle' },
        { name: 'ne', xMul: 1, yMul: 0, deltaX: 0, deltaY: 1, deltaW: 1, deltaH: -1, label: 'Top Right Crop Handle' },
        { name: 'sw', xMul: 0, yMul: 1, deltaX: 1, deltaY: 0, deltaW: -1, deltaH: 1, label: 'Bottom Left Crop Handle' },
        { name: 'se', xMul: 1, yMul: 1, deltaX: 0, deltaY: 0, deltaW: 1, deltaH: 1, label: 'Bottom Right Crop Handle' }
      ];
      blockers = ["top", "right", "bottom", "left"];
      function getAbsoluteRect(outerRect, relativeRect) {
        return {
          x: relativeRect.x + outerRect.x,
          y: relativeRect.y + outerRect.y,
          w: relativeRect.w,
          h: relativeRect.h
        };
      }
      function getRelativeRect(outerRect, innerRect) {
        return {
          x: innerRect.x - outerRect.x,
          y: innerRect.y - outerRect.y,
          w: innerRect.w,
          h: innerRect.h
        };
      }
      function getInnerRect() {
        return getRelativeRect(clampRect, currentRect);
      }
      function moveRect(handle, startRect, deltaX, deltaY) {
        var x, y, w, h, rect;
        x = startRect.x;
        y = startRect.y;
        w = startRect.w;
        h = startRect.h;
        x += deltaX * handle.deltaX;
        y += deltaY * handle.deltaY;
        w += deltaX * handle.deltaW;
        h += deltaY * handle.deltaH;
        if (w < 20) {
          w = 20;
        }
        if (h < 20) {
          h = 20;
        }
        rect = currentRect = Rect.clamp({ x: x, y: y, w: w, h: h }, clampRect, handle.name === 'move');
        rect = getRelativeRect(clampRect, rect);
        instance.fire('updateRect', { rect: rect });
        setInnerRect(rect);
      }
      function render() {
        function createDragHelper(handle) {
          var startRect;
          var DragHelper = Factory.get('DragHelper');
          return new DragHelper(id, {
            document: containerElm.ownerDocument,
            handle: id + '-' + handle.name,
            start: function () {
              startRect = currentRect;
            },
            drag: function (e) {
              moveRect(handle, startRect, e.deltaX, e.deltaY);
            }
          });
        }
        DomQuery(
          '<div id="' + id + '" class="' + prefix + 'croprect-container"' +
          ' role="grid" aria-dropeffect="execute">'
        ).appendTo(containerElm);
        Tools.each(blockers, function (blocker) {
          DomQuery('#' + id, containerElm).append(
            '<div id="' + id + '-' + blocker + '"class="' + prefix + 'croprect-block" style="display: none" data-mce-bogus="all">'
          );
        });
        Tools.each(handles, function (handle) {
          DomQuery('#' + id, containerElm).append(
            '<div id="' + id + '-' + handle.name + '" class="' + prefix +
            'croprect-handle ' + prefix + 'croprect-handle-' + handle.name + '"' +
            'style="display: none" data-mce-bogus="all" role="gridcell" tabindex="-1"' +
            ' aria-label="' + handle.label + '" aria-grabbed="false">'
          );
        });
        dragHelpers = Tools.map(handles, createDragHelper);
        repaint(currentRect);
        DomQuery(containerElm).on('focusin focusout', function (e) {
          DomQuery(e.target).attr('aria-grabbed', e.type === 'focus');
        });
        DomQuery(containerElm).on('keydown', function (e) {
          var activeHandle;
          Tools.each(handles, function (handle) {
            if (e.target.id === id + '-' + handle.name) {
              activeHandle = handle;
              return false;
            }
          });
          function moveAndBlock(evt, handle, startRect, deltaX, deltaY) {
            evt.stopPropagation();
            evt.preventDefault();
            moveRect(activeHandle, startRect, deltaX, deltaY);
          }
          switch (e.keyCode) {
            case VK.LEFT:
              moveAndBlock(e, activeHandle, currentRect, -10, 0);
              break;
            case VK.RIGHT:
              moveAndBlock(e, activeHandle, currentRect, 10, 0);
              break;
            case VK.UP:
              moveAndBlock(e, activeHandle, currentRect, 0, -10);
              break;
            case VK.DOWN:
              moveAndBlock(e, activeHandle, currentRect, 0, 10);
              break;
            case VK.ENTER:
            case VK.SPACEBAR:
              e.preventDefault();
              action();
              break;
          }
        });
      }
      function toggleVisibility(state) {
        var selectors;
        selectors = Tools.map(handles, function (handle) {
          return '#' + id + '-' + handle.name;
        }).concat(Tools.map(blockers, function (blocker) {
          return '#' + id + '-' + blocker;
        })).join(',');
        if (state) {
          DomQuery(selectors, containerElm).show();
        } else {
          DomQuery(selectors, containerElm).hide();
        }
      }
      function repaint(rect) {
        function updateElementRect(name, rect) {
          if (rect.h < 0) {
            rect.h = 0;
          }
          if (rect.w < 0) {
            rect.w = 0;
          }
          DomQuery('#' + id + '-' + name, containerElm).css({
            left: rect.x,
            top: rect.y,
            width: rect.w,
            height: rect.h
          });
        }
        Tools.each(handles, function (handle) {
          DomQuery('#' + id + '-' + handle.name, containerElm).css({
            left: rect.w * handle.xMul + rect.x,
            top: rect.h * handle.yMul + rect.y
          });
        });
        updateElementRect('top', { x: viewPortRect.x, y: viewPortRect.y, w: viewPortRect.w, h: rect.y - viewPortRect.y });
        updateElementRect('right', { x: rect.x + rect.w, y: rect.y, w: viewPortRect.w - rect.x - rect.w + viewPortRect.x, h: rect.h });
        updateElementRect('bottom', {
          x: viewPortRect.x,
          y: rect.y + rect.h,
          w: viewPortRect.w,
          h: viewPortRect.h - rect.y - rect.h + viewPortRect.y
        });
        updateElementRect('left', { x: viewPortRect.x, y: rect.y, w: rect.x - viewPortRect.x, h: rect.h });
        updateElementRect('move', rect);
      }
      function setRect(rect) {
        currentRect = rect;
        repaint(currentRect);
      }
      function setViewPortRect(rect) {
        viewPortRect = rect;
        repaint(currentRect);
      }
      function setInnerRect(rect) {
        setRect(getAbsoluteRect(clampRect, rect));
      }
      function setClampRect(rect) {
        clampRect = rect;
        repaint(currentRect);
      }
      function destroy() {
        Tools.each(dragHelpers, function (helper) {
          helper.destroy();
        });
        dragHelpers = [];
      }
      render(containerElm);
      instance = Tools.extend({
        toggleVisibility: toggleVisibility,
        setClampRect: setClampRect,
        setRect: setRect,
        getInnerRect: getInnerRect,
        setInnerRect: setInnerRect,
        setViewPortRect: setViewPortRect,
        destroy: destroy
      }, Observable);
      return instance;
    };
  }
);
 |