blob: 843b263442f8398d07db05667584af8e58505daa [file] [log] [blame]
// Copyright 2018 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.
'use strict';
/**
* Namespace for the Camera app.
*/
var cca = cca || {};
/**
* Namespace for views.
*/
cca.views = cca.views || {};
/**
* Namespace for Camera view.
*/
cca.views.camera = cca.views.camera || {};
/**
* Creates a controller to handle layouts of Camera view.
* @constructor
*/
cca.views.camera.Layout = function() {
/**
* CSS sylte of the shifted right-stripe.
* @type {CSSStyleDeclaration}
* @private
*/
this.rightStripe_ = cca.views.camera.Layout.cssStyle_(
'body.shift-right-stripe .right-stripe, ' +
'body.shift-right-stripe.tablet-landscape .actions-group');
/**
* CSS sylte of the shifted bottom-stripe.
* @type {CSSStyleDeclaration}
* @private
*/
this.bottomStripe_ = cca.views.camera.Layout.cssStyle_(
'body.shift-bottom-stripe .bottom-stripe, ' +
'body.shift-bottom-stripe:not(.tablet-landscape) .actions-group');
/**
* CSS sylte of the shifted left-stripe.
* @type {CSSStyleDeclaration}
* @private
*/
this.leftStripe_ = cca.views.camera.Layout.cssStyle_(
'body.shift-left-stripe .left-stripe');
/**
* CSS sylte of the shifted top-stripe.
* @type {CSSStyleDeclaration}
* @private
*/
this.topStripe_ = cca.views.camera.Layout.cssStyle_(
'body.shift-top-stripe .top-stripe');
// End of properties, seal the object.
Object.seal(this);
};
/**
* CSS rules.
* @type {Array<CSSRule>}
* @private
*/
cca.views.camera.Layout.cssRules_ =
[].slice.call(document.styleSheets[0].cssRules);
/**
* Gets the CSS style by the given selector.
* @param {string} selector Selector text.
* @return {CSSStyleDeclaration}
* @private
*/
cca.views.camera.Layout.cssStyle_ = function(selector) {
return cca.views.camera.Layout.cssRules_.find(
(rule) => rule.selectorText == selector).style;
};
/**
* Updates the video element size for previewing in the window.
* @return {Array<number>} Letterbox size in [width, height].
* @private
*/
cca.views.camera.Layout.prototype.updatePreviewSize_ = function() {
// Make video content keeps its aspect ratio inside the window's inner-bounds;
// it may fill up the window or be letterboxed when fullscreen/maximized.
// Don't use app-window.innerBounds' width/height properties during resizing
// as they are not updated immediately.
var video = document.querySelector('#preview-video');
if (video.videoHeight) {
var f = cca.util.isWindowFullSize() ? Math.min : Math.max;
var scale = f(window.innerHeight / video.videoHeight,
window.innerWidth / video.videoWidth);
video.width = scale * video.videoWidth;
video.height = scale * video.videoHeight;
}
return [window.innerWidth - video.width, window.innerHeight - video.height];
};
/**
* Updates the layout for video-size or window-size changes.
*/
cca.views.camera.Layout.prototype.update = function() {
// TODO(yuli): Replace tablet-landscape with full-wnd/vert-orien.
var fullWindow = cca.util.isWindowFullSize();
var tabletLandscape = fullWindow && (window.innerWidth > window.innerHeight);
cca.state.set('tablet-landscape', tabletLandscape);
var [letterboxW, letterboxH] = this.updatePreviewSize_();
var [halfW, halfH] = [letterboxW / 2, letterboxH / 2];
var [rightBox, bottomBox, leftBox, topBox] = [halfW, halfH, halfW, halfH];
// Shift preview to accommodate the shutter in letterbox if applicable.
var dimens = (shutter) => {
// These following constants need kept in sync with relevant values in css.
// preset: button-size + preset-margin + min-margin
// least: button-size + 2 * min-margin
// gap: preset-margin - min-margin
// baseline: preset-baseline
return shutter ? [100, 88, 12, 56] : [76, 56, 20, 48];
};
var accommodate = (measure) => {
var [, leastShutter] = dimens(true);
return (measure > leastShutter) && (measure < leastShutter * 2);
};
if (cca.state.set('shift-preview-left',
fullWindow && tabletLandscape && accommodate(letterboxW))) {
[rightBox, leftBox] = [letterboxW, 0];
}
if (cca.state.set('shift-preview-top',
fullWindow && !tabletLandscape && accommodate(letterboxH))) {
[bottomBox, topBox] = [letterboxH, 0];
}
// Shift buttons' stripes if necessary. Buttons are either fully on letterbox
// or preview while the shutter/options keep minimum margin to either edges.
var calc = (measure, least) => {
return (measure >= least) ?
Math.round(measure / 2) : // Centered in letterbox.
Math.round(measure + least / 2); // Inset in preview.
};
var shift = (stripe, name, measure, shutter) => {
var [preset, least, gap, baseline] = dimens(shutter);
if (cca.state.set('shift-' + name + '-stripe',
measure > gap && measure < preset)) {
baseline = calc(measure, least);
stripe.setProperty(name, baseline + 'px');
}
// Return shutter's baseline in letterbox if applicable.
return (shutter && baseline < measure) ? baseline : 0;
};
var symm = (stripe, name, measure, shutterBaseline) => {
if (measure && shutterBaseline) {
cca.state.set('shift-' + name + '-stripe', true);
stripe.setProperty(name, shutterBaseline + 'px');
return true;
}
return false;
};
// Make both letterbox look symmetric if shutter is in either letterbox.
if (!symm(this.leftStripe_, 'left', leftBox,
shift(this.rightStripe_, 'right', rightBox, tabletLandscape))) {
shift(this.leftStripe_, 'left', leftBox, false);
}
if (!symm(this.topStripe_, 'top', topBox,
shift(this.bottomStripe_, 'bottom', bottomBox, !tabletLandscape))) {
shift(this.topStripe_, 'top', topBox, false);
}
};