#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); } // The two distances the rules run on, deliberately different (REQ-UI-SELECTION-PANEL): // the edge margin the panel keeps from the view and from the widgets it steps around, and // the wider gap it keeps from the selection -- half a tile, so 20 px at a 40 px tile. static const int kMarginPx = 8; static const int kSelectionGapPx = 20; 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, kSelectionGapPx) == PanelSide::Right); } TEST_CASE("The panel goes left when the right cannot hold it", "[layout]") { // A selection near the right edge leaves 80 px there once the gap it keeps from the // selection is taken off, not enough for a 300 px panel, and the left is wide open. REQUIRE(chooseSide(makeBand(), QRect(880, 100, 20, 60), 300, kSelectionGapPx) == PanelSide::Left); } TEST_CASE("The room a side offers is measured less the gap", "[layout]") { // A 200 px panel beside a selection whose right edge leaves 214 px to the band's: it // fits there on the margin alone, but not once the wider gap is taken off. const QRect anchorRect(766, 100, 20, 60); REQUIRE(chooseSide(makeBand(), anchorRect, 200, kMarginPx) == PanelSide::Right); REQUIRE(chooseSide(makeBand(), anchorRect, 200, kSelectionGapPx) == PanelSide::Left); } TEST_CASE("Fitting on neither side, the panel takes the roomier one", "[layout]") { // A bounding box spanning most of the view: 180 px free on the left, 80 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, kSelectionGapPx) == PanelSide::Left); REQUIRE(chooseSide(makeBand(), QRect(100, 100, 700, 200), 300, kSelectionGapPx) == 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 gap it keeps from the selection, growing // away from it, top edge on the anchor's top edge. const QRect placed = placeBesideAnchor(makeBand(), QRect(100, 120, 60, 60), PanelSide::Right, QSize(300, 200), {}, kSelectionGapPx, kMarginPx); REQUIRE(placed == QRect(180, 120, 300, 200)); const QRect placedLeft = placeBesideAnchor(makeBand(), QRect(520, 120, 60, 60), PanelSide::Left, QSize(300, 200), {}, kSelectionGapPx, kMarginPx); REQUIRE(placedLeft == QRect(200, 120, 300, 200)); } TEST_CASE("The gap separates the panel horizontally only", "[layout]") { // REQ-UI-SELECTION-PANEL: widening the gap moves the panel further from the selection // sideways and nowhere else -- its top stays level with the top of what it describes. const QRect anchorRect(100, 120, 60, 60); REQUIRE(placeBesideAnchor(makeBand(), anchorRect, PanelSide::Right, QSize(300, 200), {}, 0, kMarginPx) == QRect(160, 120, 300, 200)); REQUIRE(placeBesideAnchor(makeBand(), anchorRect, PanelSide::Right, QSize(300, 200), {}, kSelectionGapPx, kMarginPx) == QRect(180, 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), {}, kSelectionGapPx, kMarginPx); REQUIRE(placed == QRect(180, 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 edge margin, the gap from the // selection having settled its left edge (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, kSelectionGapPx, kMarginPx); REQUIRE(placed == QRect(180, 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), {}, kSelectionGapPx, kMarginPx); REQUIRE(placed == QRect(180, 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), {}, kSelectionGapPx, kMarginPx); REQUIRE(placed == QRect(700, 100, 300, 200)); } // --------------------------------------------------------------------------- // Where it stands once the player has dragged it (REQ-UI-SELECTION-PANEL-DRAG) // --------------------------------------------------------------------------- TEST_CASE("A dragged panel stands where it was dropped", "[layout]") { // With room for it there, the desired top-left corner is the answer: nothing about // the selection it describes is consulted any more. const QRect placed = placeAtDesiredTopLeft(makeBand(), QPoint(420, 180), QSize(300, 200), {}, kMarginPx); REQUIRE(placed == QRect(420, 180, 300, 200)); } TEST_CASE("A panel dragged past the view's edges is pushed back inside", "[layout]") { // Dropped off the right edge and below the bottom, it is brought back within the // band, as an anchored panel is. REQUIRE(placeAtDesiredTopLeft(makeBand(), QPoint(900, 500), QSize(300, 200), {}, kMarginPx) == QRect(700, 400, 300, 200)); // And off the left edge and above the top, the other way. REQUIRE(placeAtDesiredTopLeft(makeBand(), QPoint(-120, -60), QSize(300, 200), {}, kMarginPx) == QRect(0, 0, 300, 200)); } TEST_CASE("A dragged panel rises above the widget it was dropped over", "[layout]") { // The player drops it over the build button bar; it lands above the bar's top by the // margin instead, the bar staying where it is (REQ-UI-BUILD-BAR). const std::vector occupied = { QRect(400, 520, 200, 72) }; const QRect placed = placeAtDesiredTopLeft(makeBand(), QPoint(450, 480), QSize(300, 200), occupied, kMarginPx); REQUIRE(placed == QRect(450, 312, 300, 200)); } TEST_CASE("A widget beside a dragged panel's column does not move it", "[layout]") { // Only what the panel's own column meets is in its way: the controls panel in the // bottom-left corner leaves a panel dropped at the right edge alone // (REQ-UI-CONTROLS-PANEL). const std::vector occupied = { QRect(0, 300, 260, 300) }; const QRect placed = placeAtDesiredTopLeft(makeBand(), QPoint(600, 380), QSize(300, 200), occupied, kMarginPx); REQUIRE(placed == QRect(600, 380, 300, 200)); } TEST_CASE("A dragged panel too tall for the room left is capped", "[layout]") { // Capping is the caller's cue to scroll here too: a card asking for 700 px over a bar // that leaves 512 px of band gets what there is, from the top of the band. const std::vector occupied = { QRect(400, 520, 200, 72) }; const QRect placed = placeAtDesiredTopLeft(makeBand(), QPoint(450, 100), QSize(300, 700), occupied, kMarginPx); REQUIRE(placed == QRect(450, 0, 300, 512)); } TEST_CASE("A dragged panel returns to where it was dropped", "[layout]") { // The resolution never writes back to the desired position: a panel lifted above the // build button bar goes back to the point the player dropped it at as soon as the bar // stops meeting its column -- here because the bar's button set shrank. const QPoint desired(450, 380); const std::vector wideBar = { QRect(400, 520, 200, 72) }; REQUIRE(placeAtDesiredTopLeft(makeBand(), desired, QSize(300, 200), wideBar, kMarginPx) == QRect(450, 312, 300, 200)); const std::vector narrowBar = { QRect(400, 520, 40, 72) }; REQUIRE(placeAtDesiredTopLeft(makeBand(), desired, QSize(300, 200), narrowBar, kMarginPx) == QRect(450, 380, 300, 200)); }