Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
117 lines
3.5 KiB
C++
117 lines
3.5 KiB
C++
/**
|
|
* @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 <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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<Assignment> 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<PaneNode> 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<PaneNode> node);
|
|
|
|
/**
|
|
* @brief Walk the tree, producing every leaf's rectangle and every split's
|
|
* drag zone.
|
|
*/
|
|
void layout(const Rect& area,
|
|
std::vector<Placed>& leaves,
|
|
std::vector<Splitter>& 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<Splitter>& 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<Placed>& leaves,
|
|
std::vector<Splitter>& 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<PaneNode> root_;
|
|
};
|
|
|
|
} /* namespace udpscope */
|