#include "catch.hpp" #include #include #include "FloatingPanelPlacement.h" // The band every case below places into: 1000x600, so a bottom edge of 599. static QRect makeBand() { return QRect(0, 0, 1000, 600); } static const int kMarginPx = 8; TEST_CASE("With nothing in the way a widget may use the whole band", "[layout]") { // REQ-UI-SELECTION-PANEL: the band's own bottom is the limit when no other floating // widget has been placed yet. REQUIRE(getAvailableBottomPx(makeBand(), {}, 0, 999, kMarginPx) == 599); } TEST_CASE("A widget in the same column pushes the bottom above it", "[layout]") { // The build button bar sitting at the bottom center leaves the space above it, less // the margin kept between the two (REQ-UI-BUILD-BAR). const std::vector occupied = { QRect(400, 520, 200, 72) }; REQUIRE(getAvailableBottomPx(makeBand(), occupied, 350, 650, kMarginPx) == 511); } TEST_CASE("A widget beside the column does not shorten it", "[layout]") { // The controls panel in the bottom-left corner is not in the way of a panel standing // at the right edge, however tall it is (REQ-UI-CONTROLS-PANEL). const std::vector occupied = { QRect(0, 100, 200, 499) }; REQUIRE(getAvailableBottomPx(makeBand(), occupied, 700, 999, kMarginPx) == 599); // Touching columns do count as meeting: the panel starts exactly where the widget // ends, which is an overlap of one pixel. REQUIRE(getAvailableBottomPx(makeBand(), occupied, 199, 999, kMarginPx) == 91); } TEST_CASE("The topmost widget in the column decides", "[layout]") { // Several widgets meet the column: the one that reaches highest is the binding one, // whatever order they are given in. const std::vector occupied = { QRect(400, 520, 200, 72), QRect(0, 300, 500, 299), QRect(900, 560, 100, 40) }; REQUIRE(getAvailableBottomPx(makeBand(), occupied, 450, 550, kMarginPx) == 291); } TEST_CASE("An empty rectangle occupies nothing", "[layout]") { // A floating widget that is hidden contributes a null rect rather than being left // out of the pass. const std::vector occupied = { QRect(), QRect(400, 520, 200, 0) }; REQUIRE(getAvailableBottomPx(makeBand(), occupied, 0, 999, kMarginPx) == 599); } TEST_CASE("A widget filling the column leaves nothing", "[layout]") { // The caller is expected to notice that the space left is not positive rather than // being handed a floor of its own. const std::vector occupied = { QRect(0, 0, 1000, 600) }; REQUIRE(getAvailableBottomPx(makeBand(), occupied, 0, 999, kMarginPx) == -9); } // --------------------------------------------------------------------------- // Which side of the selection the panel takes // --------------------------------------------------------------------------- TEST_CASE("The panel stands to the right of the selection where it fits", "[layout]") { // REQ-UI-SELECTION-PANEL: right of the anchor is the first choice. REQUIRE(chooseSide(makeBand(), QRect(100, 100, 60, 60), 300, kMarginPx) == PanelSide::Right); } TEST_CASE("The panel goes left when the right cannot hold it", "[layout]") { // A selection near the right edge leaves 100 px there, not enough for a 300 px // panel, and the left is wide open. REQUIRE(chooseSide(makeBand(), QRect(880, 100, 20, 60), 300, kMarginPx) == PanelSide::Left); } TEST_CASE("Fitting on neither side, the panel takes the roomier one", "[layout]") { // A bounding box spanning most of the view: 192 px free on the left, 92 on the // right, and a 300 px panel fits in neither. It covers as little as it can. REQUIRE(chooseSide(makeBand(), QRect(200, 100, 700, 200), 300, kMarginPx) == PanelSide::Left); REQUIRE(chooseSide(makeBand(), QRect(100, 100, 700, 200), 300, kMarginPx) == PanelSide::Right); } // --------------------------------------------------------------------------- // Where it then stands // --------------------------------------------------------------------------- TEST_CASE("The panel sits beside the anchor with its top edges aligned", "[layout]") { // REQ-UI-SELECTION-PANEL: separated by the margin, growing away from the selection, // top edge on the anchor's top edge. const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 120, 60, 60), PanelSide::Right, QSize(300, 200), {}, kMarginPx); REQUIRE(placed == QRect(168, 120, 300, 200)); const QRect placedLeft = placeBesideAnchor(makeBand(), QRect(500, 120, 60, 60), PanelSide::Left, QSize(300, 200), {}, kMarginPx); REQUIRE(placedLeft == QRect(192, 120, 300, 200)); } TEST_CASE("A panel that would hang below the view is lifted", "[layout]") { // Top-aligning with a selection low in the view would put the panel's bottom past // the band, so it rises until it fits rather than overrunning it. const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 500, 60, 60), PanelSide::Right, QSize(300, 200), {}, kMarginPx); REQUIRE(placed == QRect(168, 400, 300, 200)); } TEST_CASE("A panel standing over another widget rises above it", "[layout]") { // The controls panel in the bottom-left is in the way of a panel placed to the left // of a selection: it clears the top of it by the margin (REQ-UI-CONTROLS-PANEL). const std::vector occupied = { QRect(0, 300, 260, 300) }; const QRect placed = placeBesideAnchor(makeBand(), QRect(500, 250, 60, 60), PanelSide::Left, QSize(300, 200), occupied, kMarginPx); REQUIRE(placed == QRect(192, 92, 300, 200)); } TEST_CASE("A panel taller than the space left is capped", "[layout]") { // Capping is the caller's cue to scroll: it asked for 700 and got what there was. const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 100, 60, 60), PanelSide::Right, QSize(300, 700), {}, kMarginPx); REQUIRE(placed == QRect(168, 0, 300, 600)); } TEST_CASE("A panel that fits on neither side is pushed inside the view", "[layout]") { // The one case where it covers part of the selection (REQ-UI-SELECTION-PANEL): it // stands as far from the anchor as the band allows, not off the edge of it. const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 100, 700, 200), PanelSide::Right, QSize(300, 200), {}, kMarginPx); REQUIRE(placed == QRect(700, 100, 300, 200)); }