#include "PaneTree.h" #include using namespace udpscope; namespace { const Rect kScreen{0.0, 0.0, 1000.0, 600.0}; std::vector leavesOf(const PaneTree& tree, const Rect& area) { std::vector leaves; std::vector splitters; tree.layout(area, leaves, splitters); return leaves; } } /* namespace */ TEST(PaneTree, StartsAsOneEmptyLeafFillingTheArea) { PaneTree tree; EXPECT_EQ(tree.leafCount(), 1u); const auto leaves = leavesOf(tree, kScreen); ASSERT_EQ(leaves.size(), 1u); EXPECT_DOUBLE_EQ(leaves[0].rect.w, 1000.0); EXPECT_DOUBLE_EQ(leaves[0].rect.h, 600.0); EXPECT_TRUE(leaves[0].leaf->signals.empty()); } TEST(PaneTree, SplittingIntoColumnsHalvesTheWidth) { PaneTree tree; tree.splitLeaf(tree.root(), Orient::Columns); const auto leaves = leavesOf(tree, kScreen); ASSERT_EQ(leaves.size(), 2u); EXPECT_DOUBLE_EQ(leaves[0].rect.w, 500.0); EXPECT_DOUBLE_EQ(leaves[1].rect.w, 500.0); EXPECT_DOUBLE_EQ(leaves[0].rect.h, 600.0); EXPECT_DOUBLE_EQ(leaves[1].rect.x, 500.0); } TEST(PaneTree, SplittingIntoRowsHalvesTheHeight) { PaneTree tree; tree.splitLeaf(tree.root(), Orient::Rows); const auto leaves = leavesOf(tree, kScreen); ASSERT_EQ(leaves.size(), 2u); EXPECT_DOUBLE_EQ(leaves[0].rect.h, 300.0); EXPECT_DOUBLE_EQ(leaves[1].rect.y, 300.0); EXPECT_DOUBLE_EQ(leaves[0].rect.w, 1000.0); } // The pane being split keeps its content; the new pane is the empty one. TEST(PaneTree, SplitKeepsTheOriginalContentInTheFirstChild) { PaneTree tree; tree.root()->signals.push_back(Assignment{"Voltage", Color{}, 1.5f, VScale{}}); tree.splitLeaf(tree.root(), Orient::Columns); const auto leaves = leavesOf(tree, kScreen); ASSERT_EQ(leaves.size(), 2u); ASSERT_EQ(leaves[0].leaf->signals.size(), 1u); EXPECT_EQ(leaves[0].leaf->signals[0].signalName, "Voltage"); EXPECT_TRUE(leaves[1].leaf->signals.empty()); } TEST(PaneTree, ClosingALeafGivesItsSpaceToTheSibling) { PaneTree tree; tree.splitLeaf(tree.root(), Orient::Columns); auto leaves = leavesOf(tree, kScreen); ASSERT_EQ(leaves.size(), 2u); leaves[1].leaf->signals.push_back(Assignment{"Keep", Color{}, 1.5f, VScale{}}); tree.closeLeaf(leaves[0].leaf); EXPECT_EQ(tree.leafCount(), 1u); leaves = leavesOf(tree, kScreen); ASSERT_EQ(leaves.size(), 1u); EXPECT_DOUBLE_EQ(leaves[0].rect.w, 1000.0); ASSERT_EQ(leaves[0].leaf->signals.size(), 1u); EXPECT_EQ(leaves[0].leaf->signals[0].signalName, "Keep"); } TEST(PaneTree, RefusesToCloseTheLastLeaf) { PaneTree tree; tree.closeLeaf(tree.root()); EXPECT_EQ(tree.leafCount(), 1u); } // A pane in the middle of a 3x3 touches no window edge. It must still be // splittable, which is why handles are inset inside the pane rather than // keyed on the window border. TEST(PaneTree, AnInteriorPaneIsStillSplittable) { PaneTree tree; tree.splitLeaf(tree.root(), Orient::Rows); // top / bottom auto leaves = leavesOf(tree, kScreen); tree.splitLeaf(leaves[1].leaf, Orient::Rows); // 3 rows leaves = leavesOf(tree, kScreen); ASSERT_EQ(leaves.size(), 3u); PaneNode* middle = leaves[1].leaf; tree.splitLeaf(middle, Orient::Columns); leaves = leavesOf(tree, kScreen); tree.splitLeaf(leaves[2].leaf, Orient::Columns); EXPECT_EQ(tree.leafCount(), 5u); } TEST(PaneTree, LayoutReportsOneSplitterPerSplitNode) { PaneTree tree; tree.splitLeaf(tree.root(), Orient::Columns); auto leaves = leavesOf(tree, kScreen); tree.splitLeaf(leaves[0].leaf, Orient::Rows); std::vector out; std::vector splitters; tree.layout(kScreen, out, splitters); EXPECT_EQ(out.size(), 3u); EXPECT_EQ(splitters.size(), 2u); } TEST(PaneTree, RatioSurvivesALayoutRoundTrip) { PaneTree tree; tree.splitLeaf(tree.root(), Orient::Columns); tree.setRatio(tree.root(), 0.25); const auto leaves = leavesOf(tree, kScreen); ASSERT_EQ(leaves.size(), 2u); EXPECT_DOUBLE_EQ(leaves[0].rect.w, 250.0); EXPECT_DOUBLE_EQ(leaves[1].rect.w, 750.0); } TEST(PaneTree, RatioIsClampedSoNeitherPaneGoesBelowTheMinimum) { PaneTree tree; tree.splitLeaf(tree.root(), Orient::Columns); tree.setRatio(tree.root(), 0.001); const auto leaves = leavesOf(tree, kScreen); EXPECT_GE(leaves[0].rect.w, kMinPaneSize); EXPECT_GE(leaves[1].rect.w, kMinPaneSize); } TEST(PaneTree, HitTestFindsTheSplitterBetweenTwoPanes) { PaneTree tree; tree.splitLeaf(tree.root(), Orient::Columns); std::vector leaves; std::vector splitters; tree.layout(kScreen, leaves, splitters); ASSERT_EQ(splitters.size(), 1u); const PaneTree::Splitter* hit = tree.hitTestSplitter(splitters, 500.0, 300.0); ASSERT_NE(hit, nullptr); EXPECT_EQ(hit->orient, Orient::Columns); EXPECT_EQ(tree.hitTestSplitter(splitters, 100.0, 300.0), nullptr); } TEST(PaneTree, HitTestFindsInsetSplitHandlesAndTheCloseButton) { const Rect pane{0.0, 0.0, 400.0, 300.0}; EXPECT_EQ(PaneTree::hitTestHandle(pane, 8.0, 150.0), Handle::Left); EXPECT_EQ(PaneTree::hitTestHandle(pane, 392.0, 150.0), Handle::Right); EXPECT_EQ(PaneTree::hitTestHandle(pane, 200.0, 8.0), Handle::Top); EXPECT_EQ(PaneTree::hitTestHandle(pane, 200.0, 292.0), Handle::Bottom); EXPECT_EQ(PaneTree::hitTestHandle(pane, 392.0, 8.0), Handle::Close); EXPECT_EQ(PaneTree::hitTestHandle(pane, 200.0, 150.0), Handle::None); }