Commit 578e7842 authored by stevenjb's avatar stevenjb Committed by Commit bot
Browse files

MD Settings: Display: Implement dragging

This introduces drag behavior, but does not persist new positions.

BUG=547080
CQ_INCLUDE_TRYBOTS=tryserver.chromium.linux:closure_compilation

Review-Url: https://codereview.chromium.org/2097793004
Cr-Commit-Position: refs/heads/master@{#402354}
parent d71e32d3
......@@ -57,6 +57,7 @@
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
'<(EXTERNS_GYP):system_display',
'<(INTERFACES_GYP):system_display_interface',
'drag_behavior'
],
'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
},
......@@ -69,6 +70,13 @@
'display'
],
'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
},
{
'target_name': 'drag_behavior',
'dependencies': [
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:cr',
],
'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
},
],
],
}
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-resizable-behavior/iron-resizable-behavior.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-button/paper-button.html">
<link rel="import" href="/device_page/drag_behavior.html">
<link rel="import" href="/settings_shared_css.html">
<dom-module id="display-layout">
......@@ -31,8 +32,8 @@
</style>
<div id="displayArea" on-iron-resize="calculateVisualScale_">
<template is="dom-repeat" items="[[displays]]">
<div id="_[[item.id]]" class="display"
style$="[[getDivStyle_(item, visualScale)]]"
<div id="_[[item.id]]" class="display" draggable="true"
style$="[[getDivStyle_(item.id, item.bounds, visualScale)]]"
selected$="[[isSelected_(item, selectedDisplay)]]"
on-tap="onSelectDisplayTap_">
[[item.name]]
......
......@@ -17,6 +17,7 @@ Polymer({
behaviors: [
Polymer.IronResizableBehavior,
DragBehavior,
],
properties: {
......@@ -32,6 +33,12 @@ Polymer({
*/
layouts: Array,
/**
* Whether or not mirroring is enabled.
* @type {boolean}
*/
mirroring: false,
/** @type {!chrome.system.display.DisplayUnitInfo|undefined} */
selectedDisplay: Object,
......@@ -42,14 +49,17 @@ Polymer({
visualScale: 1,
},
/** @private {!Object<chrome.system.display.DisplayUnitInfo>} */
displayMap_: {},
/** @private {!Object<chrome.system.display.Bounds>} */
displayBoundsMap_: {},
/** @private {!Object<chrome.system.display.DisplayLayout>} */
layoutMap_: {},
/** @private {!Object<chrome.system.display.Bounds>} */
boundsMap_: {},
/**
* The calculated bounds used for generating the div bounds.
* @private {!Object<chrome.system.display.Bounds>}
*/
calculatedBoundsMap_: {},
/** @private {!{left: number, top: number}} */
visualOffset_: {left: 0, top: 0},
......@@ -67,6 +77,11 @@ Polymer({
tryCalcVisualScale();
},
/** @override */
detached: function() {
this.initializeDrag(false);
},
/**
* Called explicitly when |this.displays| and their associated |this.layouts|
* have been fetched from chrome.
......@@ -77,19 +92,24 @@ Polymer({
this.displays = displays;
this.layouts = layouts;
this.displayMap_ = {};
this.mirroring = displays.length > 0 && !!displays[0].mirroringSourceId;
this.displayBoundsMap_ = {};
for (let display of this.displays)
this.displayMap_[display.id] = display;
this.displayBoundsMap_[display.id] = display.bounds;
this.layoutMap_ = {};
for (let layout of this.layouts)
this.layoutMap_[layout.id] = layout;
this.boundsMap_ = {};
this.calculatedBoundsMap_ = {};
for (let display of this.displays)
this.calcDisplayBounds_(display);
this.calculateBounds_(display.id, display.bounds);
this.calculateVisualScale_();
this.initializeDrag(
!this.mirroring, this.$.displayArea, this.onDrag_.bind(this));
},
/**
......@@ -107,7 +127,7 @@ Polymer({
}
var display = this.displays[0];
var bounds = this.boundsMap_[display.id];
var bounds = this.calculatedBoundsMap_[display.id];
var displayInfoBoundingBox = {
left: bounds.left,
right: bounds.left + bounds.width,
......@@ -118,7 +138,7 @@ Polymer({
var maxHeight = bounds.height;
for (let i = 1; i < this.displays.length; ++i) {
display = this.displays[i];
bounds = this.boundsMap_[display.id];
bounds = this.calculatedBoundsMap_[display.id];
displayInfoBoundingBox.left =
Math.min(displayInfoBoundingBox.left, bounds.left);
displayInfoBoundingBox.right =
......@@ -154,15 +174,16 @@ Polymer({
},
/**
* @param {!chrome.system.display.DisplayUnitInfo} display
* @param {string} id
* @param {!chrome.system.display.Bounds} displayBounds
* @param {number} visualScale
* @return {string} The style string for the div.
* @private
*/
getDivStyle_: function(display, visualScale) {
getDivStyle_: function(id, displayBounds, visualScale) {
// This matches the size of the box-shadow or border in CSS.
/** @const {number} */ var BORDER = 2;
var bounds = this.boundsMap_[display.id];
var bounds = this.calculatedBoundsMap_[id];
var height = Math.round(bounds.height * this.visualScale) - BORDER * 2;
var width = Math.round(bounds.width * this.visualScale) - BORDER * 2;
var left =
......@@ -196,29 +217,29 @@ Polymer({
* Caches the display bounds so that parent bounds are only calculated once.
* TODO(stevenjb): Move this function and the maps it requires to a separate
* behavior which will include snapping and collisions.
* @param {!chrome.system.display.DisplayUnitInfo} display
* @param {string} id
* @param {!chrome.system.display.Bounds} bounds
* @private
*/
calcDisplayBounds_: function(display) {
if (display.id in this.boundsMap_)
calculateBounds_: function(id, bounds) {
if (id in this.calculatedBoundsMap_)
return; // Already calculated (i.e. a parent of a previous display)
var left, top;
if (display.isPrimary) {
left = -display.bounds.width / 2;
top = -display.bounds.height / 2;
var layout = this.layoutMap_[id];
if (!layout || !layout.parentId) {
left = -bounds.width / 2;
top = -bounds.height / 2;
} else {
var layout = this.layoutMap_[display.id];
assert(layout.parentId);
var parentDisplay = this.displayMap_[layout.parentId];
var parentDisplayBounds = this.displayBoundsMap_[layout.parentId];
var parentBounds;
if (!(parentDisplay.id in this.boundsMap_))
this.calcDisplayBounds_(parentDisplay);
parentBounds = this.boundsMap_[parentDisplay.id];
if (!(layout.parentId in this.calculatedBoundsMap_))
this.calculateBounds_(layout.parentId, parentDisplayBounds);
parentBounds = this.calculatedBoundsMap_[layout.parentId];
left = parentBounds.left;
top = parentBounds.top;
switch (layout.position) {
case chrome.system.display.LayoutPosition.TOP:
top -= display.bounds.height;
top -= bounds.height;
break;
case chrome.system.display.LayoutPosition.RIGHT:
left += parentBounds.width;
......@@ -227,18 +248,52 @@ Polymer({
top += parentBounds.height;
break;
case chrome.system.display.LayoutPosition.LEFT:
left -= display.bounds.height;
left -= bounds.height;
break;
}
}
var result = {
left: left,
top: top,
width: display.bounds.width,
height: display.bounds.height
width: bounds.width,
height: bounds.height
};
this.boundsMap_[display.id] = result;
}
this.calculatedBoundsMap_[id] = result;
},
/**
* @param {string} id
* @param {?DragPosition} amount
*/
onDrag_(id, amount) {
id = id.substr(1); // Skip prefix
var newBounds;
if (!amount) {
// TODO(stevenjb): Resolve layout and send update.
newBounds = this.calculatedBoundsMap_[id];
} else {
// Make sure the dragged display is also selected.
if (id != this.selectedDisplay.id)
this.fire('select-display', id);
var calculatedBounds = this.calculatedBoundsMap_[id];
newBounds =
/** @type {chrome.system.display.Bounds} */ (
Object.assign({}, calculatedBounds));
newBounds.left += Math.round(amount.x / this.visualScale);
newBounds.top += Math.round(amount.y / this.visualScale);
// TODO(stevenjb): Update layout.
}
var left =
this.visualOffset_.left + Math.round(newBounds.left * this.visualScale);
var top =
this.visualOffset_.top + Math.round(newBounds.top * this.visualScale);
var div = this.$$('#_' + id);
div.style.left = '' + left + 'px';
div.style.top = '' + top + 'px';
},
});
})();
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Behavior for handling dragging elements in a container.
* Draggable elements must have the 'draggable' attribute set.
*/
/**
* @typedef {{
* x: number,
* y: number
* }}
*/
var DragPosition;
/** @polymerBehavior */
var DragBehavior = {
/**
* The id of the element being dragged, or empty if not dragging.
* @private {string}
*/
dragId_: '',
/** @private {boolean} */
enabled_: false,
/** @private {!HTMLDivElement|undefined} */
container_: undefined,
/** @private {?function(string, ?DragPosition):void} */
callback_: null,
/** @private {!DragPosition} */
dragStartLocation_: {x: 0, y: 0},
/**
* Used to ignore unnecessary drag events.
* @private {?DragPosition}
*/
lastTouchLocation_: null,
/** @private {?function(!Event)} */
mouseDownListener_: null,
/** @private {?function(!Event)} */
mouseMoveListener_: null,
/** @private {?function(!Event)} */
touchStartListener_: null,
/** @private {?function(!Event)} */
touchMoveListener_: null,
/** @private {?function(!Event)} */
endDragListener_: null,
/**
* @param {boolean} enabled
* @param {!HTMLDivElement=} opt_container
* @param {!function(string, ?DragPosition):void=} opt_callback
*/
initializeDrag: function(enabled, opt_container, opt_callback) {
this.enabled_ = enabled;
if (!enabled) {
if (this.container) {
this.container.removeEventListener('mousdown', this.mouseDownListener_);
this.mouseDownListener_ = null;
this.container.removeEventListener(
'mousemove', this.mouseMoveListener_);
this.mouseMoveListener_ = null;
this.container.removeEventListener(
'touchstart', this.touchStartListener_);
this.touchStartListener_ = null;
this.container.removeEventListener(
'touchmove', this.touchMoveListener_);
this.touchMoveListener_ = null;
this.container.removeEventListener('touchend', this.endDragListener_);
}
if (this.mouseUpListener_)
window.removeEventListener('mouseup', this.endDragListener_);
this.endDragListener_ = null;
return;
}
if (opt_container !== undefined)
this.container_ = opt_container;
var container = this.container_;
assert(container);
this.mouseDownListener_ = this.onMouseDown_.bind(this);
container.addEventListener('mousedown', this.mouseDownListener_, true);
this.mouseMoveListener_ = this.onMouseMove_.bind(this);
container.addEventListener('mousemove', this.mouseMoveListener_, true);
this.touchStartListener_ = this.onTouchStart_.bind(this);
container.addEventListener('touchstart', this.touchStartListener_, true);
this.touchMoveListener_ = this.onTouchMove_.bind(this);
container.addEventListener('touchmove', this.touchMoveListener_, true);
this.endDragListener_ = this.endDrag_.bind(this);
window.addEventListener('mouseup', this.endDragListener_, true);
container.addEventListener('touchend', this.endDragListener_, true);
if (opt_callback !== undefined)
this.callback_ = opt_callback;
},
/**
* @param {Event} e The mouse down event.
* @return {boolean}
* @private
*/
onMouseDown_: function(e) {
if (e.button != 0)
return true;
if (!e.target.getAttribute('draggable'))
return true;
e.preventDefault();
var target = assertInstanceof(e.target, HTMLElement);
return this.startDrag_(target, {x: e.pageX, y: e.pageY});
},
/**
* @param {Event} e The mouse move event.
* @return {boolean}
* @private
*/
onMouseMove_: function(e) {
e.preventDefault();
return this.processDrag_(e, {x: e.pageX, y: e.pageY});
},
/**
* @param {Event} e The touch start event.
* @return {boolean}
* @private
*/
onTouchStart_: function(e) {
if (e.touches.length != 1)
return false;
e.preventDefault();
var touch = e.touches[0];
this.lastTouchLocation_ = {x: touch.pageX, y: touch.pageY};
var target = assertInstanceof(e.target, HTMLElement);
return this.startDrag_(target, this.lastTouchLocation_);
},
/**
* @param {Event} e The touch move event.
* @return {boolean}
* @private
*/
onTouchMove_: function(e) {
if (e.touches.length != 1)
return true;
var touchLocation = {x: e.touches[0].pageX, y: e.touches[0].pageY};
// Touch move events can happen even if the touch location doesn't change
// and on small unintentional finger movements. Ignore these small changes.
if (this.lastTouchLocation_) {
/** @const */ var IGNORABLE_TOUCH_MOVE_PX = 1;
var xDiff = Math.abs(touchLocation.x - this.lastTouchLocation_.x);
var yDiff = Math.abs(touchLocation.y - this.lastTouchLocation_.y);
if (xDiff <= IGNORABLE_TOUCH_MOVE_PX && yDiff <= IGNORABLE_TOUCH_MOVE_PX)
return true;
}
this.lastTouchLocation_ = touchLocation;
e.preventDefault();
return this.processDrag_(e, touchLocation);
},
/**
* @param {!HTMLElement} target
* @param {!DragPosition} eventLocation
* @return {boolean}
* @private
*/
startDrag_: function(target, eventLocation) {
this.dragId_ = target.id;
this.dragStartLocation_ = eventLocation;
return false;
},
/**
* @param {Event} e
* @return {boolean}
* @private
*/
endDrag_: function(e) {
if (this.dragId_ && this.callback_)
this.callback_(this.dragId_, null);
this.dragId_ = '';
this.lastTouchLocation_ = null;
return false;
},
/**
* @param {Event} e The event which triggers this drag.
* @param {DragPosition} eventLocation The location of the event.
* @return {boolean}
* @private
*/
processDrag_: function(e, eventLocation) {
if (!this.dragId_)
return true;
if (this.callback_) {
var delta = {
x: eventLocation.x - this.dragStartLocation_.x,
y: eventLocation.y - this.dragStartLocation_.y,
};
this.callback_(this.dragId_, delta);
}
return false;
},
};
......@@ -372,6 +372,12 @@
<structure name="IDR_SETTINGS_DEVICE_TOUCHPAD_JS"
file="device_page/touchpad.js"
type="chrome_html" />
<structure name="IDR_SETTINGS_DEVICE_DRAG_BEHAVIOR_HTML"
file="device_page/drag_behavior.html"
type="chrome_html" />
<structure name="IDR_SETTINGS_DEVICE_DRAG_BEHAVIOR_JS"
file="device_page/drag_behavior.js"
type="chrome_html" />
</if>
<structure name="IDR_SETTINGS_DIRECTION_DELEGATE_HTML"
file="direction_delegate.html"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment