/* ScummVM - Graphic Adventure Engine * * ScummVM is the legal property of its developers, whose names * are too numerous to list here. Please refer to the COPYRIGHT * file distributed with this source distribution. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "common/textconsole.h" #include "tetraedge/tetraedge.h" #include "tetraedge/game/application.h" #include "tetraedge/te/te_layout.h" #include "tetraedge/te/te_i_3d_object2.h" namespace Tetraedge { TeLayout::TeLayout() : Te3DObject2(), _autoz(true), _needZUpdate(true), _updatingZ(false), _needZSizeUpdate(true), _updatingZSize(false), _sizeChanged(true), _updatingSize(false), _positionChanged(true), _updatingPosition(false), _worldMatrixChanged(true), _updatingWorldMatrix(false), _drawMode(TeILayout::DrawMode0), _sizeType(CoordinatesType::ABSOLUTE), _userSize(1.0f, 1.0f, 1.0f), _anchor(0.5f, 0.5f, 0.5f), _ratio(1.0f), _safeAreaRatio(1.333333f), _ratioMode(RATIO_MODE_NONE), _positionType(CoordinatesType::RELATIVE_TO_PARENT) { _userPosition = _position = TeVector3f32(0.5f, 0.5f, 0.5f); _size = TeVector3f32(1.0f, 1.0f, 1.0f); _onChildSizeChangedCallback.reset( new TeCallback0Param(this, &TeLayout::onChildSizeChanged)); _onParentSizeChangedCallback.reset( new TeCallback0Param(this, &TeLayout::onParentSizeChanged)); _onParentWorldTransformationMatrixChangedCallback.reset( new TeCallback0Param(this, &TeLayout::onParentWorldTransformationMatrixChanged)); updateSize(); updateMesh(); } TeLayout::~TeLayout() { if (parent() && _onParentSizeChangedCallback) { parent()->onSizeChanged().remove(_onParentSizeChangedCallback); _onParentSizeChangedCallback.reset(); parent()->onWorldTransformationMatrixChanged().remove(_onParentWorldTransformationMatrixChangedCallback); _onParentWorldTransformationMatrixChangedCallback.reset(); } if (_onChildSizeChangedCallback) { for (auto &child : childList()) { child->onSizeChanged().remove(_onChildSizeChangedCallback); } } } void TeLayout::addChild(Te3DObject2 *child) { Te3DObject2::addChild(child); if (_onChildSizeChangedCallback) { child->onSizeChanged().push_back(_onChildSizeChangedCallback); } _needZSizeUpdate = true; _needZUpdate = true; updateZSize(); updateZ(); } void TeLayout::addChildBefore(Te3DObject2 *child, const Te3DObject2 *ref) { Te3DObject2::addChildBefore(child, ref); if (_onChildSizeChangedCallback) { child->onSizeChanged().push_back(_onChildSizeChangedCallback); } _needZSizeUpdate = true; _needZUpdate = true; updateZSize(); updateZ(); } void TeLayout::removeChild(Te3DObject2 *child) { if (_onChildSizeChangedCallback) { child->onSizeChanged().remove(_onChildSizeChangedCallback); } Te3DObject2::removeChild(child); _needZSizeUpdate = true; _needZUpdate = true; updateZSize(); updateZ(); } const TeVector3f32 &TeLayout::anchor() { return _anchor; } void TeLayout::disableAutoZ() { _autoz = false; } void TeLayout::enableAutoZ() { _autoz = true; } bool TeLayout::isAutoZEnabled() { return _autoz; } void TeLayout::draw() { if (visible() && worldVisible()) { // Ensure world transform is up-to-date. worldTransformationMatrix(); for (auto &child : childList()) { child->draw(); } } } static const float EPSILON = 1.192093e-07f; bool TeLayout::isMouseIn(const TeVector2s32 &mouseloc) { const TeVector3f32 transformedPos = transformMousePosition(mouseloc); const TeVector3f32 halfSize = size() / 2.0; return ( (transformedPos.x() >= -halfSize.x() - EPSILON) && (transformedPos.x() < halfSize.x() + EPSILON) && (transformedPos.y() >= -halfSize.y() - EPSILON) && (transformedPos.y() < halfSize.y() + EPSILON)); } TeILayout::DrawMode TeLayout::mode() { return _drawMode; } bool TeLayout::onChildSizeChanged() { _needZSizeUpdate = true; _needZUpdate = true; updateSize(); if (!_updatingZSize) updateZSize(); return false; } bool TeLayout::onParentSizeChanged() { _sizeChanged = true; _positionChanged = true; _worldMatrixChanged = true; return false; } bool TeLayout::onParentWorldTransformationMatrixChanged() { _worldMatrixChanged = true; return false; } TeVector3f32 TeLayout::position() { updateZ(); updatePosition(); return _position; } TeLayout::CoordinatesType TeLayout::positionType() const { return _positionType; } float TeLayout::ratio() const { return _ratio; } TeLayout::RatioMode TeLayout::ratioMode() const { return _ratioMode; } float TeLayout::safeAreaRatio() const { return _safeAreaRatio; } void TeLayout::setAnchor(const TeVector3f32 &anchor) { if (_anchor != anchor) { _anchor = anchor; _positionChanged = true; _worldMatrixChanged = true; updatePosition(); } } void TeLayout::setMode(DrawMode mode) { _drawMode = mode; } void TeLayout::setParent(Te3DObject2 *parent) { assert(parent != this); Te3DObject2 *oldParent = Te3DObject2::parent(); if (oldParent) { if (_onParentSizeChangedCallback) oldParent->onSizeChanged().remove(_onParentSizeChangedCallback); if (_onParentWorldTransformationMatrixChangedCallback) oldParent->onWorldTransformationMatrixChanged().remove(_onParentWorldTransformationMatrixChangedCallback); } // TeLayout &mainWindowLayout = g_engine->getApplication()->getMainWindow(); mainWindowLayout.onSizeChanged().remove(_onMainWindowChangedCallback); Te3DObject2::setParent(parent); if (parent) { if (_onParentSizeChangedCallback) parent->onSizeChanged().push_back(_onParentSizeChangedCallback); if (_onParentWorldTransformationMatrixChangedCallback) parent->onWorldTransformationMatrixChanged().push_back(_onParentWorldTransformationMatrixChangedCallback); if (_onMainWindowChangedCallback) mainWindowLayout.onSizeChanged().push_back(_onMainWindowChangedCallback); } _needZUpdate = true; _sizeChanged = true; _positionChanged = true; _worldMatrixChanged = true; updateSize(); } void TeLayout::setPosition(const TeVector3f32 &pos) { TeVector3f32 pos3d(pos.x(), pos.y(), _userPosition.z()); if (_userPosition != pos3d) { _userPosition.x() = pos.x(); _userPosition.y() = pos.y(); _positionChanged = true; _worldMatrixChanged = true; } } void TeLayout::setPositionType(CoordinatesType newtype) { if (_positionType != newtype) { _positionType = newtype; _positionChanged = true; _worldMatrixChanged = true; } } void TeLayout::setRatio(float val) { if (_ratio != val) { _ratio = val; _sizeChanged = true; _worldMatrixChanged = true; } } void TeLayout::setRatioMode(RatioMode mode) { if (_ratioMode != mode) { _ratioMode = mode; _sizeChanged = true; _worldMatrixChanged = true; } } void TeLayout::setRotation(const TeQuaternion &rot) { if (rot != _rotation) { Te3DObject2::setRotation(rot); _worldMatrixChanged = true; } } void TeLayout::setSafeAreaRatio(float ratio) { if (_safeAreaRatio != ratio) { _safeAreaRatio = ratio; _sizeChanged = true; _worldMatrixChanged = true; } } void TeLayout::setScale(const TeVector3f32 &scale) { Te3DObject2::setScale(scale); _worldMatrixChanged = true; } void TeLayout::setSize(const TeVector3f32 &size) { const TeVector3f32 size3d(size.x(), size.y(), _userSize.z()); if (_userSize != size3d) { _userSize.x() = size.x(); _userSize.y() = size.y(); _sizeChanged = true; _positionChanged = true; _worldMatrixChanged = true; } } void TeLayout::setSizeType(CoordinatesType coordtype) { assert(coordtype == RELATIVE_TO_PARENT || coordtype == ABSOLUTE); if (_sizeType != coordtype) { _sizeType = coordtype; _sizeChanged = true; _worldMatrixChanged = true; } } void TeLayout::setZPosition(float zpos) { if (_userPosition.z() != zpos) { _userPosition.z() = zpos; _positionChanged = true; _worldMatrixChanged = true; } } TeVector3f32 TeLayout::size() { updateSize(); updateZSize(); return _size; } TeLayout::CoordinatesType TeLayout::sizeType() const { return _sizeType; } TeVector3f32 TeLayout::transformMousePosition(const TeVector2s32 &mousepos) { TeVector3f32 relativeMouse(mousepos); relativeMouse.x() -= g_system->getWidth() / 2; relativeMouse.y() -= g_system->getHeight() / 2; TeMatrix4x4 transform = worldTransformationMatrix(); transform.inverse(); TeVector3f32 transformpos = transform * relativeMouse; //debug("transformMousePosition: %d %d -> %f %f", mousepos._x, mousepos._y, transformpos.x(), transformpos.y()); return transformpos; } void TeLayout::updatePosition() { if (!_positionChanged) return; _positionChanged = false; _updatingPosition = true; TeVector3f32 oldpos = _position; Te3DObject2 *parentObj = parent(); static const TeVector3f32 midPoint(0.5f, 0.5f, 0.5f); if (_positionType == RELATIVE_TO_PARENT && parentObj) { const TeVector3f32 offsetUserPos = _userPosition - midPoint; const TeVector3f32 parentSize(parentObj->xSize(), parentObj->ySize(), 0.0); const TeVector3f32 offsetAnchor = midPoint - _anchor; const TeVector3f32 thisSize(xSize(), ySize(), 0.0); const TeVector3f32 newpos = (offsetUserPos * parentSize) + (offsetAnchor * thisSize); _position = newpos; } else if (_positionType == RELATIVE_TO_PARENT && !parentObj) { // Not in original, but no parent -> set midpoint. const TeVector3f32 offsetAnchor = midPoint - _anchor; const TeVector3f32 thisSize(xSize(), ySize(), 0.0); _position = offsetAnchor * thisSize; } else if (_positionType == ABSOLUTE) { _position = _userPosition; } _position.z() = _userPosition.z(); _worldMatrixChanged = true; _updatingPosition = false; if (_position != oldpos) { onPositionChanged().call(); } } void TeLayout::updateSize() { if (!_sizeChanged) return; _sizeChanged = false; _updatingSize = true; const TeVector3f32 oldSize = _size; if (_sizeType == ABSOLUTE) { TeVector3f32 newSize = _userSize; _size.x() = abs(newSize.x()); _size.y() = abs(newSize.y()); // don't set Z val. } else if (_sizeType == RELATIVE_TO_PARENT) { Te3DObject2 *parentObj = parent(); if (parentObj) { const TeVector3f32 parentSize(parentObj->xSize(), parentObj->ySize(), 0.0); TeVector3f32 newSize = _userSize * parentSize; if (newSize.x() > 0.0f && newSize.y() > 0.0f && _ratio > 0.0f && _safeAreaRatio > 0.0f) { float newSizeRatio = newSize.x() / newSize.y(); if (_ratioMode == RATIO_MODE_PAN_SCAN) { if (_safeAreaRatio <= newSizeRatio) { newSize.x() = _ratio * newSize.y(); } else { newSize.x() = (1.0f - (_safeAreaRatio - newSizeRatio) / _safeAreaRatio) * _ratio * newSize.y(); } } else if (_ratioMode == RATIO_MODE_LETTERBOX) { if (_ratio < newSizeRatio) newSize.x() = _ratio * newSize.y(); else newSize.y() = newSize.x() / _ratio; } } _size.x() = newSize.x(); _size.y() = newSize.y(); } else { _size.x() = 0.0f; _size.y() = 0.0f; } } _updatingSize = false; // TODO: check this, is it the right flag to set? _positionChanged = true; updateMesh(); if (_size != oldSize) { onSizeChanged().call(); } } void TeLayout::updateWorldMatrix() { if (!_worldMatrixChanged) return; _worldMatrixChanged = false; _updatingWorldMatrix = true; const TeMatrix4x4 oldMatrix = _worldMatrixCache; _worldMatrixCache = Te3DObject2::worldTransformationMatrix(); _updatingWorldMatrix = false; if (_worldMatrixCache != oldMatrix) { onWorldTransformationMatrixChanged().call(); } } void TeLayout::updateZ() { if (!_needZUpdate || !_autoz) return; _needZUpdate = false; _updatingZ = true; float ztotal = 0.1f; for (auto &child : childList()) { child->setZPosition(ztotal); ztotal += child->zSize(); } _updatingZ = false; } void TeLayout::updateZSize() { if (!_needZSizeUpdate) return; _needZSizeUpdate = false; _updatingZSize = true; const TeVector3f32 oldSize = _size; _size.z() = 0.1f; for (auto &child : childList()) { _size.z() += child->zSize(); } _positionChanged = true; _updatingZSize = false; if (_size != oldSize) { onSizeChanged().call(); } } TeVector3f32 TeLayout::userPosition() const { return _userPosition; } TeVector3f32 TeLayout::userSize() { updateZ(); return _userSize; } TeVector3f32 TeLayout::worldPosition() { if (!parent()) { return position(); } else { return parent()->worldPosition() + position(); } } TeMatrix4x4 TeLayout::worldTransformationMatrix() { updateZ(); updatePosition(); updateWorldMatrix(); return _worldMatrixCache; } bool TeLayout::worldVisible() { bool visible = Te3DObject2::visible(); if (visible && parent()) { return parent()->worldVisible(); } return visible; } float TeLayout::xSize() { updateSize(); return _size.x(); } float TeLayout::ySize() { updateSize(); return _size.y(); } float TeLayout::zSize() { updateZSize(); return _size.z(); } } // end namespace Tetraedge