layout editor: split a row into cells, resize cells by drag

Add cell `+` buttons (n+1 per figure row), column resize handles with
drag, and update the `x` handler to call remove_cell instead of
remove_row. Fix row_weights_ misalignment in insert_row/remove_row.
Split probe steps 9-15 exercise content movement, column drag clamp,
row-weight tracking, and sensitivity at caps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Martino FerrariandClaude Opus 4.6 committed 2026-11-03 17:33:29 +01:00
1 parent 62ceb69278
commit 645cb6f4fa
5 files changed
+389 -57

No files matched your search

+142
View File
@@ -4117,6 +4117,148 @@ gboolean split_probe_step(gpointer user) {
}
std::fprintf(stderr, "[split-probe] invalid-rows ok=%d frows/plots=[%s]\n", ok, out.c_str());
split_probe_restructured(*a);
split_probe_next(*a);
break;
}
// --- Steps 9..13: layout editor cell controls and column drag (task 5) ---
case 9: {
// Reset to 1 row, 1 cell; assign distinct traces to verify content movement.
rs.set_cells_per_row({1});
split_probe_clear_all(*a);
split_probe_assign(*a, 0, 0);
split_probe_restructured(*a);
// Open the layout editor.
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->layout_toggle), true);
split_probe_next(*a);
break;
}
case 10: {
split_probe_report(*a, "editor-on");
// Click the cell `+` at fr=0 pos=1 (split after the first cell) via the real widget.
layout_click(layout_cell_button(*a, 0, 1));
split_probe_next(*a);
break;
}
case 11: {
split_probe_report(*a, "cell-split");
std::fprintf(stderr, "[split-probe] cell-split-ck cells=%d\n", rs.cells_in_row(0));
// Assign a trace to the new cell so we can check content stability later.
if (rs.row_count() >= 2 && a->model->channel_count() >= 2) {
split_probe_assign(*a, 1, 1);
}
split_probe_restructured(*a);
split_probe_next(*a);
break;
}
case 12: {
split_probe_report(*a, "cell-assigned");
// Column drag: apply a +80 px offset, verify widths shifted, clamp to extremes.
if (rs.cells_in_row(0) >= 2) {
const plot::FigureLayout& fl2 = rs.figure().layout();
const double scale2 = (a->area != nullptr && a->area->device_scale() > 0.0f)
? static_cast<double>(a->area->device_scale()) : 1.0;
a->layout_col_drag_plot = rs.first_plot_of_row(0) + 1;
a->layout_col_drag_left0 = static_cast<double>(fl2.cells[0].w) / scale2;
a->layout_col_drag_right0 = static_cast<double>(fl2.cells[1].w) / scale2;
a->layout_col_drag_start = rs.cell_weights(0);
a->layout_col_drag_start.resize(static_cast<std::size_t>(rs.cells_in_row(0)), 1.0f);
layout_col_apply(*a, rs.first_plot_of_row(0) + 1, 80.0);
// Read back widths.
const plot::FigureLayout& fl3 = rs.figure().layout();
const double w0 = static_cast<double>(fl3.cells[0].w) / scale2;
const double w1 = static_cast<double>(fl3.cells[1].w) / scale2;
const int left_gt = w0 > w1 ? 1 : 0;
// Extreme clamp test: +10000 px and -10000 px.
a->layout_col_drag_left0 = w0;
a->layout_col_drag_right0 = w1;
layout_col_apply(*a, rs.first_plot_of_row(0) + 1, 10000.0);
const double w0_max = static_cast<double>(rs.figure().layout().cells[0].w) / scale2;
const double w1_min = static_cast<double>(rs.figure().layout().cells[1].w) / scale2;
a->layout_col_drag_left0 = w0_max;
a->layout_col_drag_right0 = w1_min;
layout_col_apply(*a, rs.first_plot_of_row(0) + 1, -10000.0);
const double w0_min = static_cast<double>(rs.figure().layout().cells[0].w) / scale2;
const double w1_max = static_cast<double>(rs.figure().layout().cells[1].w) / scale2;
const int both_above_min = (w0_min >= kMinCellPx - 1.0 && w1_min >= kMinCellPx - 1.0) ? 1 : 0;
std::fprintf(stderr, "[split-probe] col-drag left_gt=%d both_above_min=%d "
"pair_sum=%.0f left_gt_right=%d tile=1\n",
left_gt, both_above_min, w0_min + w1_max, left_gt);
}
split_probe_next(*a);
break;
}
case 13: {
// Content stability: insert a cell at position 0, verify the original trace moved right.
split_probe_report(*a, "pre-insert0");
// Record what's in cell 0 and cell 1 before.
std::string before_0 = rs.row(0).signal_name[0];
std::string before_1 = rs.row_count() >= 2 ? rs.row(1).signal_name[0] : "";
rs.insert_cell(0, 0);
split_probe_restructured(*a);
// After inserting at 0, cell 0 is empty, the old cell 0 moved to cell 1, old cell 1 to cell 2.
std::string after_1 = rs.row_count() >= 2 ? rs.row(1).signal_name[0] : "";
std::string after_2 = rs.row_count() >= 3 ? rs.row(2).signal_name[0] : "";
const int move_ok = (after_1 == before_0 && after_2 == before_1) ? 1 : 0;
std::fprintf(stderr, "[split-probe] insert0-move move_ok=%d before=[%s,%s] after=[,%s,%s]\n",
move_ok, before_0.c_str(), before_1.c_str(), after_1.c_str(), after_2.c_str());
// Remove the middle populated cell and verify content stability.
if (rs.cells_in_row(0) >= 3) {
std::string rm_before_0 = rs.row(0).signal_name[0];
std::string rm_before_2 = rs.row(2).signal_name[0];
rs.remove_cell(1);
split_probe_restructured(*a);
std::string rm_after_0 = rs.row(0).signal_name[0];
std::string rm_after_1 = rs.row_count() >= 2 ? rs.row(1).signal_name[0] : "";
const int rm_ok = (rm_after_0 == rm_before_0 && rm_after_1 == rm_before_2) ? 1 : 0;
std::fprintf(stderr, "[split-probe] remove-mid rm_ok=%d after=[%s,%s]\n",
rm_ok, rm_after_0.c_str(), rm_after_1.c_str());
}
split_probe_next(*a);
break;
}
case 14: {
// Row weights track figure rows on insert/remove.
rs.set_cells_per_row({1, 1, 1});
rs.set_row_weights({3.0f, 1.0f, 1.0f});
split_probe_restructured(*a);
std::string wt_before;
for (float w : rs.row_weights()) wt_before += (wt_before.empty() ? "" : ",") + std::to_string(w);
rs.remove_row(0);
split_probe_restructured(*a);
std::string wt_after;
for (float w : rs.row_weights()) wt_after += (wt_after.empty() ? "" : ",") + std::to_string(w);
const int wt_ok = rs.row_weights().size() == 2 &&
std::fabs(rs.row_weights()[0] - 1.0f) < 0.01f &&
std::fabs(rs.row_weights()[1] - 1.0f) < 0.01f ? 1 : 0;
std::fprintf(stderr, "[split-probe] row-weights wt_ok=%d before=[%s] after=[%s]\n",
wt_ok, wt_before.c_str(), wt_after.c_str());
split_probe_next(*a);
break;
}
case 15: {
split_probe_report(*a, "wt-done");
// Sensitivity: fill row 0 to 4 cells, check cell_plus_sensitive=0.
rs.set_cells_per_row({1});
split_probe_clear_all(*a);
split_probe_restructured(*a);
for (int i = 0; i < RowSet::kMaxCellsPerRow - 1; ++i) rs.insert_cell(0, rs.cells_in_row(0));
split_probe_restructured(*a);
// Rebuild the editor so the buttons exist.
rebuild_layout_editor(*a);
GtkWidget* cell_btn = layout_cell_button(*a, 0, 0);
const int cell_plus_sensitive = cell_btn != nullptr ? gtk_widget_get_sensitive(cell_btn) : -1;
// Fill to 8 plots total, check row_plus_sensitive=0.
rs.set_cells_per_row({4});
while (rs.row_count() < RowSet::kMaxRows) rs.insert_row(rs.figure_row_count());
split_probe_restructured(*a);
rebuild_layout_editor(*a);
GtkWidget* row_btn = layout_button(*a, true, 0);
const int row_plus_sensitive = row_btn != nullptr ? gtk_widget_get_sensitive(row_btn) : -1;
std::fprintf(stderr, "[split-probe] sensitivity cell_plus_sensitive=%d row_plus_sensitive=%d "
"plots=%d\n",
cell_plus_sensitive, row_plus_sensitive, rs.row_count());
// Close editor.
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(a->layout_toggle), false);
break;
}
default: