Commit b656a14d authored by rbpotter's avatar rbpotter Committed by Commit Bot

Print Preview Componentization: Make custom margins movable

Make custom margins draggable and allow for updates with text boxes.
Does not yet update margins ticket item or limit margins values based
on page size.

Bug: 773928, 812095
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: Ifa0e270bb32cc77c420788dae2daecf7817c063f
Reviewed-on: https://chromium-review.googlesource.com/961483
Commit-Queue: Rebekah Potter <rbpotter@chromium.org>
Reviewed-by: default avatarDemetrios Papadopoulos <dpapad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#543892}
parent b24363e5
......@@ -124,7 +124,8 @@
<div id="preview-area-container">
<print-preview-preview-area id="previewArea" settings="{{settings}}"
destination="[[destination_]]" document-info="{{documentInfo_}}"
state="[[state]]" on-preview-failed="onPreviewFailed_"
state="[[state]]" measurement-system="[[measurementSystem_]]"
on-preview-failed="onPreviewFailed_"
on-preview-loaded="onPreviewLoaded_">
</print-preview-preview-area>
</div>
......
......@@ -67,16 +67,19 @@ Polymer({
type: Boolean,
notify: true,
computed: 'computeControlsDisabled_(state)',
}
},
/** @private {?print_preview.MeasurementSystem} */
measurementSystem_: {
type: Object,
notify: true,
value: null,
},
},
/** @private {?WebUIListenerTracker} */
listenerTracker_: null,
/** @type {!print_preview.MeasurementSystem} */
measurementSystem_: new print_preview.MeasurementSystem(
',', '.', print_preview.MeasurementSystemUnitType.IMPERIAL),
/** @private {?print_preview.NativeLayer} */
nativeLayer_: null,
......@@ -142,7 +145,7 @@ Polymer({
this.notifyPath('documentInfo_.title');
this.notifyPath('documentInfo_.pageCount');
this.$.model.setStickySettings(settings.serializedAppStateStr);
this.measurementSystem_.setSystem(
this.measurementSystem_ = new print_preview.MeasurementSystem(
settings.thousandsDelimeter, settings.decimalDelimeter,
settings.unitType);
this.setSetting('selectionOnly', settings.shouldPrintSelectionOnly);
......
......@@ -188,9 +188,12 @@
'dependencies': [
'../data/compiled_resources2.gyp:coordinate2d',
'../data/compiled_resources2.gyp:margins',
'../data/compiled_resources2.gyp:measurement_system',
'../data/compiled_resources2.gyp:size',
'margin_control',
'settings_behavior',
'<(DEPTH)/ui/webui/resources/js/compiled_resources2.gyp:event_tracker',
'<(EXTERNS_GYP):pending',
],
'includes': ['../../../../../third_party/closure_compiler/compile_js2.gypi'],
},
......
......@@ -36,25 +36,25 @@
cursor: default;
}
.line {
#line {
border: 1px dashed rgb(64, 128, 250);
}
.disabled .line {
.disabled #line {
border-color: gray;
}
:host([side=top]) .line,
:host([side=bottom]) .line {
:host([side=top]) #line,
:host([side=bottom]) #line {
width: 100%;
}
:host([side=left]) .line,
:host([side=right]) .line {
:host([side=left]) #line,
:host([side=right]) #line {
height: 100%;
}
.textbox {
#textbox {
background-color: #2a2a2a;
border: 1px solid #888;
box-sizing: border-box;
......@@ -68,32 +68,33 @@
width: 60px;
}
.textbox.invalid {
#textbox.invalid {
background-color: rgb(193, 27, 23);
}
:host([side=top]) .textbox {
:host([side=top]) #textbox {
left: 50%;
top: 8px;
}
:host([side=right]) .textbox {
:host([side=right]) #textbox {
right: 8px;
top: 50%;
}
:host([side=bottom]) .textbox {
:host([side=bottom]) #textbox {
bottom: 8px;
right: 50%;
}
:host([side=left]) .textbox {
:host([side=left]) #textbox {
bottom: 50%;
left: 8px;
}
</style>
<div class="line"></div>
<input class="textbox" aria-hidden$="[[invisible]]" type="text">
<div id="line"></div>
<input id="textbox" aria-hidden$="[[getAriaHidden_(invisible)]]" type="text"
disabled="[[invisible]]" on-input="onInput_">
</template>
<script src="margin_control.js"></script>
</dom-module>
......@@ -62,11 +62,68 @@ Polymer({
['updatePosition_(positionInPts_, scaleTransform, translateTransform, ' +
'pageSize, side)'],
/** @param {string} value New value of the margin control's textbox. */
setTextboxValue: function(value) {
const textbox = this.$.textbox;
if (textbox.value != value)
textbox.value = value;
},
/** @param {number} position The new position for the margin control. */
setPositionInPts(position) {
setPositionInPts: function(position) {
this.positionInPts_ = position;
},
/**
* @return {string} 'true' or 'false', indicating whether the input should be
* aria-hidden.
* @private
*/
getAriaHidden_: function() {
return this.invisible.toString();
},
/**
* Converts a value in pixels to points.
* @param {number} pixels Pixel value to convert.
* @return {number} Given value expressed in points.
*/
convertPixelsToPts: function(pixels) {
let pts;
const orientationEnum = print_preview.ticket_items.CustomMarginsOrientation;
if (this.side == orientationEnum.TOP) {
pts = pixels - this.translateTransform.y + RADIUS_PX;
pts /= this.scaleTransform;
} else if (this.side == orientationEnum.RIGHT) {
pts = pixels - this.translateTransform.x + RADIUS_PX;
pts /= this.scaleTransform;
pts = this.pageSize.width - pts;
} else if (this.side == orientationEnum.BOTTOM) {
pts = pixels - this.translateTransform.y + RADIUS_PX;
pts /= this.scaleTransform;
pts = this.pageSize.height - pts;
} else {
assert(this.side == orientationEnum.LEFT);
pts = pixels - this.translateTransform.x + RADIUS_PX;
pts /= this.scaleTransform;
}
return pts;
},
/**
* @param {!PointerEvent} event A pointerdown event triggered by this element.
* @return {boolean} Whether the margin should start being dragged.
*/
shouldDrag: function(event) {
return !this.$.textbox.disabled && event.button == 0 &&
(event.path[0] == this || event.path[0] == this.$.line);
},
/** @private */
onInput_: function() {
this.fire('text-change', this.$.textbox.value);
},
/** @private */
updatePosition_: function() {
const orientationEnum = print_preview.ticket_items.CustomMarginsOrientation;
......
<link rel="import" href="chrome://resources/html/polymer.html">
<link rel="import" href="chrome://resources/html/event_tracker.html">
<link rel="import" href="margin_control.html">
<link rel="import" href="settings_behavior.html">
<link rel="import" href="../data/coordinate2d.html">
<link rel="import" href="../data/margins.html">
<link rel="import" href="../data/measurement_system.html">
<link rel="import" href="../data/size.html">
<dom-module id="print-preview-margin-control-container">
<template>
<style>
:host([dragging_=dragging-vertical]) {
cursor: ns-resize;
}
:host([dragging_=dragging-horizontal]) {
cursor: ew-resize;
}
</style>
<template is="dom-repeat" items="[[marginSides_]]">
<print-preview-margin-control side="[[item]]" invisible="[[hidden_]]"
<print-preview-margin-control side="[[item]]"
invisible="[[isInvisible_(available_, visible)]]"
translate-transform="[[translateTransform_]]"
clip-size="[[clipSize_]]" scale-transform="[[scaleTransform_]]"
page-size="[[pageSize]]">
page-size="[[pageSize]]" on-pointerdown="onPointerDown_"
on-text-change="onTextChange_">
</print-preview-margin-control>
</template>
</template>
......
......@@ -16,6 +16,9 @@ Polymer({
previewLoaded: Boolean,
/** @type {?print_preview.MeasurementSystem} */
measurementSystem: Object,
/** @private {number} */
scaleTransform_: {
type: Number,
......@@ -38,11 +41,11 @@ Polymer({
},
/** @private {boolean} */
hidden_: {
available_: {
type: Boolean,
notify: true,
computed: 'computeHidden_(' +
'previewLoaded, settings.margins.value, pageSize, clipSize_)',
computed: 'computeAvailable_(previewLoaded, settings.margins.value)',
observer: 'onAvailableChange_',
},
/**
......@@ -58,17 +61,227 @@ Polymer({
print_preview.ticket_items.CustomMarginsOrientation.LEFT,
],
},
/**
* String attribute used to set cursor appearance. Possible values:
* empty (''): No margin control is currently being dragged.
* 'dragging-horizontal': The left or right control is being dragged.
* 'dragging-vertical': The top or bottom control is being dragged.
* @private {string}
*/
dragging_: {
type: String,
reflectToAttribute: true,
value: '',
},
/** @private {boolean} */
visible: {
type: Boolean,
notify: true,
reflectToAttribute: true,
value: true,
},
},
/** @private {!print_preview.Coordinate2d} */
pointerStartPositionInPixels_: new print_preview.Coordinate2d(0, 0),
/** @private {?print_preview.Coordinate2d} */
marginStartPositionInPixels_: null,
/**
* @return {boolean}
* @private
*/
computeHidden_: function() {
return !this.previewLoaded || !this.clipSize_ ||
this.getSetting('margins').value !=
print_preview.ticket_items.MarginsTypeValue.CUSTOM ||
!this.pageSize;
computeAvailable_: function() {
return this.previewLoaded && !!this.clipSize_ &&
this.getSetting('margins').value ==
print_preview.ticket_items.MarginsTypeValue.CUSTOM &&
!!this.pageSize;
},
/** @private */
onAvailableChange_: function() {
this.visible = this.available_;
},
/**
* @return {boolean} Whether the margin controls should be invisible.
* @private
*/
isInvisible_: function() {
return !this.available_ || !this.visible;
},
/**
* @param {!print_preview.ticket_items.CustomMarginsOrientation} orientation
* Orientation value to test.
* @return {boolean} Whether the given orientation is TOP or BOTTOM.
* @private
*/
isTopOrBottom_: function(orientation) {
return orientation ==
print_preview.ticket_items.CustomMarginsOrientation.TOP ||
orientation ==
print_preview.ticket_items.CustomMarginsOrientation.BOTTOM;
},
/**
* Moves the position of the given control to the desired position in
* pixels within some constraint minimum and maximum.
* @param {!HTMLElement} control Control to move.
* @param {!print_preview.Coordinate2d} posInPixels Desired position to move
* to in pixels.
* @private
*/
moveControlWithConstraints_: function(control, posInPixels) {
let newPosInPts;
if (this.isTopOrBottom_(
/** @type {print_preview.ticket_items.CustomMarginsOrientation} */ (
control.side))) {
newPosInPts = control.convertPixelsToPts(posInPixels.y);
} else {
newPosInPts = control.convertPixelsToPts(posInPixels.x);
}
newPosInPts = Math.max(0, newPosInPts);
newPosInPts = Math.round(newPosInPts);
control.setPositionInPts(newPosInPts);
control.setTextboxValue(this.serializeValueFromPts_(newPosInPts));
},
/**
* @param {number} value Value in points to serialize.
* @return {string} String representation of the value in the system's local
* units.
* @private
*/
serializeValueFromPts_: function(value) {
assert(this.measurementSystem);
value = this.measurementSystem.convertFromPoints(value);
value = this.measurementSystem.roundValue(value);
return value + this.measurementSystem.unitSymbol;
},
/**
* Translates the position of the margin control relative to the pointer
* position in pixels.
* @param {!print_preview.Coordinate2d} pointerPosition New position of
* the pointer.
* @return {!print_preview.Coordinate2d} New position of the margin control.
*/
translatePointerToPositionInPixels: function(pointerPosition) {
return new print_preview.Coordinate2d(
pointerPosition.x - this.pointerStartPositionInPixels_.x +
this.marginStartPositionInPixels_.x,
pointerPosition.y - this.pointerStartPositionInPixels_.y +
this.marginStartPositionInPixels_.y);
},
/**
* Called when the pointer moves in the custom margins component. Moves the
* dragged margin control.
* @param {!PointerEvent} event Contains the position of the pointer.
* @private
*/
onPointerMove_: function(event) {
const control =
/** @type {!PrintPreviewMarginControlElement} */ (event.target);
this.moveControlWithConstraints_(
control,
this.translatePointerToPositionInPixels(
new print_preview.Coordinate2d(event.x, event.y)));
this.updateClippingMask(this.clipSize_);
},
/**
* Called when the pointer is released in the custom margins component.
* Releases the dragged margin control.
* @param {!PointerEvent} event Contains the position of the pointer.
* @private
*/
onPointerUp_: function(event) {
const control =
/** @type {!PrintPreviewMarginControlElement} */ (event.target);
this.dragging_ = '';
const posInPixels = this.translatePointerToPositionInPixels(
new print_preview.Coordinate2d(event.x, event.y));
this.moveControlWithConstraints_(control, posInPixels);
this.updateClippingMask(this.clipSize_);
this.unlisten(control, 'pointercancel', 'onPointerUp_');
this.unlisten(control, 'pointerup', 'onPointerUp_');
this.unlisten(control, 'pointermove', 'onPointerMove_');
this.fire('margin-drag-changed', false);
},
/** @return {boolean} Whether the user is dragging a margin control. */
isDragging: function() {
return this.dragging_ != '';
},
/**
* @param {!CustomEvent} e Event containing the new textbox value.
* @private
*/
onTextChange_: function(e) {
const marginValue = this.parseValueToPts_(/** @type {string} */ (e.detail));
e.target.setPositionInPts(marginValue);
},
/**
* @param {!PointerEvent} e Fired when pointerdown occurs on a margin control.
* @private
*/
onPointerDown_: function(e) {
const control =
/** @type {!PrintPreviewMarginControlElement} */ (e.target);
if (!control.shouldDrag(e))
return;
this.pointerStartPositionInPixels_ =
new print_preview.Coordinate2d(e.x, e.y);
this.marginStartPositionInPixels_ =
new print_preview.Coordinate2d(control.offsetLeft, control.offsetTop);
this.dragging_ =
this.isTopOrBottom_(
/** @type {print_preview.ticket_items.CustomMarginsOrientation} */ (
control.side)) ?
'dragging-vertical' :
'dragging-horizontal';
this.listen(control, 'pointercancel', 'onPointerUp_');
this.listen(control, 'pointerup', 'onPointerUp_');
this.listen(control, 'pointermove', 'onPointerMove_');
control.setPointerCapture(e.pointerId);
this.fire('margin-drag-changed', true);
},
/**
* @param {string} value Value to parse to points. E.g. '3.40"' or '200mm'.
* @return {?number} Value in points represented by the input value.
* @private
*/
parseValueToPts_: function(value) {
// Removing whitespace anywhere in the string.
value = value.replace(/\s*/g, '');
if (value.length == 0) {
return null;
}
assert(this.measurementSystem);
const validationRegex = new RegExp(
'^(^-?)(\\d)+(\\' + this.measurementSystem.thousandsDelimeter +
'\\d{3})*(\\' + this.measurementSystem.decimalDelimeter + '\\d*)?' +
'(' + this.measurementSystem.unitSymbol + ')?$');
if (validationRegex.test(value)) {
// Replacing decimal point with the dot symbol in order to use
// parseFloat() properly.
const replacementRegex =
new RegExp('\\' + this.measurementSystem.decimalDelimeter + '{1}');
value = value.replace(replacementRegex, '.');
return this.measurementSystem.convertToPoints(parseFloat(value));
}
return null;
},
/**
......@@ -101,5 +314,6 @@ Polymer({
return;
}
this.clipSize_ = clipSize;
this.notifyPath('clipSize_');
},
});
......@@ -143,7 +143,9 @@
</div>
<print-preview-margin-control-container id="marginControlContainer"
page-size="[[documentInfo.pageSize]]" settings="{{settings}}"
preview-loaded="[[previewLoaded_]]">
measurement-system="[[measurementSystem]]"
preview-loaded="[[previewLoaded_]]"
on-margin-drag-changed="onMarginDragChanged_">
</print-preview-margin-control-container>
</template>
<script src="preview_area.js"></script>
......
......@@ -64,6 +64,9 @@ Polymer({
observer: 'onStateChanged_',
},
/** @type {?print_preview.MeasurementSystem} */
measurementSystem: Object,
/** @private {string} */
previewState_: {
type: String,
......@@ -79,6 +82,11 @@ Polymer({
},
},
listeners: {
'pointerover': 'onPointerOver_',
'pointerout': 'onPointerOut_',
},
observers: [
'onSettingsChanged_(settings.color.value, settings.cssBackground.value, ' +
'settings.fitToPage.value, settings.headerFooter.value, ' +
......@@ -139,6 +147,46 @@ Polymer({
return this.previewLoaded_;
},
/**
* Called when the pointer moves onto the component. Shows the margin
* controls if custom margins are being used.
* @param {!Event} event Contains element pointer moved from.
* @private
*/
onPointerOver_: function(event) {
const marginControlContainer = this.$.marginControlContainer;
let fromElement = event.fromElement;
while (fromElement != null) {
if (fromElement == marginControlContainer)
return;
fromElement = fromElement.parentElement;
}
marginControlContainer.visible = true;
},
/**
* Called when the pointer moves off of the component. Hides the margin
* controls if they are visible.
* @param {!Event} event Contains element pointer moved to.
* @private
*/
onPointerOut_: function(event) {
const marginControlContainer = this.$.marginControlContainer;
let toElement = event.toElement;
while (toElement != null) {
if (toElement == marginControlContainer)
return;
toElement = toElement.parentElement;
}
if (marginControlContainer.isDragging())
return;
marginControlContainer.visible = false;
},
/** @private */
onSettingsChanged_: function() {
if (this.state == print_preview_new.State.READY) {
......@@ -413,6 +461,20 @@ Polymer({
this.onPreviewVisualStateChange_.bind(this));
},
/**
* Called when dragging margins starts or stops.
*/
onMarginDragChanged_: function(e) {
if (!this.plugin_)
return;
// When hovering over the plugin (which may be in a separate iframe)
// pointer events will be sent to the frame. When dragging the margins,
// we don't want this to happen as it can cause the margin to stop
// being draggable.
this.plugin_.style.pointerEvents = e.detail ? 'none' : 'auto';
},
/**
* Requests a preview from the native layer.
* @return {!Promise} Promise that resolves when the preview has been
......
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