From cd6c8afddca6f6b6732ae599c8da91db4c7face5 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 10:06:34 -0800 Subject: [PATCH 01/12] [Fixed Position] Add selection/handle markup Add initial markup for selection highlight, drag handles to allow position/resize of elements in Fixed Position view, WTD-882. --- .../features/layout/res/templates/fixed.html | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/platform/features/layout/res/templates/fixed.html b/platform/features/layout/res/templates/fixed.html index 9d6d741a62..502b5e2579 100644 --- a/platform/features/layout/res/templates/fixed.html +++ b/platform/features/layout/res/templates/fixed.html @@ -18,9 +18,24 @@ ng-class="{ test: controller.selected(element) }" ng-style="element.style" ng-click="controller.select(element)" - ng-model="element" - mct-drag-down="controller.startDrag(element); controller.select(element)" - mct-drag="controller.continueDrag(delta)" - mct-drag-up="controller.endDrag()"> + ng-model="element"> + + + +
+
+
+ O +
+
+ \ No newline at end of file From b871e0da914c4add40d47f922442fecca8358dde Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 10:12:33 -0800 Subject: [PATCH 02/12] [Fixed Position] Update FixedController spec Update FixedController spec to support updates to template for selection highlight, drag handles; WTD-882. --- .../layout/test/FixedControllerSpec.js | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/platform/features/layout/test/FixedControllerSpec.js b/platform/features/layout/test/FixedControllerSpec.js index c8b943cc62..9d2ffa9e95 100644 --- a/platform/features/layout/test/FixedControllerSpec.js +++ b/platform/features/layout/test/FixedControllerSpec.js @@ -167,6 +167,19 @@ define( expect(controller.selected(elements[1])).toBeTruthy(); }); + it("allows selection retrieval", function () { + // selected with no arguments should give the current + // selection + var elements; + + testModel.modified = 1; + findWatch("model.modified")(testModel.modified); + + elements = controller.getElements(); + controller.select(elements[1]); + expect(controller.selected()).toEqual(elements[1]); + }); + it("allows selections to be cleared", function () { var elements; @@ -288,6 +301,27 @@ define( // elements to size SVGs appropriately expect(controller.getGridSize()).toEqual(testGrid); }); + + it("exposes drag handles", function () { + var handles; + + // Select something so that drag handles are expected + testModel.modified = 1; + findWatch("model.modified")(testModel.modified); + controller.select(controller.getElements()[1]); + + // Should have a non-empty array of handles + handles = controller.handles(); + expect(handles).toEqual(jasmine.any(Array)); + expect(handles.length).not.toEqual(0); + + // And they should have start/continue/end drag methods + handles.forEach(function (handle) { + expect(handle.startDrag).toEqual(jasmine.any(Function)); + expect(handle.continueDrag).toEqual(jasmine.any(Function)); + expect(handle.endDrag).toEqual(jasmine.any(Function)); + }); + }); }); } ); \ No newline at end of file From abad8df135fd26429bd25205bc5a4b3371c3f1ad Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 10:33:36 -0800 Subject: [PATCH 03/12] [Fixed Position] Begin updating controller Begin updating controller to expose handles for dragging, resizing elements in fixed position view. WTD-882. --- .../features/layout/src/FixedController.js | 30 +++++++++-- .../features/layout/src/FixedDragHandle.js | 52 +++++++++++++++++++ .../layout/src/elements/ElementProxy.js | 13 ++++- .../layout/src/elements/ResizeHandle.js | 43 +++++++++++++++ 4 files changed, 132 insertions(+), 6 deletions(-) create mode 100644 platform/features/layout/src/FixedDragHandle.js create mode 100644 platform/features/layout/src/elements/ResizeHandle.js diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index afef3355b4..d40699ff8a 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -1,8 +1,8 @@ /*global define*/ define( - ['./LayoutDrag', './LayoutSelection', './FixedProxy', './elements/ElementProxies'], - function (LayoutDrag, LayoutSelection, FixedProxy, ElementProxies) { + ['./LayoutSelection', './FixedProxy', './elements/ElementProxies', './FixedDragHandle'], + function (LayoutSelection, FixedProxy, ElementProxies, FixedDragHandle) { "use strict"; var DEFAULT_DIMENSIONS = [ 2, 1 ], @@ -27,6 +27,7 @@ define( names = {}, // Cache names by ID values = {}, // Cache values by ID elementProxiesById = {}, + handles = [], selection; // Refresh cell styles (e.g. because grid extent changed) @@ -52,6 +53,16 @@ define( } } + // Generate a specific drag handle + function generateDragHandle(elementHandle) { + return new FixedDragHandle(elementHandle, gridSize, $scope.commit); + } + + // Generate drag handles for an element + function generateDragHandles(element) { + return element.handles().map(generateDragHandle); + } + // Convert from element x/y/width/height to an // apropriate ng-style argument, to position elements. function convertPosition(elementProxy) { @@ -275,11 +286,13 @@ define( return elementProxies; }, /** - * Check if the element is currently selected. + * Check if the element is currently selected, or (if no + * argument is supplied) get the currently selected element. * @returns {boolean} true if selected */ selected: function (element) { - return selection && selection.selected(element); + return selection && ((arguments.length > 0) ? + selection.selected(element) : selection.get()); }, /** * Set the active user selection in this view. @@ -288,6 +301,7 @@ define( select: function (element) { if (selection) { selection.select(element); + handles = generateDragHandles(element); } }, /** @@ -296,8 +310,16 @@ define( clearSelection: function () { if (selection) { selection.deselect(); + handles = []; } }, + /** + * Get drag handles. + * @returns {Array} drag handles for the current selection + */ + handles: function () { + return handles; + }, /** * Start a drag gesture to move/resize a frame. * diff --git a/platform/features/layout/src/FixedDragHandle.js b/platform/features/layout/src/FixedDragHandle.js new file mode 100644 index 0000000000..d01af84223 --- /dev/null +++ b/platform/features/layout/src/FixedDragHandle.js @@ -0,0 +1,52 @@ +/*global define*/ + +define( + [], + function () { + 'use strict'; + + + // 8 by 8 pixels + var DRAG_HANDLE_SIZE = [ 8, 8 ]; + + /** + * Template-displayable drag handle for an element in fixed + * position mode. + * @constructor + */ + function FixedDragHandle(elementHandle, gridSize, commit) { + var self = {}; + + function getStyle() { + // Adjust from grid to pixel coordinates + var x = elementHandle.x() * gridSize[0], + y = elementHandle.y() * gridSize[1]; + + // Convert to a CSS style centered on that point + return { + left: (x - DRAG_HANDLE_SIZE[0] / 2) + 'px', + right: (x - DRAG_HANDLE_SIZE[1] / 2) + 'px', + width: DRAG_HANDLE_SIZE[0] + 'px', + height: DRAG_HANDLE_SIZE[1] + 'px' + }; + } + + function noop() { + + } + + return { + /** + * Get a CSS style to position this drag handle. + * @returns CSS style object (for `ng-style`) + */ + style: getStyle, + startDrag: noop, + continueDrag: noop, + endDrag: noop + }; + } + + return FixedDragHandle; + } +); \ No newline at end of file diff --git a/platform/features/layout/src/elements/ElementProxy.js b/platform/features/layout/src/elements/ElementProxy.js index 7d5fa3540b..a041acf092 100644 --- a/platform/features/layout/src/elements/ElementProxy.js +++ b/platform/features/layout/src/elements/ElementProxy.js @@ -1,8 +1,8 @@ /*global define*/ define( - ['./AccessorMutator'], - function (AccessorMutator) { + ['./AccessorMutator', './ResizeHandle'], + function (AccessorMutator, ResizeHandle) { "use strict"; // Index deltas for changes in order @@ -29,6 +29,8 @@ define( * @param {Array} elements the full array of elements */ function ElementProxy(element, index, elements) { + var handles = [ new ResizeHandle(element, 1, 1) ]; + return { /** * The element as stored in the view configuration. @@ -97,6 +99,13 @@ define( if (elements[index] === element) { elements.splice(index, 1); } + }, + /** + * Get handles to control specific features of this element, + * e.g. corner size. + */ + handles: function () { + return handles; } }; } diff --git a/platform/features/layout/src/elements/ResizeHandle.js b/platform/features/layout/src/elements/ResizeHandle.js new file mode 100644 index 0000000000..bd9a3687e9 --- /dev/null +++ b/platform/features/layout/src/elements/ResizeHandle.js @@ -0,0 +1,43 @@ +/*global define*/ +define( + [], + function () { + 'use strict'; + + /** + * Handle for changing width/height properties of an element. + * This is used to support drag handles for different + * element types in a fixed position view. + * @constructor + */ + function ResizeHandle(element, minWidth, minHeight) { + // Ensure reasonable defaults + minWidth = minWidth || 0; + minHeight = minHeight || 0; + + return { + x: function (value) { + if (arguments.length > 0) { + element.width = Math.max( + minWidth, + value - element.x + ); + } + return element.x + element.width; + }, + y: function (value) { + if (arguments.length > 0) { + element.height = Math.max( + minHeight, + value - element.y + ); + } + return element.y + element.height; + } + }; + } + + return ResizeHandle; + + } +); \ No newline at end of file From 5e3d5fd00ff2b59b52437ded94d202a1b30aa61b Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 11:00:36 -0800 Subject: [PATCH 04/12] [Fixed Position] Initially implement handles Initially implement handles for resizing elements in a fixed position view, WTD-882. --- .../features/layout/res/templates/fixed.html | 4 +- .../features/layout/src/FixedDragHandle.js | 52 ++++++++++++++-- .../layout/test/FixedDragHandleSpec.js | 62 +++++++++++++++++++ .../layout/test/elements/ResizeHandleSpec.js | 59 ++++++++++++++++++ platform/features/layout/test/suite.json | 2 + 5 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 platform/features/layout/test/FixedDragHandleSpec.js create mode 100644 platform/features/layout/test/elements/ResizeHandleSpec.js diff --git a/platform/features/layout/res/templates/fixed.html b/platform/features/layout/res/templates/fixed.html index 502b5e2579..dbe03ef22a 100644 --- a/platform/features/layout/res/templates/fixed.html +++ b/platform/features/layout/res/templates/fixed.html @@ -24,13 +24,15 @@
diff --git a/platform/features/layout/src/FixedDragHandle.js b/platform/features/layout/src/FixedDragHandle.js index d01af84223..a57e6e48d3 100644 --- a/platform/features/layout/src/FixedDragHandle.js +++ b/platform/features/layout/src/FixedDragHandle.js @@ -15,8 +15,10 @@ define( * @constructor */ function FixedDragHandle(elementHandle, gridSize, commit) { - var self = {}; + var self = {}, + dragging; + // Generate ng-style-appropriate style for positioning function getStyle() { // Adjust from grid to pixel coordinates var x = elementHandle.x() * gridSize[0], @@ -25,14 +27,39 @@ define( // Convert to a CSS style centered on that point return { left: (x - DRAG_HANDLE_SIZE[0] / 2) + 'px', - right: (x - DRAG_HANDLE_SIZE[1] / 2) + 'px', + top: (y - DRAG_HANDLE_SIZE[1] / 2) + 'px', width: DRAG_HANDLE_SIZE[0] + 'px', height: DRAG_HANDLE_SIZE[1] + 'px' }; } - function noop() { + // Begin a drag gesture + function startDrag() { + // Cache initial x/y positions + dragging = { x: elementHandle.x(), y: elementHandle.y() }; + } + // Reposition during drag + function continueDrag(delta) { + if (dragging) { + // Update x/y positions (snapping to grid) + elementHandle.x( + dragging.x + Math.round(delta[0] / gridSize[0]) + ); + elementHandle.y( + dragging.y + Math.round(delta[1] / gridSize[1]) + ); + } + } + + // Conclude a drag gesture + function endDrag() { + // Clear cached state + dragging = undefined; + // Mark change as complete + if (commit) { + commit("Dragged handle."); + } } return { @@ -41,9 +68,22 @@ define( * @returns CSS style object (for `ng-style`) */ style: getStyle, - startDrag: noop, - continueDrag: noop, - endDrag: noop + /** + * Start a drag gesture. This should be called when a drag + * begins to track initial state. + */ + startDrag: startDrag, + /** + * Continue a drag gesture; update x/y positions. + * @param {number[]} delta x/y pixel difference since drag + * started + */ + continueDrag: continueDrag, + /** + * End a drag gesture. This should be callled when a drag + * concludes to trigger commit of changes. + */ + endDrag: endDrag }; } diff --git a/platform/features/layout/test/FixedDragHandleSpec.js b/platform/features/layout/test/FixedDragHandleSpec.js new file mode 100644 index 0000000000..ae0b8a42d6 --- /dev/null +++ b/platform/features/layout/test/FixedDragHandleSpec.js @@ -0,0 +1,62 @@ +/*global define,describe,it,expect,beforeEach,jasmine,xit*/ + +define( + ['../src/FixedDragHandle'], + function (FixedDragHandle) { + "use strict"; + + var TEST_GRID_SIZE = [ 13, 33 ]; + + describe("A fixed position drag handle", function () { + var mockElementHandle, + mockCommit, + handle; + + beforeEach(function () { + mockElementHandle = jasmine.createSpyObj( + 'elementHandle', + [ 'x', 'y' ] + ); + mockCommit = jasmine.createSpy('commit'); + + mockElementHandle.x.andReturn(6); + mockElementHandle.y.andReturn(8); + + handle = new FixedDragHandle( + mockElementHandle, + TEST_GRID_SIZE, + mockCommit + ); + }); + + it("provides a style for positioning", function () { + var style = handle.style(); + // 6 grid coords * 13 pixels - 4 pixels for centering + expect(style.left).toEqual('74px'); + // 8 grid coords * 33 pixels - 4 pixels for centering + expect(style.top).toEqual('260px'); + }); + + it("allows handles to be dragged", function () { + handle.startDrag(); + handle.continueDrag([ 16, 8 ]); + + // Should update x/y, snapped to grid + expect(mockElementHandle.x).toHaveBeenCalledWith(7); + expect(mockElementHandle.y).toHaveBeenCalledWith(8); + + handle.continueDrag([ -16, -35 ]); + + // Should have interpreted relative to initial state + expect(mockElementHandle.x).toHaveBeenCalledWith(5); + expect(mockElementHandle.y).toHaveBeenCalledWith(7); + + // Finally, ending drag should commit + expect(mockCommit).not.toHaveBeenCalled(); + handle.endDrag(); + expect(mockCommit).toHaveBeenCalled(); + }); + + }); + } +); \ No newline at end of file diff --git a/platform/features/layout/test/elements/ResizeHandleSpec.js b/platform/features/layout/test/elements/ResizeHandleSpec.js new file mode 100644 index 0000000000..49168aa5cd --- /dev/null +++ b/platform/features/layout/test/elements/ResizeHandleSpec.js @@ -0,0 +1,59 @@ +/*global define,describe,it,expect,beforeEach,jasmine,xit*/ + +define( + ['../../src/elements/ResizeHandle'], + function (ResizeHandle) { + "use strict"; + + var TEST_MIN_WIDTH = 4, TEST_MIN_HEIGHT = 2; + + describe("A fixed position drag handle", function () { + var testElement, + handle; + + beforeEach(function () { + testElement = { + x: 3, + y: 42, + width: 30, + height: 36 + }; + + handle = new ResizeHandle( + testElement, + TEST_MIN_WIDTH, + TEST_MIN_HEIGHT + ); + }); + + it("provides x/y grid coordinates for lower-right corner", function () { + expect(handle.x()).toEqual(33); + expect(handle.y()).toEqual(78); + }); + + it("changes width of an element", function () { + handle.x(30); + // Should change width, not x + expect(testElement.x).toEqual(3); + expect(testElement.width).toEqual(27); + }); + + it("changes height of an element", function () { + handle.y(60); + // Should change height, not y + expect(testElement.y).toEqual(42); + expect(testElement.height).toEqual(18); + }); + + it("enforces minimum width/height", function () { + handle.x(testElement.x); + handle.y(testElement.y); + expect(testElement.x).toEqual(3); + expect(testElement.y).toEqual(42); + expect(testElement.width).toEqual(TEST_MIN_WIDTH); + expect(testElement.height).toEqual(TEST_MIN_HEIGHT); + }); + + }); + } +); \ No newline at end of file diff --git a/platform/features/layout/test/suite.json b/platform/features/layout/test/suite.json index 8f0eec06c3..684c8fda12 100644 --- a/platform/features/layout/test/suite.json +++ b/platform/features/layout/test/suite.json @@ -1,5 +1,6 @@ [ "FixedController", + "FixedDragHandle", "FixedProxy", "LayoutController", "LayoutDrag", @@ -10,6 +11,7 @@ "elements/ElementProxies", "elements/ElementProxy", "elements/LineProxy", + "elements/ResizeHandle", "elements/TelemetryProxy", "elements/TextProxy" ] \ No newline at end of file From c306865d873db28de6a1ab0e0da000d394ac1f2f Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 11:22:52 -0800 Subject: [PATCH 05/12] [Fixed Position] Update style during resize Update element style during resize, WTD-882. --- .../features/layout/res/templates/fixed.html | 5 ++-- .../features/layout/src/FixedController.js | 30 ++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/platform/features/layout/res/templates/fixed.html b/platform/features/layout/res/templates/fixed.html index dbe03ef22a..ea48e019b7 100644 --- a/platform/features/layout/res/templates/fixed.html +++ b/platform/features/layout/res/templates/fixed.html @@ -15,7 +15,6 @@ style="position: absolute;" key="element.template" parameters="{ gridSize: controller.getGridSize() }" - ng-class="{ test: controller.selected(element) }" ng-style="element.style" ng-click="controller.select(element)" ng-model="element"> @@ -26,7 +25,7 @@
@@ -34,7 +33,7 @@ style="position: absolute;" ng-style="handle.style()" mct-drag-down="handle.startDrag()" - mct-drag="handle.continueDrag(delta)" + mct-drag="handle.continueDrag(delta); controller.updateStyle(controller.selected())" mct-drag-up="handle.endDrag()"> O
diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index d40699ff8a..d198041e69 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -63,6 +63,16 @@ define( return element.handles().map(generateDragHandle); } + // Select an element + function select(element) { + if (selection) { + // Update selection... + selection.select(element); + // ...as well as drag handles + handles = generateDragHandles(element); + } + } + // Convert from element x/y/width/height to an // apropriate ng-style argument, to position elements. function convertPosition(elementProxy) { @@ -132,7 +142,7 @@ define( if (selection) { selection.deselect(); if (index > -1) { - selection.select(elementProxies[index]); + select(elementProxies[index]); } } @@ -194,9 +204,7 @@ define( // Refresh displayed elements refreshElements(); // Select the newly-added element - if (selection) { - selection.select(elementProxies[elementProxies.length - 1]); - } + select(elementProxies[elementProxies.length - 1]); // Mark change as persistable if ($scope.commit) { $scope.commit("Dropped an element."); @@ -298,12 +306,7 @@ define( * Set the active user selection in this view. * @param element the element to select */ - select: function (element) { - if (selection) { - selection.select(element); - handles = generateDragHandles(element); - } - }, + select: select, /** * Clear the current user selection. */ @@ -320,6 +323,13 @@ define( handles: function () { return handles; }, + /** + * Update the style (for position/sizing) for the specified + * element. + */ + updateStyle: function (element) { + element.style = convertPosition(element); + }, /** * Start a drag gesture to move/resize a frame. * From e56bac777edec8982a93aa49067bca4003ca6bc9 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 11:29:52 -0800 Subject: [PATCH 06/12] [Fixed Position] Use FixedDragHandle for moves Use FixedDragHandle to deal with repositioning, WTD-882. --- .../features/layout/res/templates/fixed.html | 8 +- .../features/layout/src/FixedController.js | 106 ++++++------------ .../features/layout/src/FixedDragHandle.js | 6 +- .../layout/test/FixedDragHandleSpec.js | 6 + 4 files changed, 49 insertions(+), 77 deletions(-) diff --git a/platform/features/layout/res/templates/fixed.html b/platform/features/layout/res/templates/fixed.html index ea48e019b7..9ca5f981c7 100644 --- a/platform/features/layout/res/templates/fixed.html +++ b/platform/features/layout/res/templates/fixed.html @@ -24,16 +24,16 @@
O
diff --git a/platform/features/layout/src/FixedController.js b/platform/features/layout/src/FixedController.js index d198041e69..cec05fb800 100644 --- a/platform/features/layout/src/FixedController.js +++ b/platform/features/layout/src/FixedController.js @@ -28,6 +28,7 @@ define( values = {}, // Cache values by ID elementProxiesById = {}, handles = [], + moveHandle, selection; // Refresh cell styles (e.g. because grid extent changed) @@ -53,9 +54,34 @@ define( } } + // Convert from element x/y/width/height to an + // apropriate ng-style argument, to position elements. + function convertPosition(elementProxy) { + // Multiply position/dimensions by grid size + return { + left: (gridSize[0] * elementProxy.x()) + 'px', + top: (gridSize[1] * elementProxy.y()) + 'px', + width: (gridSize[0] * elementProxy.width()) + 'px', + height: (gridSize[1] * elementProxy.height()) + 'px' + }; + } + + // Update the style for a selected element + function updateSelectionStyle() { + var element = selection && selection.get(); + if (element) { + element.style = convertPosition(element); + } + } + // Generate a specific drag handle function generateDragHandle(elementHandle) { - return new FixedDragHandle(elementHandle, gridSize, $scope.commit); + return new FixedDragHandle( + elementHandle, + gridSize, + updateSelectionStyle, + $scope.commit + ); } // Generate drag handles for an element @@ -68,23 +94,12 @@ define( if (selection) { // Update selection... selection.select(element); - // ...as well as drag handles + // ...as well as move, resize handles + moveHandle = generateDragHandle(element); handles = generateDragHandles(element); } } - // Convert from element x/y/width/height to an - // apropriate ng-style argument, to position elements. - function convertPosition(elementProxy) { - // Multiply position/dimensions by grid size - return { - left: (gridSize[0] * elementProxy.x()) + 'px', - top: (gridSize[1] * elementProxy.y()) + 'px', - width: (gridSize[0] * elementProxy.width()) + 'px', - height: (gridSize[1] * elementProxy.height()) + 'px' - }; - } - // Update the displayed value for this object function updateValue(telemetryObject) { var id = telemetryObject && telemetryObject.getId(); @@ -314,6 +329,7 @@ define( if (selection) { selection.deselect(); handles = []; + moveHandle = undefined; } }, /** @@ -324,65 +340,11 @@ define( return handles; }, /** - * Update the style (for position/sizing) for the specified - * element. + * Get the handle to handle dragging to reposition an element. + * @returns {FixedDragHandle} the drag handle */ - updateStyle: function (element) { - element.style = convertPosition(element); - }, - /** - * Start a drag gesture to move/resize a frame. - * - * The provided position and dimensions factors will determine - * whether this is a move or a resize, and what type it - * will be. For instance, a position factor of [1, 1] - * will move a frame along with the mouse as the drag - * proceeds, while a dimension factor of [0, 0] will leave - * dimensions unchanged. Combining these in different - * ways results in different handles; a position factor of - * [1, 0] and a dimensions factor of [-1, 0] will implement - * a left-edge resize, as the horizontal position will move - * with the mouse while the horizontal dimensions shrink in - * kind (and vertical properties remain unmodified.) - * - * @param element the raw (undecorated) element to drag - */ - startDrag: function (element) { - // Only allow dragging in edit mode - if ($scope.domainObject && - $scope.domainObject.hasCapability('editor')) { - dragging = { - element: element, - x: element.x(), - y: element.y() - }; - } - }, - /** - * Continue an active drag gesture. - * @param {number[]} delta the offset, in pixels, - * of the current pointer position, relative - * to its position when the drag started - */ - continueDrag: function (delta) { - if (dragging) { - // Update x/y values - dragging.element.x(dragging.x + Math.round(delta[0] / gridSize[0])); - dragging.element.y(dragging.y + Math.round(delta[1] / gridSize[1])); - // Update display position - dragging.element.style = convertPosition(dragging.element); - } - }, - /** - * End the active drag gesture. This will update the - * view configuration. - */ - endDrag: function () { - // Mark this object as dirty to encourage persistence - if (dragging && $scope.commit) { - dragging = undefined; - $scope.commit("Moved element."); - } + moveHandle: function () { + return moveHandle; } }; diff --git a/platform/features/layout/src/FixedDragHandle.js b/platform/features/layout/src/FixedDragHandle.js index a57e6e48d3..261912daef 100644 --- a/platform/features/layout/src/FixedDragHandle.js +++ b/platform/features/layout/src/FixedDragHandle.js @@ -14,7 +14,7 @@ define( * position mode. * @constructor */ - function FixedDragHandle(elementHandle, gridSize, commit) { + function FixedDragHandle(elementHandle, gridSize, update, commit) { var self = {}, dragging; @@ -49,6 +49,10 @@ define( elementHandle.y( dragging.y + Math.round(delta[1] / gridSize[1]) ); + // Invoke update callback + if (update) { + update(); + } } } diff --git a/platform/features/layout/test/FixedDragHandleSpec.js b/platform/features/layout/test/FixedDragHandleSpec.js index ae0b8a42d6..674bb8794b 100644 --- a/platform/features/layout/test/FixedDragHandleSpec.js +++ b/platform/features/layout/test/FixedDragHandleSpec.js @@ -9,6 +9,7 @@ define( describe("A fixed position drag handle", function () { var mockElementHandle, + mockUpdate, mockCommit, handle; @@ -17,6 +18,7 @@ define( 'elementHandle', [ 'x', 'y' ] ); + mockUpdate = jasmine.createSpy('update'); mockCommit = jasmine.createSpy('commit'); mockElementHandle.x.andReturn(6); @@ -25,6 +27,7 @@ define( handle = new FixedDragHandle( mockElementHandle, TEST_GRID_SIZE, + mockUpdate, mockCommit ); }); @@ -51,6 +54,9 @@ define( expect(mockElementHandle.x).toHaveBeenCalledWith(5); expect(mockElementHandle.y).toHaveBeenCalledWith(7); + // Should have called update once per continueDrag + expect(mockUpdate.calls.length).toEqual(2); + // Finally, ending drag should commit expect(mockCommit).not.toHaveBeenCalled(); handle.endDrag(); From 5ba58ef05650b9df42af98252697cab9ab879afc Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 11:38:46 -0800 Subject: [PATCH 07/12] [Fixed Position] Keep elements in view Prevent elements from being positioned at negative x/y locations in a fixed position view, WTD-882. --- .../layout/src/elements/AccessorMutator.js | 16 ++++++++++++++-- .../features/layout/src/elements/ElementProxy.js | 9 +++++++-- .../features/layout/src/elements/LineProxy.js | 4 ++-- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/platform/features/layout/src/elements/AccessorMutator.js b/platform/features/layout/src/elements/AccessorMutator.js index ac296ddd05..284bbd9b18 100644 --- a/platform/features/layout/src/elements/AccessorMutator.js +++ b/platform/features/layout/src/elements/AccessorMutator.js @@ -8,14 +8,26 @@ define( /** * Utility function for creating getter-setter functions, * since these are frequently useful for element proxies. + * + * An optional third argument may be supplied in order to + * constrain or modify arguments when using as a setter; + * this argument is a function which takes two arguments + * (the current value for the property, and the requested + * new value.) This is useful when values need to be kept + * in certain ranges; specifically, to keep x/y positions + * non-negative in a fixed position view. + * * @constructor * @param {Object} object the object to get/set values upon * @param {string} key the property to get/set + * @param {function} [updater] function used to process updates */ - function AccessorMutator(object, key) { + function AccessorMutator(object, key, updater) { return function (value) { if (arguments.length > 0) { - object[key] = value; + object[key] = updater ? + updater(value, object[key]) : + value; } return object[key]; }; diff --git a/platform/features/layout/src/elements/ElementProxy.js b/platform/features/layout/src/elements/ElementProxy.js index a041acf092..f2e22f92de 100644 --- a/platform/features/layout/src/elements/ElementProxy.js +++ b/platform/features/layout/src/elements/ElementProxy.js @@ -13,6 +13,11 @@ define( bottom: Number.NEGATIVE_INFINITY }; + // Ensure a value is non-negative (for x/y setters) + function clamp(value) { + return Math.max(value, 0); + } + /** * Abstract superclass for other classes which provide useful * interfaces upon an elements in a fixed position view. @@ -42,14 +47,14 @@ define( * @param {number} [x] the new x position (if setting) * @returns {number} the x position */ - x: new AccessorMutator(element, 'x'), + x: new AccessorMutator(element, 'x', clamp), /** * Get and/or set the y position of this element. * Units are in fixed position grid space. * @param {number} [y] the new y position (if setting) * @returns {number} the y position */ - y: new AccessorMutator(element, 'y'), + y: new AccessorMutator(element, 'y', clamp), /** * Get and/or set the stroke color of this element. * @param {string} [stroke] the new stroke color (if setting) diff --git a/platform/features/layout/src/elements/LineProxy.js b/platform/features/layout/src/elements/LineProxy.js index 14671689dd..9247840b54 100644 --- a/platform/features/layout/src/elements/LineProxy.js +++ b/platform/features/layout/src/elements/LineProxy.js @@ -24,7 +24,7 @@ define( */ proxy.x = function (v) { var x = Math.min(element.x, element.x2), - delta = v - x; + delta = Math.max(v, 0) - x; if (arguments.length > 0 && delta) { element.x += delta; element.x2 += delta; @@ -39,7 +39,7 @@ define( */ proxy.y = function (v) { var y = Math.min(element.y, element.y2), - delta = v - y; + delta = Math.max(v, 0) - y; if (arguments.length > 0 && delta) { element.y += delta; element.y2 += delta; From 11360ba46c97005a81ec7350608e1855a6390f74 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 11:40:11 -0800 Subject: [PATCH 08/12] [Fixed Position] Add comments Add in-line documentation for WTD-882. --- platform/features/layout/src/elements/ResizeHandle.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/platform/features/layout/src/elements/ResizeHandle.js b/platform/features/layout/src/elements/ResizeHandle.js index bd9a3687e9..72c5963192 100644 --- a/platform/features/layout/src/elements/ResizeHandle.js +++ b/platform/features/layout/src/elements/ResizeHandle.js @@ -16,6 +16,11 @@ define( minHeight = minHeight || 0; return { + /** + * Get/set the x position of the lower-right corner + * of the handle-controlled element, changing size + * as necessary. + */ x: function (value) { if (arguments.length > 0) { element.width = Math.max( @@ -25,6 +30,11 @@ define( } return element.x + element.width; }, + /** + * Get/set the y position of the lower-right corner + * of the handle-controlled element, changing size + * as necessary. + */ y: function (value) { if (arguments.length > 0) { element.height = Math.max( From 6efccc0784a4ea456db5f003f450daa22fecb3dd Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 11:48:46 -0800 Subject: [PATCH 09/12] [Fixed Position] Add handles for line end points Add handles for moving line end points in fixed position view, WTD-882. --- .../layout/src/elements/LineHandle.js | 61 +++++++++++++++++++ .../features/layout/src/elements/LineProxy.js | 19 +++++- 2 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 platform/features/layout/src/elements/LineHandle.js diff --git a/platform/features/layout/src/elements/LineHandle.js b/platform/features/layout/src/elements/LineHandle.js new file mode 100644 index 0000000000..03474df1c1 --- /dev/null +++ b/platform/features/layout/src/elements/LineHandle.js @@ -0,0 +1,61 @@ +/*global define*/ +define( + [], + function () { + 'use strict'; + + /** + * Handle for changing x/y position of a line's end point. + * This is used to support drag handles for line elements + * in a fixed position view. Field names for opposite ends + * are provided to avoid zero-length lines. + * @constructor + * @param element the line element + * @param {string} xProperty field which stores x position + * @param {string} yProperty field which stores x position + * @param {string} xOther field which stores x of other end + * @param {string} yOther field which stores y of other end + */ + function LineHandle(element, xProperty, yProperty, xOther, yOther) { + return { + /** + * Get/set the x position of the lower-right corner + * of the handle-controlled element, changing size + * as necessary. + */ + x: function (value) { + if (arguments.length > 0) { + // Ensure we stay in view + value = Math.max(value, 0); + // Make sure end points will still be different + if (element[yOther] !== element[yProperty] || + element[xOther] !== value) { + element[xProperty] = value; + } + } + return element[xProperty]; + }, + /** + * Get/set the y position of the lower-right corner + * of the handle-controlled element, changing size + * as necessary. + */ + y: function (value) { + if (arguments.length > 0) { + // Ensure we stay in view + value = Math.max(value, 0); + // Make sure end points will still be different + if (element[xOther] !== element[xProperty] || + element[yOther] !== value) { + element[yProperty] = value; + } + } + return element[yProperty]; + } + }; + } + + return LineHandle; + + } +); \ No newline at end of file diff --git a/platform/features/layout/src/elements/LineProxy.js b/platform/features/layout/src/elements/LineProxy.js index 9247840b54..dc4729ef6e 100644 --- a/platform/features/layout/src/elements/LineProxy.js +++ b/platform/features/layout/src/elements/LineProxy.js @@ -1,8 +1,8 @@ /*global define*/ define( - ['./ElementProxy'], - function (ElementProxy) { + ['./ElementProxy', './LineHandle'], + function (ElementProxy, LineHandle) { 'use strict'; /** @@ -15,7 +15,11 @@ define( * @param {Array} elements the full array of elements */ function LineProxy(element, index, elements) { - var proxy = new ElementProxy(element, index, elements); + var proxy = new ElementProxy(element, index, elements), + handles = [ + new LineHandle(element, 'x', 'y', 'x2', 'y2'), + new LineHandle(element, 'x2', 'y2', 'x', 'y') + ]; /** * Get the top-left x coordinate, in grid space, of @@ -105,6 +109,15 @@ define( return element.y2 - proxy.y(); }; + /** + * Get element handles for changing the position of end + * points of this line. + * @returns {LineHandle[]} line handles for both end points + */ + proxy.handles = function () { + return handles; + }; + return proxy; } From e2af1e85a1a599c8c0ca0fed39798cc1718e1c88 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 11:54:41 -0800 Subject: [PATCH 10/12] [Fixed Position] Add spec for LineHandle Add tests for handles for positioning end point of lines in fixed position view, WTD-882. --- .../layout/test/elements/LineHandleSpec.js | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 platform/features/layout/test/elements/LineHandleSpec.js diff --git a/platform/features/layout/test/elements/LineHandleSpec.js b/platform/features/layout/test/elements/LineHandleSpec.js new file mode 100644 index 0000000000..c9049f81b9 --- /dev/null +++ b/platform/features/layout/test/elements/LineHandleSpec.js @@ -0,0 +1,53 @@ +/*global define,describe,it,expect,beforeEach,jasmine,xit*/ + +define( + ['../../src/elements/LineHandle'], + function (LineHandle) { + "use strict"; + + describe("A fixed position drag handle", function () { + var testElement, + handle; + + beforeEach(function () { + testElement = { + x: 3, + y: 42, + x2: 8, + y2: 11 + }; + + handle = new LineHandle(testElement, 'x', 'y', 'x2', 'y2'); + }); + + it("provides x/y grid coordinates for its corner", function () { + expect(handle.x()).toEqual(3); + expect(handle.y()).toEqual(42); + }); + + it("changes x and y positions", function () { + handle.x(30); + expect(testElement.x).toEqual(30); + handle.y(40); + expect(testElement.y).toEqual(40); + }); + + it("disallows values less than zero", function () { + handle.x(-1); + handle.y(-400); + expect(testElement.x).toEqual(0); + expect(testElement.y).toEqual(0); + }); + + it("ensures that end points remain different", function () { + handle.x(testElement.x2); + handle.y(testElement.y2); + // First change should have been fine, because y was different + expect(testElement.x).toEqual(testElement.x2); + // Second change should have been rejected + expect(testElement.y).not.toEqual(testElement.y2); + }); + + }); + } +); \ No newline at end of file From 91754f76add0305ae587834b7335e9aefc4dc679 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 12:02:43 -0800 Subject: [PATCH 11/12] [Fixed Position] Update specs Update specs for Fixed Position view to ensure code coverage after changes for resize/reposition behavior, WTD-882. --- .../layout/test/FixedControllerSpec.js | 41 +++++++++++++++++++ .../layout/test/elements/ElementProxySpec.js | 7 ++++ .../layout/test/elements/LineHandleSpec.js | 1 + .../layout/test/elements/LineProxySpec.js | 4 ++ 4 files changed, 53 insertions(+) diff --git a/platform/features/layout/test/FixedControllerSpec.js b/platform/features/layout/test/FixedControllerSpec.js index 9d2ffa9e95..087028c44a 100644 --- a/platform/features/layout/test/FixedControllerSpec.js +++ b/platform/features/layout/test/FixedControllerSpec.js @@ -322,6 +322,47 @@ define( expect(handle.endDrag).toEqual(jasmine.any(Function)); }); }); + + it("exposes a move handle", function () { + var handle; + + // Select something so that drag handles are expected + testModel.modified = 1; + findWatch("model.modified")(testModel.modified); + controller.select(controller.getElements()[1]); + + // Should have a move handle + handle = controller.moveHandle(); + + // And it should have start/continue/end drag methods + expect(handle.startDrag).toEqual(jasmine.any(Function)); + expect(handle.continueDrag).toEqual(jasmine.any(Function)); + expect(handle.endDrag).toEqual(jasmine.any(Function)); + }); + + it("updates selection style during drag", function () { + var oldStyle; + + // Select something so that drag handles are expected + testModel.modified = 1; + findWatch("model.modified")(testModel.modified); + controller.select(controller.getElements()[1]); + + // Get style + oldStyle = controller.selected().style; + + // Start a drag gesture + controller.moveHandle().startDrag(); + + // Haven't moved yet; style shouldn't have updated yet + expect(controller.selected().style).toEqual(oldStyle); + + // Drag a little + controller.moveHandle().continueDrag([ 1000, 100 ]); + + // Style should have been updated + expect(controller.selected().style).not.toEqual(oldStyle); + }); }); } ); \ No newline at end of file diff --git a/platform/features/layout/test/elements/ElementProxySpec.js b/platform/features/layout/test/elements/ElementProxySpec.js index ab90204ce0..b05061594c 100644 --- a/platform/features/layout/test/elements/ElementProxySpec.js +++ b/platform/features/layout/test/elements/ElementProxySpec.js @@ -47,6 +47,13 @@ define( proxy.order("top"); expect(testElements).toEqual([{}, {}, {}, testElement]); }); + + it("ensures x/y values are non-negative", function () { + proxy.x(-1); + proxy.y(-400); + expect(proxy.x()).toEqual(0); + expect(proxy.y()).toEqual(0); + }); }); } ); diff --git a/platform/features/layout/test/elements/LineHandleSpec.js b/platform/features/layout/test/elements/LineHandleSpec.js index c9049f81b9..25d51f617e 100644 --- a/platform/features/layout/test/elements/LineHandleSpec.js +++ b/platform/features/layout/test/elements/LineHandleSpec.js @@ -48,6 +48,7 @@ define( expect(testElement.y).not.toEqual(testElement.y2); }); + }); } ); \ No newline at end of file diff --git a/platform/features/layout/test/elements/LineProxySpec.js b/platform/features/layout/test/elements/LineProxySpec.js index fe38c7c3c9..9e2ed88b44 100644 --- a/platform/features/layout/test/elements/LineProxySpec.js +++ b/platform/features/layout/test/elements/LineProxySpec.js @@ -67,6 +67,10 @@ define( expect(proxy.y2()).toEqual(0); }); + it("provides handles for both ends", function () { + expect(new LineProxy(diagonal).handles().length).toEqual(2); + }); + }); } ); \ No newline at end of file From be3a31324de7aa17b4abc5356dc48b18901316e6 Mon Sep 17 00:00:00 2001 From: Victor Woeltjen Date: Tue, 24 Feb 2015 12:13:27 -0800 Subject: [PATCH 12/12] [Fixed Position] Change class based on selection state Change classes for all non-selected objects when an object is selected, WTD-882. --- platform/features/layout/res/templates/fixed.html | 1 + 1 file changed, 1 insertion(+) diff --git a/platform/features/layout/res/templates/fixed.html b/platform/features/layout/res/templates/fixed.html index 9ca5f981c7..03e0b106ee 100644 --- a/platform/features/layout/res/templates/fixed.html +++ b/platform/features/layout/res/templates/fixed.html @@ -15,6 +15,7 @@ style="position: absolute;" key="element.template" parameters="{ gridSize: controller.getGridSize() }" + ng-class="{ disabled: controller.selected() && !controller.selected(element) }" ng-style="element.style" ng-click="controller.select(element)" ng-model="element">