blob: 133a4dfabc5d4ff0e29ec884d76badb5726aec28 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/******** Constants ********/
const MIN_PANE_DIMS = new Dims2D(50, 150);
/******** LayoutController ********/
class LayoutController {
constructor(model, divMain, divPaneScreenshot, divMainSplitter, divScreenshot,
divScreenshotInfo, hintCtrl) {
this.model = model;
this.visOpts = model.visOpts;
this.el = {
divMain,
divPaneScreenshot,
divScreenshotInfo,
divMainSplitter,
divScreenshot,
};
this.hintCtrl = hintCtrl;
this.activeDragState = null;
this.mainSplitterDragHandler = new DragHandler(this.el.divMainSplitter, {
pointerStyle: this.visOpts.layoutMode.cursor,
onDragStart: (e) => this._handleDragStart(e),
onDrag: (e, dx, dy) => this._handleDrag(e, dx, dy),
onDragEnd: (e) => this._handleDragEnd(e, false),
onDragCancel: (e) => this._handleDragEnd(e, true),
onDoubleClick: (e) => this._autoResize(),
});
this.el.divMainSplitter.addEventListener('pointerenter', () => {
this.hintCtrl.setHint(HINT.LAYOUT_SPLITTER);
});
this.el.divMainSplitter.addEventListener('pointerleave', () => {
this.hintCtrl.clear();
});
this.setLayoutMode(LayoutMode.LEFT);
}
/** @param {!LayoutMode} mode */
setLayoutMode(mode) {
this.visOpts.layoutMode = mode;
this.el.divMain.dataset.layout = mode.name;
this.mainSplitterDragHandler.setPointerStyle(mode.cursor);
}
/** @param {!LayoutMode} mode */
_getParentSizeAlongMode(mode) {
const {width, height} = this.el.divMain.getBoundingClientRect();
return new Dims2D(width, height).sizeAlong(mode.dir);
}
/**
* @param {!LayoutMode} mode
* @param {number} ratio
*/
_computePaneScreenshotRatio(mode, ratio) {
const parentSize = this._getParentSizeAlongMode(mode);
const minSize = MIN_PANE_DIMS.sizeAlong(mode.dir);
const ratioBound =
(parentSize <= 0) ? 0.5 : Math.min(minSize / parentSize, 0.5);
return clip(ratioBound, ratio, 1.0 - ratioBound);
}
_setPaneScreenshotRatio(ratio) {
const safeRatio =
this._computePaneScreenshotRatio(this.visOpts.layoutMode, ratio);
this.el.divPaneScreenshot.style.flexBasis = `${safeRatio * 100}%`;
}
_readPaneScreenshotRatio() {
// If flex-basis is not explicitly set, assume default 50% from CSS.
const basis = this.el.divPaneScreenshot.style.flexBasis;
return (basis && basis.endsWith('%')) ? parseFloat(basis) / 100 : 0.5;
}
_handleDragStart(e) {
this.activeDragState = {
startRatio: this._readPaneScreenshotRatio(),
};
}
_handleDrag(e, dx, dy) {
const state = this.activeDragState;
if (!state) return;
// Resize along axis based on ratio change.
const parentSize = this._getParentSizeAlongMode(this.visOpts.layoutMode);
if (parentSize > 0) {
const dSize = -this.visOpts.layoutMode.dir.dotxy(dx, dy);
this._setPaneScreenshotRatio(state.startRatio + dSize / parentSize);
}
}
_handleDragEnd(e, isCancel) {
const state = this.activeDragState;
if (!state) return;
if (isCancel) this._setPaneScreenshotRatio(state.startRatio);
this.activeDragState = null;
}
_autoResize() {
if (!this.model.isLoaded) return;
const layoutMode = this.visOpts.layoutMode;
const parentSizePx = this._getParentSizeAlongMode(layoutMode);
if (parentSizePx <= 0) return;
const imgDims = this.visOpts.getAutoResizeDims();
const rect = this.el.divScreenshot.getBoundingClientRect();
const thickness = getScrollbarThickness();
// 1. Primary Scrollbar Detection based on stable container dims.
let hScrollNeeded = imgDims.w > rect.width;
let vScrollNeeded = imgDims.h > rect.height;
// 2. Ripple Effect Detection.
// If one scrollbar appears, it might trigger the other by eating space.
if (hScrollNeeded && !vScrollNeeded) {
if (imgDims.h > rect.height - thickness) vScrollNeeded = true;
} else if (vScrollNeeded && !hScrollNeeded) {
if (imgDims.w > rect.width - thickness) hScrollNeeded = true;
}
// 3. Calculate total overhead based on layout axis.
let overhead = 0;
if (layoutMode.orientation === ORIENTATION.HORIZ) {
if (vScrollNeeded) overhead += thickness;
} else { // layoutMode.orientation === ORIENTATION.VERT
overhead += this.el.divScreenshotInfo.offsetHeight;
if (hScrollNeeded) overhead += thickness;
}
const targetSizePx = imgDims.sizeAlong(layoutMode.dir) + overhead;
this._setPaneScreenshotRatio(targetSizePx / parentSizePx);
}
}