layout editor: column drag follows the pointer through the cell floor
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
2183317953
commit
4fab14949c
5 files changed
+127
-52
No files matched your search
+24
-10
@@ -758,30 +758,37 @@ void layout_col_apply(App& app, int plot, double offset_px) {
|
||||
const int fr = rs.figure_row_of(plot);
|
||||
const int k = rs.cell_of(plot);
|
||||
if (fr < 0 || k < 1) return;
|
||||
// The weights the drag began with: a drag is relative to its start, so reading the live weights here
|
||||
// would compound with every update.
|
||||
if (app.layout_col_drag_start.size() != static_cast<std::size_t>(rs.cells_in_row(fr))) return;
|
||||
const double scale =
|
||||
app.area != nullptr && app.area->device_scale() > 0.0f ? app.area->device_scale() : 1.0;
|
||||
const double pair = app.layout_col_drag_left0 + app.layout_col_drag_right0;
|
||||
const double floor = std::min(kMinCellPx, pair * 0.5);
|
||||
const double want_left = std::clamp(app.layout_col_drag_left0 + offset_px / scale,
|
||||
floor, pair - floor);
|
||||
std::vector<float> w = rs.cell_weights(fr);
|
||||
w.resize(static_cast<std::size_t>(rs.cells_in_row(fr)), 1.0f);
|
||||
// The plotter lays a cell out as `floor + extra * weight / total`, so a width is *not* proportional to
|
||||
// its weight: the pair's `pair` px are `2 * floor` plus `(pair - 2 * floor)` shared by the two weights.
|
||||
// Solve for the share that puts the left cell at `want_left`; the pair's weight sum stays what it was,
|
||||
// which keeps the pair's total width (and every other cell) where it was.
|
||||
const double spare = pair - 2.0 * kPlotterMinCellPx;
|
||||
const double frac = spare > 0.0
|
||||
? std::clamp((want_left - kPlotterMinCellPx) / spare, 0.001, 0.999)
|
||||
: 0.5;
|
||||
std::vector<float> w = app.layout_col_drag_start;
|
||||
const float sum = w[static_cast<std::size_t>(k - 1)] + w[static_cast<std::size_t>(k)];
|
||||
w[static_cast<std::size_t>(k - 1)] = static_cast<float>(sum * want_left / pair);
|
||||
w[static_cast<std::size_t>(k - 1)] = static_cast<float>(sum * frac);
|
||||
w[static_cast<std::size_t>(k)] = sum - w[static_cast<std::size_t>(k - 1)];
|
||||
rs.set_cell_weights(fr, w);
|
||||
if (app.area_widget != nullptr) gtk_gl_area_queue_render(GTK_GL_AREA(app.area_widget));
|
||||
}
|
||||
|
||||
void on_layout_col_begin(GtkGestureDrag* gesture, double, double, gpointer user) {
|
||||
App& app = *static_cast<App*>(user);
|
||||
app.layout_col_drag_plot =
|
||||
GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gesture), "udpscope-layout-col-drag"));
|
||||
const int plot = app.layout_col_drag_plot;
|
||||
if (plot < 0 || plot >= app.rows->row_count()) return;
|
||||
bool layout_col_drag_begin(App& app, int plot) {
|
||||
app.layout_col_drag_plot = plot;
|
||||
if (app.rows == nullptr || plot < 1 || plot >= app.rows->row_count()) return false;
|
||||
const int fr = app.rows->figure_row_of(plot);
|
||||
const int k = app.rows->cell_of(plot);
|
||||
if (fr < 0 || k < 1) return;
|
||||
if (fr < 0 || k < 1) return false;
|
||||
const plot::FigureLayout& fl = app.rows->figure().layout();
|
||||
const double scale =
|
||||
app.area != nullptr && app.area->device_scale() > 0.0f ? app.area->device_scale() : 1.0;
|
||||
@@ -789,6 +796,13 @@ void on_layout_col_begin(GtkGestureDrag* gesture, double, double, gpointer user)
|
||||
app.layout_col_drag_right0 = static_cast<double>(fl.cells[static_cast<std::size_t>(plot)].w) / scale;
|
||||
app.layout_col_drag_start = app.rows->cell_weights(fr);
|
||||
app.layout_col_drag_start.resize(static_cast<std::size_t>(app.rows->cells_in_row(fr)), 1.0f);
|
||||
return true;
|
||||
}
|
||||
|
||||
void on_layout_col_begin(GtkGestureDrag* gesture, double, double, gpointer user) {
|
||||
App& app = *static_cast<App*>(user);
|
||||
layout_col_drag_begin(
|
||||
app, GPOINTER_TO_INT(g_object_get_data(G_OBJECT(gesture), "udpscope-layout-col-drag")));
|
||||
}
|
||||
|
||||
void on_layout_col_update(GtkGestureDrag*, double offset_x, double, gpointer user) {
|
||||
|
||||
+10
-1
@@ -209,7 +209,8 @@ struct App {
|
||||
int layout_drag_row = -1;
|
||||
std::vector<float> layout_drag_start;
|
||||
/// Column drag state: which plot (right-hand cell), the two starting widths in logical px,
|
||||
/// and the cell weights snapshot at drag begin.
|
||||
/// and the cell weights snapshot at drag begin (`layout_col_apply()` redistributes the pair's
|
||||
/// weight sum from these, so a drag stays relative to where it started).
|
||||
int layout_col_drag_plot = -1;
|
||||
double layout_col_drag_left0 = 0.0;
|
||||
double layout_col_drag_right0 = 0.0;
|
||||
@@ -1622,6 +1623,11 @@ constexpr const char* kLayoutCellKey = "udpscope-layout-cell";
|
||||
constexpr const char* kLayoutColKey = "udpscope-layout-col";
|
||||
/// The minimum width a cell may be dragged to, in logical pixels.
|
||||
constexpr double kMinCellPx = 96.0;
|
||||
/// The plotter's own per-cell floor, `kMinCellWidthLogical` in `deps/plotter/src/plot/figure.cpp`
|
||||
/// (`place_cells()`): a cell is `floor + extra * weight / total`. Both it and `kMinCellPx` are
|
||||
/// *logical* px -- the plotter scales its floor by the `ui_scale` the renderer installs, which is
|
||||
/// `PlotArea::device_scale()`, the same divisor that turns a layout rect into logical px here.
|
||||
constexpr double kPlotterMinCellPx = 64.0;
|
||||
constexpr gint64 kMeasureRefreshUs = 100 * 1000;
|
||||
constexpr gint64 kMeasureHeavyUs = 5 * 1000;
|
||||
constexpr const char* kCursorModeNames[] = {"vertical", "horizontal", "both", "mix"};
|
||||
@@ -2110,6 +2116,9 @@ gboolean window_probe(gpointer user);
|
||||
GtkWidget* layout_button(App& a, bool insert, int index);
|
||||
GtkWidget* layout_cell_button(App& a, int fr, int pos);
|
||||
GtkWidget* layout_col_handle(App& a, int plot);
|
||||
/// Records the starting widths (logical px) and weights of the cell pair a column drag on `plot`
|
||||
/// (the right-hand cell) begins with. False when `plot` has no left neighbour.
|
||||
bool layout_col_drag_begin(App& app, int plot);
|
||||
void layout_col_apply(App& app, int plot, double offset_px);
|
||||
void layout_probe_report(App& a, const char* what);
|
||||
/// The split in the report lines, from the `RowSet` alone (no widget is asked, so the exit dump can
|
||||
|
||||
+80
-36
@@ -3826,20 +3826,15 @@ gboolean layout_probe_step(gpointer user) {
|
||||
// Each step changes the arrangement of cells, re-points the area at the new figure, and lets the
|
||||
// next step (250 ms on) read what a frame was actually laid out with.
|
||||
|
||||
void split_probe_report(App& a, const char* tag) {
|
||||
/// 1 when every row's cells fill its rect edge to edge, share its y and height, and are one `col_gap`
|
||||
/// apart (and the layout has the cells it should); the readout both the state lines and `col-drag` use.
|
||||
int split_probe_tile(App& a) {
|
||||
RowSet& rs = *a.rows;
|
||||
const double scale = (a.area != nullptr && a.area->device_scale() > 0.0f)
|
||||
? static_cast<double>(a.area->device_scale()) : 1.0;
|
||||
const plot::FigureLayout& fl = rs.figure().layout();
|
||||
const int frows = rs.figure_row_count();
|
||||
// tile: every row's cells fill its rect edge to edge, share its y and height, and are one
|
||||
// `col_gap` apart.
|
||||
int tile = fl.cells.size() == static_cast<std::size_t>(rs.row_count()) &&
|
||||
fl.rows.size() == static_cast<std::size_t>(frows)
|
||||
? 1 : 0;
|
||||
std::string cells_s;
|
||||
std::string widths_s;
|
||||
std::string weights_s;
|
||||
for (int fr = 0; fr < frows && tile; ++fr) {
|
||||
const int first = rs.first_plot_of_row(fr);
|
||||
const int n = rs.cells_in_row(fr);
|
||||
@@ -3857,6 +3852,19 @@ void split_probe_report(App& a, const char* tag) {
|
||||
}
|
||||
}
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
void split_probe_report(App& a, const char* tag) {
|
||||
RowSet& rs = *a.rows;
|
||||
const double scale = (a.area != nullptr && a.area->device_scale() > 0.0f)
|
||||
? static_cast<double>(a.area->device_scale()) : 1.0;
|
||||
const plot::FigureLayout& fl = rs.figure().layout();
|
||||
const int frows = rs.figure_row_count();
|
||||
const int tile = split_probe_tile(a);
|
||||
std::string cells_s;
|
||||
std::string widths_s;
|
||||
std::string weights_s;
|
||||
for (int fr = 0; fr < frows; ++fr) {
|
||||
const int first = rs.first_plot_of_row(fr);
|
||||
const int n = rs.cells_in_row(fr);
|
||||
@@ -4176,37 +4184,73 @@ gboolean split_probe_step(gpointer user) {
|
||||
}
|
||||
case 12: {
|
||||
split_probe_report(*a, "cell-assigned");
|
||||
// Column drag: apply a +80 px offset, verify widths shifted, clamp to extremes.
|
||||
// Column drag, through the real begin/apply pair: every drag is relative to the state it began
|
||||
// from, and the divider must follow the pointer (the cell is not proportional to its weight --
|
||||
// the plotter floors it -- so the weights are solved for the width).
|
||||
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);
|
||||
const int plot1 = rs.first_plot_of_row(0) + 1;
|
||||
const auto width = [&](int cell) {
|
||||
return static_cast<double>(rs.figure().layout().cells[static_cast<std::size_t>(cell)].w) / scale2;
|
||||
};
|
||||
// One drag of `dx` logical px from the arrangement the figure is in now, then the widths read
|
||||
// back from the layout (the figure re-lays out on the weights, not on a frame).
|
||||
struct Pair { double l0, r0, l, r; };
|
||||
const auto drag = [&](double dx) {
|
||||
Pair p{};
|
||||
layout_col_drag_begin(*a, plot1);
|
||||
p.l0 = width(0);
|
||||
p.r0 = width(1);
|
||||
layout_col_apply(*a, plot1, dx * scale2);
|
||||
p.l = width(0);
|
||||
p.r = width(1);
|
||||
return p;
|
||||
};
|
||||
// A drag of +80 from the start: the left cell grows by 80 and the pair keeps its total.
|
||||
const Pair d80 = drag(80.0);
|
||||
const int left_gt = d80.l > d80.r ? 1 : 0;
|
||||
const int left_grew = d80.l > d80.l0 ? 1 : 0;
|
||||
const int follows_80 = std::fabs(d80.l - (d80.l0 + 80.0)) <= 1.0 ? 1 : 0;
|
||||
const int sum_80 = std::fabs((d80.l + d80.r) - (d80.l0 + d80.r0)) <= 1.0 ? 1 : 0;
|
||||
// The same drag, again: it is relative to its start, so this +80 must land where the last did
|
||||
// (a drag that read the live weights would compound).
|
||||
layout_col_drag_begin(*a, plot1);
|
||||
const double l_start = width(0);
|
||||
const double r_start = width(1);
|
||||
layout_col_apply(*a, plot1, 30.0 * scale2);
|
||||
layout_col_apply(*a, plot1, 60.0 * scale2);
|
||||
layout_col_apply(*a, plot1, 80.0 * scale2);
|
||||
const int relative = std::fabs(width(0) - (l_start + 80.0)) <= 1.0 &&
|
||||
std::fabs(width(1) - (r_start - 80.0)) <= 1.0 ? 1 : 0;
|
||||
layout_col_apply(*a, plot1, 0.0);
|
||||
// Extreme drags, each from the settled start: the shrinking cell reads the 96 px floor.
|
||||
layout_col_drag_begin(*a, plot1);
|
||||
const double pair0 = width(0) + width(1);
|
||||
layout_col_apply(*a, plot1, 10000.0 * scale2);
|
||||
const double w0_max = width(0);
|
||||
const double w1_min = width(1);
|
||||
layout_col_apply(*a, plot1, -10000.0 * scale2);
|
||||
const double w0_min = width(0);
|
||||
const double w1_max = width(1);
|
||||
const int right_floor = std::fabs(w1_min - kMinCellPx) <= 1.0 ? 1 : 0;
|
||||
const int left_floor = std::fabs(w0_min - kMinCellPx) <= 1.0 ? 1 : 0;
|
||||
const int sum_ext = std::fabs((w0_max + w1_min) - pair0) <= 1.0 &&
|
||||
std::fabs((w0_min + w1_max) - pair0) <= 1.0 ? 1 : 0;
|
||||
const int both_above_min = (w0_min >= kMinCellPx - 1.0 && w1_min >= kMinCellPx - 1.0 &&
|
||||
w0_max >= kMinCellPx - 1.0 && w1_max >= kMinCellPx - 1.0) ? 1 : 0;
|
||||
const int ok = left_gt && left_grew && follows_80 && sum_80 && relative && right_floor &&
|
||||
left_floor && sum_ext && both_above_min ? 1 : 0;
|
||||
// Leave the drag ended, as the gesture's end does.
|
||||
a->layout_col_drag_plot = -1;
|
||||
a->layout_col_drag_start.clear();
|
||||
std::fprintf(stderr,
|
||||
"[split-probe] col-drag ok=%d left_gt_right=%d follows_80=%d relative=%d "
|
||||
"sum_80=%d right_floor=%d left_floor=%d sum_ext=%d both_above_min=%d "
|
||||
"tile=%d d80=[%.1f,%.1f]->[%.1f,%.1f] ext=[%.1f,%.1f|%.1f,%.1f]\n",
|
||||
ok, left_gt, follows_80, relative, sum_80, right_floor, left_floor, sum_ext,
|
||||
both_above_min, split_probe_tile(*a), d80.l0, d80.r0, d80.l, d80.r, w0_max,
|
||||
w1_min, w0_min, w1_max);
|
||||
}
|
||||
split_probe_next(*a);
|
||||
break;
|
||||
|
||||
Reference in new issue
Block a user