feat: a tidy button lays the graph out by depth, so no two boxes overlap
A single forward pass -- the list is already in evaluation order, so every input's depth is in hand by the time the reader is reached -- then a fit. The column pitch is `kNodeW + 48` rather than the add-node grid's 160, which left 28 units for a wire's 40-unit tread and made the bezier double back.
This commit is contained in:
1 parent
5a69b2147b
commit
8918494acb
1 file changed
+73
+73
@@ -3523,6 +3523,44 @@ void math_view_fit(MathDialog* d) {
|
|||||||
if (d->canvas != nullptr) gtk_widget_queue_draw(d->canvas);
|
if (d->canvas != nullptr) gtk_widget_queue_draw(d->canvas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lays the graph out left to right by depth: a node sits one column right of the deepest thing it
|
||||||
|
/// reads, and its row is its position among the nodes at that depth. It needs no recursion and no
|
||||||
|
/// visited set -- the list is already in evaluation order, so a single forward pass has every
|
||||||
|
/// input's depth in hand by the time it reaches the node that reads it.
|
||||||
|
void math_tidy(MathDialog* d) {
|
||||||
|
const std::size_t n = d->nodes.size();
|
||||||
|
if (n == 0) return;
|
||||||
|
// Wider than the add-node grid's 160: a column pitch of `kNodeW + 48` leaves the wire its 40-unit
|
||||||
|
// tread on each side with room to curve. At 160 there were 28 units for a 40-unit tread, so the
|
||||||
|
// bezier's control points crossed and the wire doubled back on itself.
|
||||||
|
constexpr double kColPitch = kNodeW + 48.0;
|
||||||
|
constexpr double kRowPitch = kNodeH + 22.0;
|
||||||
|
std::vector<int> depth(n, 0);
|
||||||
|
std::vector<int> row(n, 0);
|
||||||
|
std::vector<int> used;
|
||||||
|
for (std::size_t i = 0; i < n; ++i) {
|
||||||
|
const MathNode& node = d->nodes[i];
|
||||||
|
int deepest = -1;
|
||||||
|
const int ins[2] = {node.in_a, node.in_b};
|
||||||
|
for (int k = 0; k < math_op_arity(node.op); ++k) {
|
||||||
|
if (ins[k] >= 0 && ins[k] < static_cast<int>(i)) {
|
||||||
|
deepest = std::max(deepest, depth[static_cast<std::size_t>(ins[k])]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const int dep = deepest + 1;
|
||||||
|
depth[i] = dep;
|
||||||
|
if (static_cast<std::size_t>(dep) >= used.size()) used.resize(static_cast<std::size_t>(dep) + 1, 0);
|
||||||
|
row[i] = used[static_cast<std::size_t>(dep)]++;
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < n; ++i) {
|
||||||
|
d->nodes[i].x = 24.0 + static_cast<double>(depth[i]) * kColPitch;
|
||||||
|
d->nodes[i].y = 20.0 + static_cast<double>(row[i]) * kRowPitch;
|
||||||
|
}
|
||||||
|
// And frame it, since a tidy that leaves the graph off screen has moved the problem rather than
|
||||||
|
// solved it.
|
||||||
|
math_view_fit(d);
|
||||||
|
}
|
||||||
|
|
||||||
/// The port under a *canvas* position, with the tolerance in screen pixels. Every gesture goes
|
/// The port under a *canvas* position, with the tolerance in screen pixels. Every gesture goes
|
||||||
/// through this rather than converting for itself: `kPort` is a size on screen, so the graph-space
|
/// through this rather than converting for itself: `kPort` is a size on screen, so the graph-space
|
||||||
/// tolerance has to shrink as the view grows, and a handler that got that backwards makes a port
|
/// tolerance has to shrink as the view grows, and a handler that got that backwards makes a port
|
||||||
@@ -4492,11 +4530,17 @@ void open_math_dialog(App& app, int edit) {
|
|||||||
g_signal_connect(fit, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
g_signal_connect(fit, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||||
math_view_fit(static_cast<MathDialog*>(user));
|
math_view_fit(static_cast<MathDialog*>(user));
|
||||||
}), d);
|
}), d);
|
||||||
|
GtkWidget* tidy = gtk_button_new_with_label("tidy");
|
||||||
|
gtk_widget_set_tooltip_text(tidy, "Lay the graph out left to right and frame it.");
|
||||||
|
g_signal_connect(tidy, "clicked", G_CALLBACK(+[](GtkButton*, gpointer user) {
|
||||||
|
math_tidy(static_cast<MathDialog*>(user));
|
||||||
|
}), d);
|
||||||
GtkWidget* buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
GtkWidget* buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
|
||||||
gtk_box_append(GTK_BOX(buttons), add);
|
gtk_box_append(GTK_BOX(buttons), add);
|
||||||
gtk_box_append(GTK_BOX(buttons), del);
|
gtk_box_append(GTK_BOX(buttons), del);
|
||||||
gtk_box_append(GTK_BOX(buttons), out);
|
gtk_box_append(GTK_BOX(buttons), out);
|
||||||
gtk_box_append(GTK_BOX(buttons), fit);
|
gtk_box_append(GTK_BOX(buttons), fit);
|
||||||
|
gtk_box_append(GTK_BOX(buttons), tidy);
|
||||||
gtk_box_append(GTK_BOX(form), buttons);
|
gtk_box_append(GTK_BOX(form), buttons);
|
||||||
gtk_box_append(GTK_BOX(body), form);
|
gtk_box_append(GTK_BOX(body), form);
|
||||||
gtk_box_append(GTK_BOX(outer), body);
|
gtk_box_append(GTK_BOX(outer), body);
|
||||||
@@ -7638,6 +7682,35 @@ gboolean math_probe(gpointer user) {
|
|||||||
std::fprintf(stderr, "[math-probe] op pick: index round trip=%d (want 1)\n", round);
|
std::fprintf(stderr, "[math-probe] op pick: index round trip=%d (want 1)\n", round);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (a->math_dialog != nullptr) {
|
||||||
|
auto* ty = static_cast<MathDialog*>(
|
||||||
|
g_object_get_data(G_OBJECT(a->math_dialog), "udpscope-math-dialog"));
|
||||||
|
if (ty != nullptr && ty->nodes.size() >= 2) {
|
||||||
|
// Pile every node on one spot, so a layout that did nothing would be visible.
|
||||||
|
for (MathNode& n : ty->nodes) {
|
||||||
|
n.x = 10.0;
|
||||||
|
n.y = 10.0;
|
||||||
|
}
|
||||||
|
math_tidy(ty);
|
||||||
|
// No two boxes may overlap -- `math_node_at()` searches topmost-first, so an overlap means
|
||||||
|
// a click or a drag lands on the wrong node, which reads as the dialog ignoring the pointer.
|
||||||
|
int overlaps = 0;
|
||||||
|
std::string xs;
|
||||||
|
for (std::size_t i = 0; i < ty->nodes.size(); ++i) {
|
||||||
|
xs += (i != 0 ? "," : "") + std::to_string(static_cast<int>(ty->nodes[i].x));
|
||||||
|
for (std::size_t j = i + 1; j < ty->nodes.size(); ++j) {
|
||||||
|
const MathNode& p = ty->nodes[i];
|
||||||
|
const MathNode& q = ty->nodes[j];
|
||||||
|
if (p.x < q.x + kNodeW && q.x < p.x + kNodeW && p.y < q.y + kNodeH &&
|
||||||
|
q.y < p.y + kNodeH) {
|
||||||
|
++overlaps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::fprintf(stderr, "[math-probe] tidy: xs=[%s] overlaps=%d (want 0)\n", xs.c_str(),
|
||||||
|
overlaps);
|
||||||
|
}
|
||||||
|
}
|
||||||
std::fflush(stderr);
|
std::fflush(stderr);
|
||||||
return G_SOURCE_REMOVE;
|
return G_SOURCE_REMOVE;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user