diff --git a/doc/Changelog.md b/doc/Changelog.md index 80686918..c570f879 100644 --- a/doc/Changelog.md +++ b/doc/Changelog.md @@ -3,6 +3,8 @@ Changelog {#changelog} # Release 1.5 (git master) +* [267](https://github.com/BlueBrain/Tide/pull/267): + Improve resizing of external streams by better considering its size hints. * [266](https://github.com/BlueBrain/Tide/pull/266): Multiple bugfixes: - Fix crashes in case of file read errors on some of the wall processes. diff --git a/tests/cpp/core/WindowControllerTests.cpp b/tests/cpp/core/WindowControllerTests.cpp index 8524b4bf..24523ceb 100644 --- a/tests/cpp/core/WindowControllerTests.cpp +++ b/tests/cpp/core/WindowControllerTests.cpp @@ -138,30 +138,45 @@ WindowPtr makeDummyWindow() return window; } -BOOST_AUTO_TEST_CASE(testOneToOneSize) +struct TestFixture { - auto window = makeDummyWindow(); - auto displayGroup = DisplayGroup::create(wallSize); - WindowController controller(*window, *displayGroup); - const QRectF& coords = window->getCoordinates(); + WindowPtr window = makeDummyWindow(); + DisplayGroupPtr displayGroup = DisplayGroup::create(wallSize); + WindowController controller{*window, *displayGroup}; +}; +BOOST_FIXTURE_TEST_CASE(testOneToOneSize, TestFixture) +{ controller.adjustSize(SIZE_1TO1); // 1:1 size restored around existing window center + const auto& coords = window->getCoordinates(); BOOST_CHECK_EQUAL(coords.size(), CONTENT_SIZE); BOOST_CHECK_EQUAL(coords.center(), QPointF(625, 240)); } -BOOST_AUTO_TEST_CASE(testOneToOneFittingSize) +BOOST_FIXTURE_TEST_CASE(testOneToOneSizeUsesPreferedSize, TestFixture) { - auto window = makeDummyWindow(); - auto displayGroup = DisplayGroup::create(wallSize); - WindowController controller(*window, *displayGroup); - const QRectF& coords = window->getCoordinates(); + deflect::SizeHints hints; + hints.preferredWidth = CONTENT_SIZE.width() * 0.8; + hints.preferredHeight = CONTENT_SIZE.height() * 1.1; + window->getContent().setSizeHints(hints); + + controller.adjustSize(SIZE_1TO1); + + // 1:1 size restored around existing window center + const auto& coords = window->getCoordinates(); + BOOST_CHECK_EQUAL(coords.size(), + QSizeF(hints.preferredWidth, hints.preferredHeight)); + BOOST_CHECK_EQUAL(coords.center(), QPointF(625, 240)); +} +BOOST_FIXTURE_TEST_CASE(testOneToOneFittingSize, TestFixture) +{ controller.adjustSize(SIZE_1TO1_FITTING); // 1:1 size restored around existing window center + const auto& coords = window->getCoordinates(); BOOST_CHECK_EQUAL(coords.size(), CONTENT_SIZE); BOOST_CHECK_EQUAL(coords.center(), QPointF(625, 240)); @@ -172,12 +187,8 @@ BOOST_AUTO_TEST_CASE(testOneToOneFittingSize) BOOST_CHECK_EQUAL(coords.center(), QPointF(625, 240)); } -BOOST_AUTO_TEST_CASE(testSizeLimitsBigContent) +BOOST_FIXTURE_TEST_CASE(testSizeLimitsBigContent, TestFixture) { - auto window = makeDummyWindow(); - auto displayGroup = DisplayGroup::create(wallSize); - WindowController controller(*window, *displayGroup); - // Make a large content and validate it auto& content = window->getContent(); content.setDimensions(BIG_CONTENT_SIZE); @@ -198,12 +209,8 @@ BOOST_AUTO_TEST_CASE(testSizeLimitsBigContent) BOOST_CHECK_EQUAL(controller.getMaxSize(), 0.25 * normalMaxSize); } -BOOST_AUTO_TEST_CASE(testSizeLimitsSmallContent) +BOOST_FIXTURE_TEST_CASE(testSizeLimitsSmallContent, TestFixture) { - auto window = makeDummyWindow(); - auto displayGroup = DisplayGroup::create(wallSize); - WindowController controller(*window, *displayGroup); - // Make a small content and validate it auto& content = window->getContent(); content.setDimensions(SMALL_CONTENT_SIZE); @@ -218,18 +225,57 @@ BOOST_AUTO_TEST_CASE(testSizeLimitsSmallContent) BOOST_CHECK_EQUAL(controller.getMaxSize(), SMALL_CONTENT_SIZE * Content::getMaxScale()); - const QSizeF normalMaxSize = controller.getMaxSize(); + const auto normalMaxSize = controller.getMaxSize(); window->getContent().setZoomRect( QRectF(QPointF(0.3, 0.1), QSizeF(0.25, 0.25))); BOOST_CHECK_EQUAL(controller.getMaxSize(), 0.25 * normalMaxSize); } -BOOST_AUTO_TEST_CASE(testAspectRatioMinSize) +BOOST_FIXTURE_TEST_CASE(smallContentMadeFullscreenRespectsMaxContentSize, + TestFixture) { - auto window = makeDummyWindow(); - auto displayGroup = DisplayGroup::create(wallSize); - WindowController controller(*window, *displayGroup); + auto& content = window->getContent(); + content.setDimensions(SMALL_CONTENT_SIZE); + + BOOST_REQUIRE(content.getMaxDimensions() < wallSize); + + controller.adjustSize(SizeState::SIZE_FULLSCREEN); + + BOOST_CHECK_EQUAL(window->getCoordinates().size(), + content.getMaxDimensions()); +} + +BOOST_FIXTURE_TEST_CASE(smallContentWithBigMaxSizeHintsCanBeMadeFullscreen, + TestFixture) +{ + auto& content = window->getContent(); + content.setDimensions(SMALL_CONTENT_SIZE); + + deflect::SizeHints hints; + hints.maxWidth = std::numeric_limits::max(); + hints.maxHeight = std::numeric_limits::max(); + content.setSizeHints(hints); + + BOOST_REQUIRE_EQUAL(content.getMaxDimensions(), + QSize(std::numeric_limits::max(), + std::numeric_limits::max())); + + // Keep content aspect ratio + controller.adjustSize(SizeState::SIZE_FULLSCREEN); + BOOST_CHECK_EQUAL(window->getCoordinates(), + QRectF(QPointF{0, 125}, QSizeF{1000, 750})); + + // Use preferred size aspect ratio + hints.preferredWidth = wallSize.width() / 2; + hints.preferredHeight = wallSize.height() / 2; + content.setSizeHints(hints); + controller.adjustSize(SizeState::SIZE_FULLSCREEN); + BOOST_CHECK_EQUAL(window->getCoordinates(), + QRectF(QPointF{0, 0}, wallSize)); +} +BOOST_FIXTURE_TEST_CASE(testAspectRatioMinSize, TestFixture) +{ // Make a content and validate MinSize keeps aspect ratio auto& content = window->getContent(); content.setDimensions(QSize(400, 800)); @@ -265,8 +311,8 @@ BOOST_AUTO_TEST_CASE(testAspectRatioMinSize) QSize(600, 300)); window->setMode(Window::STANDARD); - const QSize maxSize(CONTENT_SIZE * 2); - const QSize minSize(CONTENT_SIZE / 2); + const auto maxSize = QSize{CONTENT_SIZE * 2}; + const auto minSize = QSize{CONTENT_SIZE / 2}; deflect::SizeHints hints; hints.maxWidth = maxSize.width(); hints.maxHeight = maxSize.height(); @@ -289,15 +335,10 @@ BOOST_AUTO_TEST_CASE(testAspectRatioMinSize) QSize(400, 300)); } -BOOST_AUTO_TEST_CASE(testSizeHints) +BOOST_FIXTURE_TEST_CASE(testSizeHints, TestFixture) { - auto window = makeDummyWindow(); - auto displayGroup = DisplayGroup::create(wallSize); - WindowController controller(*window, *displayGroup); - auto& content = window->getContent(); - - const QSize maxSize(CONTENT_SIZE * 2); - const QSize minSize(CONTENT_SIZE / 2); + const auto maxSize = QSize{CONTENT_SIZE * 2}; + const auto minSize = QSize{CONTENT_SIZE / 2}; deflect::SizeHints hints; hints.maxWidth = maxSize.width(); hints.maxHeight = maxSize.height(); @@ -305,9 +346,10 @@ BOOST_AUTO_TEST_CASE(testSizeHints) hints.minHeight = minSize.height(); hints.preferredWidth = CONTENT_SIZE.width(); hints.preferredHeight = CONTENT_SIZE.height(); + auto& content = window->getContent(); content.setSizeHints(hints); content.setDimensions(CONTENT_SIZE); - const QRectF& coords = window->getCoordinates(); + const auto& coords = window->getCoordinates(); // too big, constrains to maxSize controller.resize(maxSize * 2, CENTER); @@ -326,21 +368,6 @@ BOOST_AUTO_TEST_CASE(testSizeHints) BOOST_CHECK_EQUAL(coords.size(), minSize); } -BOOST_AUTO_TEST_CASE(testLargeSize) -{ - auto window = makeDummyWindow(); - auto displayGroup = DisplayGroup::create(wallSize); - WindowController controller(*window, *displayGroup); - const QRectF& coords = window->getCoordinates(); - - controller.adjustSize(SIZE_LARGE); - - // 75% of the screen, resized around window center - BOOST_CHECK_EQUAL(coords.center(), QPointF(625, 240)); - BOOST_CHECK_EQUAL(coords.width(), 0.75 * wallSize.width()); - BOOST_CHECK_EQUAL(coords.height(), 0.75 * wallSize.width() / CONTENT_AR); -} - void _checkFullscreen(const QRectF& coords) { // full screen, center on wall diff --git a/tide/core/resources/WindowControls.qml b/tide/core/resources/WindowControls.qml index 3ff16fd1..307f7434 100644 --- a/tide/core/resources/WindowControls.qml +++ b/tide/core/resources/WindowControls.qml @@ -16,7 +16,7 @@ Item { Rectangle { id: buttonsRectangle width: buttons.width + 2 * Style.buttonsPadding - height: buttons.height + height: triangle.visible ? buttons.height : buttons.height + 2 * Style.buttonsPadding color: Style.controlsDefaultColor } Triangle { @@ -25,6 +25,8 @@ Item { height: 2 * width color: buttonsRectangle.color anchors.top: buttonsRectangle.bottom + visible: (window.focused ? window.focusedCoordinates.height : window.height) + > (buttons.height + triangle.height) } WindowControlButtons { @@ -42,9 +44,8 @@ Item { }, State { name: "opaque" - when: window.selected && - window.state !== Window.RESIZING && - !window.fullscreen + when: window.selected && window.state !== Window.RESIZING + && !window.fullscreen PropertyChanges { target: windowControls opacity: 1 diff --git a/tide/core/scene/Content.cpp b/tide/core/scene/Content.cpp index 2073f8ac..f21e5f5d 100644 --- a/tide/core/scene/Content.cpp +++ b/tide/core/scene/Content.cpp @@ -45,6 +45,14 @@ IMPLEMENT_SERIALIZE_FOR_XML(Content) +namespace +{ +int _clampToInt(const unsigned int n) +{ + return std::min((unsigned int)std::numeric_limits::max(), n); +} +} + qreal Content::_maxScale = 3.0; Content::Content(const QString& uri) @@ -127,20 +135,22 @@ QSize Content::getMaxDimensions() const return getDimensions().isValid() ? getDimensions() * getMaxScale() : UNDEFINED_SIZE; } - return QSize(_sizeHints.maxWidth, _sizeHints.maxHeight); + return QSize{_clampToInt(_sizeHints.maxWidth), + _clampToInt(_sizeHints.maxHeight)}; } void Content::setSizeHints(const deflect::SizeHints& sizeHints) { if (_sizeHints == sizeHints) return; + _sizeHints = sizeHints; emit modified(); } void Content::setMaxScale(const qreal value) { - if (value > 0) + if (value > 0.0) _maxScale = value; } diff --git a/tide/core/ui.cpp b/tide/core/ui.cpp index fc423168..fb6ad28e 100644 --- a/tide/core/ui.cpp +++ b/tide/core/ui.cpp @@ -49,10 +49,15 @@ const qreal buttonsPadding = buttonsSize / 4.0; const qreal buttonsSizeLarge = 1.15 * buttonsSize; const qreal buttonsPaddingLarge = 1.15 * buttonsPadding; +/** Derived UI element sizes. */ const qreal movieBarHeight = buttonsSize; const qreal controlsWidth = buttonsSize + 2 * buttonsPadding; const qreal controlsDistanceFromWindow = buttonsPadding; +/** Constraints. */ +const qreal minWindowSize = 300; +const qreal minWindowSpacingInFocusMode = 40; + inline bool hasMovieControls(const Content& content) { return content.getType() == ContentType::movie; @@ -71,7 +76,7 @@ qreal getWindowControlsMargin() qreal getMinWindowSpacing() { - return 40.0; + return minWindowSpacingInFocusMode; } qreal getFocusedWindowControlBarHeight(const Content& content) @@ -102,4 +107,9 @@ QRectF getFocusSurface(const DisplayGroup& group) group.width() - leftMargin - rightMargin, group.height() - topMargin - bottomMargin}; } + +qreal getMinWindowSize() +{ + return minWindowSize; +} } diff --git a/tide/core/ui.h b/tide/core/ui.h index 02405c24..ed4536e0 100644 --- a/tide/core/ui.h +++ b/tide/core/ui.h @@ -48,6 +48,8 @@ */ namespace ui { +/** @name Fixed UI element sizes. */ +//@{ /** @return the size of regular buttons. */ qreal getButtonSize(); @@ -65,9 +67,16 @@ QMarginsF getFocusedWindowControlsMargins(const Content& content); /** @return the width of the fixed side control bar on the control surface. */ qreal getSideControlWidth(); +//@} +/** @name Constraints for window layout. */ +//@{ /** @return the surface of the DisplayGroup available for focused windows. */ QRectF getFocusSurface(const DisplayGroup& group); + +/** @return the minimum size that a normal window should have. */ +qreal getMinWindowSize(); +//@} } #endif diff --git a/tide/master/control/WindowController.cpp b/tide/master/control/WindowController.cpp index b2b1c747..d3b8d756 100644 --- a/tide/master/control/WindowController.cpp +++ b/tide/master/control/WindowController.cpp @@ -43,15 +43,13 @@ #include "ZoomController.h" #include "scene/DisplayGroup.h" #include "scene/Window.h" +#include "ui.h" #include "utils/geometry.h" #include namespace { -const qreal MIN_SIZE = 0.05; -const qreal MIN_VISIBLE_AREA_PX = 300.0; -const qreal LARGE_SIZE_SCALE = 0.75; const qreal FITTING_SIZE_SCALE = 0.9; const qreal ONE_PERCENT = 0.01; } @@ -89,9 +87,9 @@ void WindowController::stopResizing() _window.setResizePolicy(Window::ResizePolicy::ADJUST_CONTENT); } -void WindowController::resize(const QSizeF size, const WindowPoint fixedPoint) +void WindowController::resize(const QSizeF& size, const WindowPoint fixedPoint) { - QSizeF newSize(_window.getContent().getDimensions()); + auto newSize = QSizeF{_window.getContent().getPreferredDimensions()}; if (newSize.isEmpty()) newSize = size; else @@ -110,10 +108,10 @@ void WindowController::resize(const QSizeF size, const WindowPoint fixedPoint) void WindowController::resizeRelative(const QPointF& delta) { - const QRectF& coord = _getCoordinates(); + const auto& coord = _getCoordinates(); - QPointF fixedPoint; - QSizeF newSize = coord.size(); + auto fixedPoint = QPointF(); + auto newSize = coord.size(); switch (_window.getActiveHandle()) { @@ -196,25 +194,19 @@ void WindowController::adjustSize(const SizeState state) case SIZE_1TO1_FITTING: { - const QSizeF max = _displayGroup.size() * FITTING_SIZE_SCALE; - const QSize oneToOne = _window.getContent().getPreferredDimensions(); - if (oneToOne.width() > max.width() || oneToOne.height() > max.height()) - resize(max, CENTER); - else - resize(oneToOne, CENTER); + const auto oneToOneSize = _window.getContent().getPreferredDimensions(); + const auto maxSize = _displayGroup.size() * FITTING_SIZE_SCALE; + resize(std::min(oneToOneSize, maxSize), CENTER); } break; - case SIZE_LARGE: - resize(LARGE_SIZE_SCALE * _displayGroup.size(), CENTER); - break; - case SIZE_FULLSCREEN: { auto& content = _window.getContent(); content.resetZoom(); - auto size = geometry::getAdjustedSize(content, _displayGroup); + auto size = geometry::getAdjustedSize(content.getPreferredDimensions(), + _displayGroup); constrainSize(size); _apply(_getCenteredCoordinates(size)); } @@ -290,25 +282,22 @@ void WindowController::moveBy(const QPointF& delta) QSizeF WindowController::getMinSize() const { - const QSizeF wallSize = _displayGroup.size(); + const auto wallSize = _displayGroup.size(); if (_targetIsFullscreen()) { - const QSizeF contentSize = _window.getContent().getDimensions(); + const auto contentSize = QSizeF{_window.getContent().getDimensions()}; return contentSize.scaled(wallSize, Qt::KeepAspectRatio); } - const QSizeF minContentSize = _window.getContent().getMinDimensions(); - const QSizeF minSize(std::max(MIN_SIZE * wallSize.width(), - MIN_VISIBLE_AREA_PX), - std::max(MIN_SIZE * wallSize.height(), - MIN_VISIBLE_AREA_PX)); + const auto minContentSize = QSizeF{_window.getContent().getMinDimensions()}; + const auto minSize = QSizeF{ui::getMinWindowSize(), ui::getMinWindowSize()}; return std::max(minContentSize, minSize); } QSizeF WindowController::getMaxSize() const { - const QRectF& zoomRect = _window.getContent().getZoomRect(); - QSizeF maxSize = _window.getContent().getMaxDimensions(); + const auto& zoomRect = _window.getContent().getZoomRect(); + auto maxSize = QSizeF{_window.getContent().getMaxDimensions()}; maxSize.rwidth() *= zoomRect.size().width(); maxSize.rheight() *= zoomRect.size().height(); return maxSize; @@ -321,7 +310,7 @@ void WindowController::constrainSize(QSizeF& windowSize) const QSizeF WindowController::getMinSizeAspectRatioCorrect() const { - const qreal contentAspectRatio = _window.getContent().getAspectRatio(); + const auto contentAspectRatio = _window.getContent().getAspectRatio(); const auto min = getMinSize(); const auto max = getMaxSize(); const auto aspectRatioCorrectSize = QSizeF(contentAspectRatio, 1.0); @@ -348,7 +337,7 @@ void WindowController::_resize(const QPointF& center, QSizeF size) void WindowController::_constrainAspectRatio(QSizeF& windowSize) const { - const QSizeF currentSize = _getCoordinates().size(); + const auto currentSize = _getCoordinates().size(); const auto mode = windowSize < currentSize ? Qt::KeepAspectRatio : Qt::KeepAspectRatioByExpanding; windowSize = currentSize.scaled(windowSize, mode); @@ -357,15 +346,15 @@ void WindowController::_constrainAspectRatio(QSizeF& windowSize) const bool WindowController::_isCloseToContentAspectRatio( const QSizeF& windowSize) const { - const qreal windowAR = windowSize.width() / windowSize.height(); - const qreal contentAR = _window.getContent().getAspectRatio(); + const auto windowAR = windowSize.width() / windowSize.height(); + const auto contentAR = _window.getContent().getAspectRatio(); return std::fabs(windowAR - contentAR) < ONE_PERCENT; } void WindowController::_snapToContentAspectRatio(QSizeF& windowSize) const { - const QSizeF contentSize(_window.getContent().getDimensions()); + const auto contentSize = QSizeF{_window.getContent().getDimensions()}; const auto mode = windowSize < contentSize ? Qt::KeepAspectRatio : Qt::KeepAspectRatioByExpanding; windowSize = contentSize.scaled(windowSize, mode); @@ -373,29 +362,29 @@ void WindowController::_snapToContentAspectRatio(QSizeF& windowSize) const void WindowController::_constrainPosition(QRectF& window) const { - const QRectF& group = _displayGroup.getCoordinates(); + const auto& group = _displayGroup.getCoordinates(); if (_targetIsFullscreen()) { - const qreal overlapX = group.width() - window.width(); - const qreal overlapY = group.height() - window.height(); + const auto overlapX = group.width() - window.width(); + const auto overlapY = group.height() - window.height(); - const qreal minX = overlapX < 0.0 ? overlapX : 0.5 * overlapX; - const qreal minY = overlapY < 0.0 ? overlapY : 0.5 * overlapY; + const auto minX = overlapX < 0.0 ? overlapX : 0.5 * overlapX; + const auto minY = overlapY < 0.0 ? overlapY : 0.5 * overlapY; - const qreal maxX = 0.0; - const qreal maxY = 0.0; + const auto maxX = 0.0; + const auto maxY = 0.0; window.moveTopLeft({std::max(minX, std::min(window.x(), maxX)), std::max(minY, std::min(window.y(), maxY))}); return; } - const qreal minX = MIN_VISIBLE_AREA_PX - window.width(); - const qreal minY = MIN_VISIBLE_AREA_PX - window.height(); + const auto minX = ui::getMinWindowSize() - window.width(); + const auto minY = ui::getMinWindowSize() - window.height(); - const qreal maxX = group.width() - MIN_VISIBLE_AREA_PX; - const qreal maxY = group.height() - MIN_VISIBLE_AREA_PX; + const auto maxX = group.width() - ui::getMinWindowSize(); + const auto maxY = group.height() - ui::getMinWindowSize(); window.moveTopLeft({std::max(minX, std::min(window.x(), maxX)), std::max(minY, std::min(window.y(), maxY))}); @@ -403,10 +392,10 @@ void WindowController::_constrainPosition(QRectF& window) const QRectF WindowController::_getCenteredCoordinates(const QSizeF& size) const { - const QRectF& group = _displayGroup.getCoordinates(); + const auto& group = _displayGroup.getCoordinates(); // centered coordinates on the display group - QRectF coord(QPointF(), size); + auto coord = QRectF{QPointF(), size}; coord.moveCenter(group.center()); return coord; } diff --git a/tide/master/control/WindowController.h b/tide/master/control/WindowController.h index 8e5af565..fb55304f 100644 --- a/tide/master/control/WindowController.h +++ b/tide/master/control/WindowController.h @@ -51,7 +51,6 @@ enum SizeState { SIZE_1TO1, SIZE_1TO1_FITTING, - SIZE_LARGE, SIZE_FULLSCREEN, SIZE_FULLSCREEN_MAX, SIZE_FULLSCREEN_1TO1 @@ -101,7 +100,8 @@ class WindowController : public QObject //@} /** Resize the window. */ - Q_INVOKABLE void resize(QSizeF size, WindowPoint fixedPoint = TOP_LEFT); + Q_INVOKABLE void resize(const QSizeF& size, + WindowPoint fixedPoint = TOP_LEFT); /** Resize the window relative to the current active window border */ Q_INVOKABLE void resizeRelative(const QPointF& delta);