/** * @file PaneTree.h * @brief Binary-space-partition layout of the plot area. * * Framework-free: no ImGui, no UDPS. The geometry and the hit-testing are the * fiddly part of the pane UI and are unit-tested without a window. */ #pragma once #include "Types.h" #include #include #include namespace udpscope { /** Direction a node splits its rectangle in. */ enum class Orient { Columns, Rows }; /** Vertical scaling strategy for one trace. */ enum class VMode { Auto, Range, Manual }; struct VScale { VMode mode = VMode::Auto; double div = 1.0; /**< Units per division, Manual only. */ double offset = 0.0; /**< Centre value, Manual only. */ }; /** One signal drawn in one pane. Signals are named, never indexed. */ struct Assignment { std::string signalName; Color color; float lineWidth = 1.5f; VScale vs; }; /** Smallest a pane may be squeezed to, in pixels. */ constexpr double kMinPaneSize = 80.0; /** Thickness of the splitter drag zone and of the inset handles, in pixels. */ constexpr double kSplitterGrab = 6.0; constexpr double kHandleSize = 18.0; /** What the pointer is over inside a pane. */ enum class Handle { None, Left, Right, Top, Bottom, Close }; struct PaneNode { bool leaf = true; /* leaf only */ std::vector signals; bool profilePane = false; /**< Holds vector signals, not time series. */ /* split only */ Orient orient = Orient::Columns; double ratio = 0.5; /**< First child's share of the parent. */ std::unique_ptr a, b; }; class PaneTree { public: struct Placed { PaneNode* leaf; Rect rect; }; struct Splitter { PaneNode* node; Rect rect; Orient orient; }; PaneTree(); PaneNode* root() { return root_.get(); } const PaneNode* root() const { return root_.get(); } /** Replaces the whole tree, e.g. when loading a session. */ void setRoot(std::unique_ptr node); /** * @brief Walk the tree, producing every leaf's rectangle and every split's * drag zone. */ void layout(const Rect& area, std::vector& leaves, std::vector& splitters) const; /** Turn a leaf into a split; the original content stays in the first child. */ void splitLeaf(PaneNode* leaf, Orient orient); /** Replace the leaf's parent with its sibling. No-op on the last leaf. */ void closeLeaf(PaneNode* leaf); void setRatio(PaneNode* split, double ratio); size_t leafCount() const; /** @return the splitter under the point, or nullptr. */ const Splitter* hitTestSplitter(const std::vector& splitters, double px, double py) const; /** * @brief Which inset handle of @p pane the point is over. * * Handles sit inside the pane so they never overlap the splitter drag zone, * and every pane has all four regardless of whether it touches a window * edge — a pane in the middle of a 3x3 touches none. */ static Handle hitTestHandle(const Rect& pane, double px, double py); private: static void layoutNode(PaneNode* node, const Rect& r, std::vector& leaves, std::vector& splitters); static size_t countLeaves(const PaneNode* node); static PaneNode* findParent(PaneNode* node, const PaneNode* child); static double clampRatio(double ratio, double extent); std::unique_ptr root_; }; } /* namespace udpscope */