[Code Style] Use prototypes in Layout bundle

WTD-1482
This commit is contained in:
Victor Woeltjen 2015-08-12 13:45:48 -07:00
parent a9e2d48036
commit ed53808556
15 changed files with 615 additions and 596 deletions

View File

@ -39,20 +39,16 @@ define(
* @param {Scope} $scope the controller's Angular scope
*/
function FixedController($scope, $q, dialogService, telemetrySubscriber, telemetryFormatter) {
var gridSize = DEFAULT_GRID_SIZE,
dragging,
var self = this,
subscription,
elementProxies = [],
names = {}, // Cache names by ID
values = {}, // Cache values by ID
elementProxiesById = {},
handles = [],
moveHandle,
selection;
elementProxiesById = {};
// Convert from element x/y/width/height to an
// apropriate ng-style argument, to position elements.
// appropriate ng-style argument, to position elements.
function convertPosition(elementProxy) {
var gridSize = self.gridSize;
// Multiply position/dimensions by grid size
return {
left: (gridSize[0] * elementProxy.x()) + 'px',
@ -64,7 +60,7 @@ define(
// Update the style for a selected element
function updateSelectionStyle() {
var element = selection && selection.get();
var element = self.selection && self.selection.get();
if (element) {
element.style = convertPosition(element);
}
@ -74,7 +70,7 @@ define(
function generateDragHandle(elementHandle) {
return new FixedDragHandle(
elementHandle,
gridSize,
self.gridSize,
updateSelectionStyle,
$scope.commit
);
@ -85,17 +81,6 @@ define(
return element.handles().map(generateDragHandle);
}
// Select an element
function select(element) {
if (selection) {
// Update selection...
selection.select(element);
// ...as well as move, resize handles
moveHandle = generateDragHandle(element);
handles = generateDragHandles(element);
}
}
// Update the displayed value for this object
function updateValue(telemetryObject) {
var id = telemetryObject && telemetryObject.getId(),
@ -121,9 +106,9 @@ define(
// Update element positions when grid size changes
function updateElementPositions(layoutGrid) {
// Update grid size from model
gridSize = layoutGrid || DEFAULT_GRID_SIZE;
self.gridSize = layoutGrid || DEFAULT_GRID_SIZE;
elementProxies.forEach(function (elementProxy) {
self.elementProxies.forEach(function (elementProxy) {
elementProxy.style = convertPosition(elementProxy);
});
}
@ -154,7 +139,7 @@ define(
function refreshElements() {
// Cache selection; we are instantiating new proxies
// so we may want to restore this.
var selected = selection && selection.get(),
var selected = self.selection && self.selection.get(),
elements = (($scope.configuration || {}).elements || []),
index = -1; // Start with a 'not-found' value
@ -164,20 +149,20 @@ define(
}
// Create the new proxies...
elementProxies = elements.map(makeProxyElement);
self.elementProxies = elements.map(makeProxyElement);
// Clear old selection, and restore if appropriate
if (selection) {
selection.deselect();
if (self.selection) {
self.selection.deselect();
if (index > -1) {
select(elementProxies[index]);
self.select(self.elementProxies[index]);
}
}
// Finally, rebuild lists of elements by id to
// facilitate faster update when new telemetry comes in.
elementProxiesById = {};
elementProxies.forEach(function (elementProxy) {
self.elementProxies.forEach(function (elementProxy) {
var id = elementProxy.id;
if (elementProxy.element.type === 'fixed.telemetry') {
// Provide it a cached name/value to avoid flashing
@ -232,7 +217,9 @@ define(
// Refresh displayed elements
refreshElements();
// Select the newly-added element
select(elementProxies[elementProxies.length - 1]);
self.select(
self.elementProxies[self.elementProxies.length - 1]
);
// Mark change as persistable
if ($scope.commit) {
$scope.commit("Dropped an element.");
@ -249,8 +236,8 @@ define(
// Store the position of this element.
addElement({
type: "fixed.telemetry",
x: Math.floor(position.x / gridSize[0]),
y: Math.floor(position.y / gridSize[1]),
x: Math.floor(position.x / self.gridSize[0]),
y: Math.floor(position.y / self.gridSize[1]),
id: id,
stroke: "transparent",
color: "#cccccc",
@ -260,12 +247,17 @@ define(
});
}
this.gridSize = DEFAULT_GRID_SIZE;
this.elementProxies = [];
this.generateDragHandle = generateDragHandle;
this.generateDragHandles = generateDragHandles;
// Track current selection state
selection = $scope.selection;
this.selection = $scope.selection;
// Expose the view's selection proxy
if (selection) {
selection.proxy(new FixedProxy(addElement, $q, dialogService));
if (this.selection) {
this.selection.proxy(new FixedProxy(addElement, $q, dialogService));
}
// Refresh list of elements whenever model changes
@ -285,73 +277,80 @@ define(
// Position panes where they are dropped
$scope.$on("mctDrop", handleDrop);
return {
/**
* Get the size of the grid, in pixels. The returned array
* is in the form `[x, y]`.
* @returns {number[]} the grid size
* @memberof platform/features/layout.FixedController#
*/
getGridSize: function () {
return gridSize;
},
/**
* Get an array of elements in this panel; these are
* decorated proxies for both selection and display.
* @returns {Array} elements in this panel
* @memberof platform/features/layout.FixedController#
*/
getElements: function () {
return elementProxies;
},
/**
* Check if the element is currently selected, or (if no
* argument is supplied) get the currently selected element.
* @returns {boolean} true if selected
* @memberof platform/features/layout.FixedController#
*/
selected: function (element) {
return selection && ((arguments.length > 0) ?
selection.selected(element) : selection.get());
},
/**
* Set the active user selection in this view.
* @param element the element to select
* @memberof platform/features/layout.FixedController#
*/
select: select,
/**
* Clear the current user selection.
* @memberof platform/features/layout.FixedController#
*/
clearSelection: function () {
if (selection) {
selection.deselect();
handles = [];
moveHandle = undefined;
}
},
/**
* Get drag handles.
* @returns {Array} drag handles for the current selection
* @memberof platform/features/layout.FixedController#
*/
handles: function () {
return handles;
},
/**
* Get the handle to handle dragging to reposition an element.
* @returns {FixedDragHandle} the drag handle
* @memberof platform/features/layout.FixedController#
*/
moveHandle: function () {
return moveHandle;
}
};
}
/**
* Get the size of the grid, in pixels. The returned array
* is in the form `[x, y]`.
* @returns {number[]} the grid size
* @memberof platform/features/layout.FixedController#
*/
FixedController.prototype.getGridSize = function () {
return this.gridSize;
};
/**
* Get an array of elements in this panel; these are
* decorated proxies for both selection and display.
* @returns {Array} elements in this panel
*/
FixedController.prototype.getElements = function () {
return this.elementProxies;
};
/**
* Check if the element is currently selected, or (if no
* argument is supplied) get the currently selected element.
* @returns {boolean} true if selected
*/
FixedController.prototype.selected = function (element) {
var selection = this.selection;
return selection && ((arguments.length > 0) ?
selection.selected(element) : selection.get());
};
/**
* Set the active user selection in this view.
* @param element the element to select
*/
FixedController.prototype.select = function select(element) {
if (this.selection) {
// Update selection...
this.selection.select(element);
// ...as well as move, resize handles
this.mvHandle = this.generateDragHandle(element);
this.resizeHandles = this.generateDragHandles(element);
}
};
/**
* Clear the current user selection.
*/
FixedController.prototype.clearSelection = function () {
if (this.selection) {
this.selection.deselect();
this.resizeHandles = [];
this.mvHandle = undefined;
}
};
/**
* Get drag handles.
* @returns {platform/features/layout.FixedDragHandle[]}
* drag handles for the current selection
*/
FixedController.prototype.handles = function () {
return this.resizeHandles;
};
/**
* Get the handle to handle dragging to reposition an element.
* @returns {platform/features/layout.FixedDragHandle} the drag handle
*/
FixedController.prototype.moveHandle = function () {
return this.mvHandle;
};
return FixedController;
}
);

View File

@ -37,86 +37,77 @@ define(
* @constructor
*/
function FixedDragHandle(elementHandle, gridSize, update, commit) {
var self = {},
dragging;
// Generate ng-style-appropriate style for positioning
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',
top: (y - DRAG_HANDLE_SIZE[1] / 2) + 'px',
width: DRAG_HANDLE_SIZE[0] + 'px',
height: DRAG_HANDLE_SIZE[1] + 'px'
};
}
// 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])
);
// Invoke update callback
if (update) {
update();
}
}
}
// Conclude a drag gesture
function endDrag() {
// Clear cached state
dragging = undefined;
// Mark change as complete
if (commit) {
commit("Dragged handle.");
}
}
return {
/**
* Get a CSS style to position this drag handle.
* @returns CSS style object (for `ng-style`)
* @memberof platform/features/layout.FixedDragHandle#
*/
style: getStyle,
/**
* Start a drag gesture. This should be called when a drag
* begins to track initial state.
* @memberof platform/features/layout.FixedDragHandle#
*/
startDrag: startDrag,
/**
* Continue a drag gesture; update x/y positions.
* @param {number[]} delta x/y pixel difference since drag
* started
* @memberof platform/features/layout.FixedDragHandle#
*/
continueDrag: continueDrag,
/**
* End a drag gesture. This should be callled when a drag
* concludes to trigger commit of changes.
* @memberof platform/features/layout.FixedDragHandle#
*/
endDrag: endDrag
};
this.elementHandle = elementHandle;
this.gridSize = gridSize;
this.update = update;
this.commit = commit;
}
/**
* Get a CSS style to position this drag handle.
* @returns CSS style object (for `ng-style`)
* @memberof platform/features/layout.FixedDragHandle#
*/
FixedDragHandle.prototype.style = function () {
// Adjust from grid to pixel coordinates
var x = this.elementHandle.x() * this.gridSize[0],
y = this.elementHandle.y() * this.gridSize[1];
// Convert to a CSS style centered on that point
return {
left: (x - DRAG_HANDLE_SIZE[0] / 2) + 'px',
top: (y - DRAG_HANDLE_SIZE[1] / 2) + 'px',
width: DRAG_HANDLE_SIZE[0] + 'px',
height: DRAG_HANDLE_SIZE[1] + 'px'
};
};
/**
* Start a drag gesture. This should be called when a drag
* begins to track initial state.
*/
FixedDragHandle.prototype.startDrag = function startDrag() {
// Cache initial x/y positions
this.dragging = {
x: this.elementHandle.x(),
y: this.elementHandle.y()
};
};
/**
* Continue a drag gesture; update x/y positions.
* @param {number[]} delta x/y pixel difference since drag
* started
*/
FixedDragHandle.prototype.continueDrag = function (delta) {
if (this.dragging) {
// Update x/y positions (snapping to grid)
this.elementHandle.x(
this.dragging.x + Math.round(delta[0] / this.gridSize[0])
);
this.elementHandle.y(
this.dragging.y + Math.round(delta[1] / this.gridSize[1])
);
// Invoke update callback
if (this.update) {
this.update();
}
}
};
/**
* End a drag gesture. This should be callled when a drag
* concludes to trigger commit of changes.
*/
FixedDragHandle.prototype.endDrag = function () {
// Clear cached state
this.dragging = undefined;
// Mark change as complete
if (this.commit) {
this.commit("Dragged handle.");
}
};
return FixedDragHandle;
}
);

View File

@ -37,33 +37,41 @@ define(
* when adding a new element will require user input
*/
function FixedProxy(addElementCallback, $q, dialogService) {
var factory = new ElementFactory(dialogService);
return {
/**
* Add a new visual element to this view.
* @memberof platform/features/layout.FixedProxy#
*/
add: function (type) {
// Place a configured element into the view configuration
function addElement(element) {
// Configure common properties of the element
element.x = element.x || 0;
element.y = element.y || 0;
element.width = element.width || 1;
element.height = element.height || 1;
element.type = type;
// Finally, add it to the view's configuration
addElementCallback(element);
}
// Defer creation to the factory
$q.when(factory.createElement(type)).then(addElement);
}
};
this.factory = new ElementFactory(dialogService);
this.$q = $q;
this.addElementCallback = addElementCallback;
}
/**
* Add a new visual element to this view. Supported types are:
*
* * `fixed.image`
* * `fixed.box`
* * `fixed.text`
* * `fixed.line`
*
* @param {string} type the type of element to add
*/
FixedProxy.prototype.add = function (type) {
var addElementCallback = this.addElementCallback;
// Place a configured element into the view configuration
function addElement(element) {
// Configure common properties of the element
element.x = element.x || 0;
element.y = element.y || 0;
element.width = element.width || 1;
element.height = element.height || 1;
element.type = type;
// Finally, add it to the view's configuration
addElementCallback(element);
}
// Defer creation to the factory
this.$q.when(this.factory.createElement(type)).then(addElement);
};
return FixedProxy;
}
);

View File

@ -31,25 +31,20 @@ define(
* They cannot contain folders.
* @constructor
* @memberof platform/features/layout
* @implements {Policy.<View, DomainObject>}
*/
function LayoutCompositionPolicy() {
return {
/**
* Is the type identified by the candidate allowed to
* contain the type described by the context?
* @memberof platform/features/layout.LayoutCompositionPolicy#
*/
allow: function (candidate, context) {
var isFolderInLayout =
candidate &&
context &&
candidate.instanceOf('layout') &&
context.instanceOf('folder');
return !isFolderInLayout;
}
};
}
LayoutCompositionPolicy.prototype.allow = function (candidate, context) {
var isFolderInLayout =
candidate &&
context &&
candidate.instanceOf('layout') &&
context.instanceOf('folder');
return !isFolderInLayout;
};
return LayoutCompositionPolicy;
}
);

View File

@ -45,11 +45,7 @@ define(
* @param {Scope} $scope the controller's Angular scope
*/
function LayoutController($scope) {
var gridSize = DEFAULT_GRID_SIZE,
activeDrag,
activeDragId,
rawPositions = {},
positions = {};
var self = this;
// Utility function to copy raw positions from configuration,
// without writing directly to configuration (to avoid triggering
@ -62,47 +58,6 @@ define(
return copy;
}
// Convert from { positions: ..., dimensions: ... } to an
// apropriate ng-style argument, to position frames.
function convertPosition(raw) {
// Multiply position/dimensions by grid size
return {
left: (gridSize[0] * raw.position[0]) + 'px',
top: (gridSize[1] * raw.position[1]) + 'px',
width: (gridSize[0] * raw.dimensions[0]) + 'px',
height: (gridSize[1] * raw.dimensions[1]) + 'px'
};
}
// Generate default positions for a new panel
function defaultDimensions() {
return MINIMUM_FRAME_SIZE.map(function (min, i) {
return Math.max(
Math.ceil(min / gridSize[i]),
DEFAULT_DIMENSIONS[i]
);
});
}
// Generate a default position (in its raw format) for a frame.
// Use an index to ensure that default positions are unique.
function defaultPosition(index) {
return {
position: [index, index],
dimensions: defaultDimensions()
};
}
// Store a computed position for a contained frame by its
// domain object id. Called in a forEach loop, so arguments
// are as expected there.
function populatePosition(id, index) {
rawPositions[id] =
rawPositions[id] || defaultPosition(index || 0);
positions[id] =
convertPosition(rawPositions[id]);
}
// Compute panel positions based on the layout's object model
function lookupPanels(ids) {
var configuration = $scope.configuration || {};
@ -112,27 +67,32 @@ define(
ids = ids || [];
// Pull panel positions from configuration
rawPositions = shallowCopy(configuration.panels || {}, ids);
self.rawPositions =
shallowCopy(configuration.panels || {}, ids);
// Clear prior computed positions
positions = {};
self.positions = {};
// Update width/height that we are tracking
gridSize = ($scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
self.gridSize =
($scope.model || {}).layoutGrid || DEFAULT_GRID_SIZE;
// Compute positions and add defaults where needed
ids.forEach(populatePosition);
ids.forEach(function (id, index) {
self.populatePosition(id, index);
});
}
// Update grid size when it changed
function updateGridSize(layoutGrid) {
var oldSize = gridSize;
var oldSize = self.gridSize;
gridSize = layoutGrid || DEFAULT_GRID_SIZE;
self.gridSize = layoutGrid || DEFAULT_GRID_SIZE;
// Only update panel positions if this actually changed things
if (gridSize[0] !== oldSize[0] || gridSize[1] !== oldSize[1]) {
lookupPanels(Object.keys(positions));
if (self.gridSize[0] !== oldSize[0] ||
self.gridSize[1] !== oldSize[1]) {
lookupPanels(Object.keys(self.positions));
}
}
@ -150,8 +110,8 @@ define(
// Store the position of this panel.
$scope.configuration.panels[id] = {
position: [
Math.floor(position.x / gridSize[0]),
Math.floor(position.y / gridSize[1])
Math.floor(position.x / self.gridSize[0]),
Math.floor(position.y / self.gridSize[1])
],
dimensions: DEFAULT_DIMENSIONS
};
@ -160,13 +120,39 @@ define(
$scope.commit("Dropped a frame.");
}
// Populate template-facing position for this id
populatePosition(id);
self.populatePosition(id);
// Layout may contain embedded views which will
// listen for drops, so call preventDefault() so
// that they can recognize that this event is handled.
e.preventDefault();
}
// End drag; we don't want to put $scope into this
// because it triggers "cpws" (copy window or scope)
// errors in Angular.
this.endDragInScope = function () {
// Write to configuration; this is watched and
// saved by the EditRepresenter.
$scope.configuration =
$scope.configuration || {};
// Make sure there is a "panels" field in the
// view configuration.
$scope.configuration.panels =
$scope.configuration.panels || {};
// Store the position of this panel.
$scope.configuration.panels[self.activeDragId] =
self.rawPositions[self.activeDragId];
// Mark this object as dirty to encourage persistence
if ($scope.commit) {
$scope.commit("Moved frame.");
}
};
this.positions = {};
this.rawPositions = {};
this.gridSize = DEFAULT_GRID_SIZE;
this.$scope = $scope;
// Watch for changes to the grid size in the model
$scope.$watch("model.layoutGrid", updateGridSize);
@ -175,92 +161,116 @@ define(
// Position panes where they are dropped
$scope.$on("mctDrop", handleDrop);
return {
/**
* Get a style object for a frame with the specified domain
* object identifier, suitable for use in an `ng-style`
* directive to position a frame as configured for this layout.
* @param {string} id the object identifier
* @returns {Object.<string, string>} an object with
* appropriate left, width, etc fields for positioning
* @memberof platform/features/layout.LayoutController#
*/
getFrameStyle: function (id) {
// Called in a loop, so just look up; the "positions"
// object is kept up to date by a watch.
return positions[id];
},
/**
* 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 {string} id the identifier of the domain object
* in the frame being manipulated
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
* @memberof platform/features/layout.LayoutController#
*/
startDrag: function (id, posFactor, dimFactor) {
activeDragId = id;
activeDrag = new LayoutDrag(
rawPositions[id],
posFactor,
dimFactor,
gridSize
);
},
/**
* 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
* @memberof platform/features/layout.LayoutController#
*/
continueDrag: function (delta) {
if (activeDrag) {
rawPositions[activeDragId] =
activeDrag.getAdjustedPosition(delta);
populatePosition(activeDragId);
}
},
/**
* End the active drag gesture. This will update the
* view configuration.
* @memberof platform/features/layout.LayoutController#
*/
endDrag: function () {
// Write to configuration; this is watched and
// saved by the EditRepresenter.
$scope.configuration =
$scope.configuration || {};
// Make sure there is a "panels" field in the
// view configuration.
$scope.configuration.panels =
$scope.configuration.panels || {};
// Store the position of this panel.
$scope.configuration.panels[activeDragId] =
rawPositions[activeDragId];
// Mark this object as dirty to encourage persistence
if ($scope.commit) {
$scope.commit("Moved frame.");
}
}
};
}
// Convert from { positions: ..., dimensions: ... } to an
// apropriate ng-style argument, to position frames.
LayoutController.prototype.convertPosition = function (raw) {
var gridSize = this.gridSize;
// Multiply position/dimensions by grid size
return {
left: (gridSize[0] * raw.position[0]) + 'px',
top: (gridSize[1] * raw.position[1]) + 'px',
width: (gridSize[0] * raw.dimensions[0]) + 'px',
height: (gridSize[1] * raw.dimensions[1]) + 'px'
};
};
// Generate default positions for a new panel
LayoutController.prototype.defaultDimensions = function () {
var gridSize = this.gridSize;
return MINIMUM_FRAME_SIZE.map(function (min, i) {
return Math.max(
Math.ceil(min / gridSize[i]),
DEFAULT_DIMENSIONS[i]
);
});
};
// Generate a default position (in its raw format) for a frame.
// Use an index to ensure that default positions are unique.
LayoutController.prototype.defaultPosition = function (index) {
return {
position: [index, index],
dimensions: this.defaultDimensions()
};
};
// Store a computed position for a contained frame by its
// domain object id. Called in a forEach loop, so arguments
// are as expected there.
LayoutController.prototype.populatePosition = function (id, index) {
this.rawPositions[id] =
this.rawPositions[id] || this.defaultPosition(index || 0);
this.positions[id] =
this.convertPosition(this.rawPositions[id]);
};
/**
* Get a style object for a frame with the specified domain
* object identifier, suitable for use in an `ng-style`
* directive to position a frame as configured for this layout.
* @param {string} id the object identifier
* @returns {Object.<string, string>} an object with
* appropriate left, width, etc fields for positioning
*/
LayoutController.prototype.getFrameStyle = function (id) {
// Called in a loop, so just look up; the "positions"
// object is kept up to date by a watch.
return this.positions[id];
};
/**
* 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 {string} id the identifier of the domain object
* in the frame being manipulated
* @param {number[]} posFactor the position factor
* @param {number[]} dimFactor the dimensions factor
*/
LayoutController.prototype.startDrag = function (id, posFactor, dimFactor) {
this.activeDragId = id;
this.activeDrag = new LayoutDrag(
this.rawPositions[id],
posFactor,
dimFactor,
this.gridSize
);
};
/**
* 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
*/
LayoutController.prototype.continueDrag = function (delta) {
if (this.activeDrag) {
this.rawPositions[this.activeDragId] =
this.activeDrag.getAdjustedPosition(delta);
this.populatePosition(this.activeDragId);
}
};
/**
* End the active drag gesture. This will update the
* view configuration.
*/
LayoutController.prototype.endDrag = function () {
this.endDragInScope();
};
return LayoutController;
}
);

View File

@ -54,63 +54,63 @@ define(
* @memberof platform/features/layout
*/
function LayoutDrag(rawPosition, posFactor, dimFactor, gridSize) {
// Convert a delta from pixel coordinates to grid coordinates,
// rounding to whole-number grid coordinates.
function toGridDelta(pixelDelta) {
return pixelDelta.map(function (v, i) {
return Math.round(v / gridSize[i]);
});
}
// Utility function to perform element-by-element multiplication
function multiply(array, factors) {
return array.map(function (v, i) {
return v * factors[i];
});
}
// Utility function to perform element-by-element addition
function add(array, other) {
return array.map(function (v, i) {
return v + other[i];
});
}
// Utility function to perform element-by-element max-choosing
function max(array, other) {
return array.map(function (v, i) {
return Math.max(v, other[i]);
});
}
function getAdjustedPosition(pixelDelta) {
var gridDelta = toGridDelta(pixelDelta);
return {
position: max(add(
rawPosition.position,
multiply(gridDelta, posFactor)
), [0, 0]),
dimensions: max(add(
rawPosition.dimensions,
multiply(gridDelta, dimFactor)
), [1, 1])
};
}
return {
/**
* Get a new position object in grid coordinates, with
* position and dimensions both offset appropriately
* according to the factors supplied in the constructor.
* @param {number[]} pixelDelta the offset from the
* original position, in pixels
* @memberof platform/features/layout.LayoutDrag#
*/
getAdjustedPosition: getAdjustedPosition
};
this.rawPosition = rawPosition;
this.posFactor = posFactor;
this.dimFactor = dimFactor;
this.gridSize = gridSize;
}
// Convert a delta from pixel coordinates to grid coordinates,
// rounding to whole-number grid coordinates.
function toGridDelta(gridSize, pixelDelta) {
return pixelDelta.map(function (v, i) {
return Math.round(v / gridSize[i]);
});
}
// Utility function to perform element-by-element multiplication
function multiply(array, factors) {
return array.map(function (v, i) {
return v * factors[i];
});
}
// Utility function to perform element-by-element addition
function add(array, other) {
return array.map(function (v, i) {
return v + other[i];
});
}
// Utility function to perform element-by-element max-choosing
function max(array, other) {
return array.map(function (v, i) {
return Math.max(v, other[i]);
});
}
/**
* Get a new position object in grid coordinates, with
* position and dimensions both offset appropriately
* according to the factors supplied in the constructor.
* @param {number[]} pixelDelta the offset from the
* original position, in pixels
*/
LayoutDrag.prototype.getAdjustedPosition = function (pixelDelta) {
var gridDelta = toGridDelta(this.gridSize, pixelDelta);
return {
position: max(add(
this.rawPosition.position,
multiply(gridDelta, this.posFactor)
), [0, 0]),
dimensions: max(add(
this.rawPosition.dimensions,
multiply(gridDelta, this.dimFactor)
), [1, 1])
};
};
return LayoutDrag;
}

View File

@ -48,7 +48,6 @@ define(
* Get/set this element's fill color. (Omitting the
* argument makes this act as a getter.)
* @method
* @memberof BoxProxy
* @param {string} fill the new fill color
* @returns {string} the fill color
* @memberof platform/features/layout.BoxProxy#

View File

@ -89,29 +89,28 @@ define(
* @constructor
*/
function ElementFactory(dialogService) {
return {
/**
* Create a new element for the fixed position view.
* @param {string} type the type of element to create
* @returns {Promise|object} the created element, or a promise
* for that element
* @memberof platform/features/layout.ElementFactory#
*/
createElement: function (type) {
var initialState = INITIAL_STATES[type] || {};
// Clone that state
initialState = JSON.parse(JSON.stringify(initialState));
// Show a dialog to configure initial state, if appropriate
return DIALOGS[type] ? dialogService.getUserInput(
DIALOGS[type],
initialState
) : initialState;
}
};
this.dialogService = dialogService;
}
/**
* Create a new element for the fixed position view.
* @param {string} type the type of element to create
* @returns {Promise|object} the created element, or a promise
* for that element
*/
ElementFactory.prototype.createElement = function (type) {
var initialState = INITIAL_STATES[type] || {};
// Clone that state
initialState = JSON.parse(JSON.stringify(initialState));
// Show a dialog to configure initial state, if appropriate
return DIALOGS[type] ? this.dialogService.getUserInput(
DIALOGS[type],
initialState
) : initialState;
};
return ElementFactory;
}
);

View File

@ -56,96 +56,108 @@ define(
* @param {Array} elements the full array of elements
*/
function ElementProxy(element, index, elements) {
var handles = [ new ResizeHandle(element, 1, 1) ];
this.resizeHandles = [ new ResizeHandle(element, 1, 1) ];
return {
/**
* The element as stored in the view configuration.
* @memberof platform/features/layout.ElementProxy#
*/
element: element,
/**
* Get and/or set the x position of this element.
* Units are in fixed position grid space.
* @param {number} [x] the new x position (if setting)
* @returns {number} the x position
* @memberof platform/features/layout.ElementProxy#
*/
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
* @memberof platform/features/layout.ElementProxy#
*/
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)
* @returns {string} the stroke color
* @memberof platform/features/layout.ElementProxy#
*/
stroke: new AccessorMutator(element, 'stroke'),
/**
* Get and/or set the width of this element.
* Units are in fixed position grid space.
* @param {number} [w] the new width (if setting)
* @returns {number} the width
* @memberof platform/features/layout.ElementProxy#
*/
width: new AccessorMutator(element, 'width'),
/**
* Get and/or set the height of this element.
* Units are in fixed position grid space.
* @param {number} [h] the new height (if setting)
* @returns {number} the height
* @memberof platform/features/layout.ElementProxy#
*/
height: new AccessorMutator(element, 'height'),
/**
* Change the display order of this element.
* @param {string} o where to move this element;
* one of "top", "up", "down", or "bottom"
* @memberof platform/features/layout.ElementProxy#
*/
order: function (o) {
var delta = ORDERS[o] || 0,
desired = Math.max(
Math.min(index + delta, elements.length - 1),
0
);
// Move to the desired index, if this is a change
if ((desired !== index) && (elements[index] === element)) {
// Splice out the current element
elements.splice(index, 1);
// Splice it back in at the correct index
elements.splice(desired, 0, element);
// Track change in index (proxy should be recreated
// anyway, but be consistent)
index = desired;
}
},
/**
* Remove this element from the fixed position view.
* @memberof platform/features/layout.ElementProxy#
*/
remove: function () {
if (elements[index] === element) {
elements.splice(index, 1);
}
},
/**
* Get handles to control specific features of this element,
* e.g. corner size.
* @memberof platform/features/layout.ElementProxy#
*/
handles: function () {
return handles;
}
};
/**
* The element as stored in the view configuration.
* @memberof platform/features/layout.ElementProxy#
*/
this.element = element;
/**
* Get and/or set the x position of this element.
* Units are in fixed position grid space.
* @param {number} [x] the new x position (if setting)
* @returns {number} the x position
* @memberof platform/features/layout.ElementProxy#
*/
this.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
* @memberof platform/features/layout.ElementProxy#
*/
this.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)
* @returns {string} the stroke color
* @memberof platform/features/layout.ElementProxy#
*/
this.stroke = new AccessorMutator(element, 'stroke');
/**
* Get and/or set the width of this element.
* Units are in fixed position grid space.
* @param {number} [w] the new width (if setting)
* @returns {number} the width
* @memberof platform/features/layout.ElementProxy#
*/
this.width = new AccessorMutator(element, 'width');
/**
* Get and/or set the height of this element.
* Units are in fixed position grid space.
* @param {number} [h] the new height (if setting)
* @returns {number} the height
* @memberof platform/features/layout.ElementProxy#
*/
this.height = new AccessorMutator(element, 'height');
this.index = index;
this.elements = elements;
}
/**
* Change the display order of this element.
* @param {string} o where to move this element;
* one of "top", "up", "down", or "bottom"
*/
ElementProxy.prototype.order = function (o) {
var index = this.index,
elements = this.elements,
element = this.element,
delta = ORDERS[o] || 0,
desired = Math.max(
Math.min(index + delta, elements.length - 1),
0
);
// Move to the desired index, if this is a change
if ((desired !== index) && (elements[index] === element)) {
// Splice out the current element
elements.splice(index, 1);
// Splice it back in at the correct index
elements.splice(desired, 0, element);
// Track change in index (proxy should be recreated
// anyway, but be consistent)
this.index = desired;
}
};
/**
* Remove this element from the fixed position view.
*/
ElementProxy.prototype.remove = function () {
var index = this.index;
if (this.elements[index] === this.element) {
this.elements.splice(index, 1);
}
};
/**
* Get handles to control specific features of this element,
* e.g. corner size.
* @return {platform/features/layout.ElementHandle[]} handles
* for moving/resizing this element
*/
ElementProxy.prototype.handles = function () {
return this.resizeHandles;
};
return ElementProxy;
}
);

View File

@ -38,6 +38,7 @@ define(
* configuration
* @param index the element's index within its array
* @param {Array} elements the full array of elements
* @augments {platform/features/layout.ElementProxy}
*/
function ImageProxy(element, index, elements) {
var proxy = new ElementProxy(element, index, elements);

View File

@ -37,48 +37,54 @@ define(
* @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
* @implements {platform/features/layout.ElementHandle}
*/
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.
* @memberof platform/features/layout.LineHandle#
*/
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.
* @memberof platform/features/layout.LineHandle#
*/
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];
}
};
this.element = element;
this.xProperty = xProperty;
this.yProperty = yProperty;
this.xOther = xOther;
this.yOther = yOther;
}
LineHandle.prototype.x = function (value) {
var element = this.element,
xProperty = this.xProperty,
yProperty = this.yProperty,
xOther = this.xOther,
yOther = this.yOther;
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];
};
LineHandle.prototype.y = function (value) {
var element = this.element,
xProperty = this.xProperty,
yProperty = this.yProperty,
xOther = this.xOther,
yOther = this.yOther;
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;
}

View File

@ -35,6 +35,7 @@ define(
* configuration
* @param index the element's index within its array
* @param {Array} elements the full array of elements
* @augments {platform/features/layout.ElementProxy}
*/
function LineProxy(element, index, elements) {
var proxy = new ElementProxy(element, index, elements),

View File

@ -25,6 +25,11 @@ define(
function () {
'use strict';
/**
* @interface platform/features/layout.ElementHandle
* @private
*/
/**
* Handle for changing width/height properties of an element.
* This is used to support drag handles for different
@ -33,44 +38,35 @@ define(
* @constructor
*/
function ResizeHandle(element, minWidth, minHeight) {
// Ensure reasonable defaults
minWidth = minWidth || 0;
minHeight = minHeight || 0;
this.element = element;
return {
/**
* Get/set the x position of the lower-right corner
* of the handle-controlled element, changing size
* as necessary.
* @memberof platform/features/layout.ResizeHandle#
*/
x: function (value) {
if (arguments.length > 0) {
element.width = Math.max(
minWidth,
value - element.x
);
}
return element.x + element.width;
},
/**
* Get/set the y position of the lower-right corner
* of the handle-controlled element, changing size
* as necessary.
* @memberof platform/features/layout.ResizeHandle#
*/
y: function (value) {
if (arguments.length > 0) {
element.height = Math.max(
minHeight,
value - element.y
);
}
return element.y + element.height;
}
};
// Ensure reasonable defaults
this.minWidth = minWidth || 0;
this.minHeight = minHeight || 0;
}
ResizeHandle.prototype.x = function (value) {
var element = this.element;
if (arguments.length > 0) {
element.width = Math.max(
this.minWidth,
value - element.x
);
}
return element.x + element.width;
};
ResizeHandle.prototype.y = function (value) {
var element = this.element;
if (arguments.length > 0) {
element.height = Math.max(
this.minHeight,
value - element.y
);
}
return element.y + element.height;
};
return ResizeHandle;
}

View File

@ -41,6 +41,7 @@ define(
* configuration
* @param index the element's index within its array
* @param {Array} elements the full array of elements
* @augments {platform/features/layout.ElementProxy}
*/
function TelemetryProxy(element, index, elements) {
var proxy = new TextProxy(element, index, elements);

View File

@ -38,6 +38,7 @@ define(
* configuration
* @param index the element's index within its array
* @param {Array} elements the full array of elements
* @augments {platform/features/layout.ElementProxy}
*/
function TextProxy(element, index, elements) {
var proxy = new BoxProxy(element, index, elements);