layout editor: horizontal-resize icon, divider drag follows the pointer; x caption on every labelled plot; probes and docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
0c228c4822
commit
7972646dd2
14 files changed
+312
-65
No files matched your search
+45
-13
@@ -753,8 +753,11 @@ void on_layout_drag_end(GtkGestureDrag*, double, double, gpointer user) {
|
||||
|
||||
// --- column (cell) resize drag -----------------------------------------------
|
||||
|
||||
// The two cells' widths here are *plot* widths (a cell's rect without the first cell's y gutter): the
|
||||
// 96 px floor and the plotter's 64 px floor both apply to them, and the weights share them out.
|
||||
// The two cells' widths here are *plot* widths -- `layout().axes.w`, the framed plotting area, which is
|
||||
// what the plotter places cells by (cell rects add the first cell's y gutter, the last cell's right gutter
|
||||
// and the column gap on top): the 96 px floor and the plotter's 64 px floor both apply to them, and the
|
||||
// weights share them out. `offset_px` is the pointer's travel in *device* px (a gesture's logical offset
|
||||
// times the area's scale).
|
||||
void layout_col_apply(App& app, int plot, double offset_px) {
|
||||
RowSet& rs = *app.rows;
|
||||
const int fr = rs.figure_row_of(plot);
|
||||
@@ -791,18 +794,15 @@ bool layout_col_drag_begin(App& app, int plot) {
|
||||
const int fr = app.rows->figure_row_of(plot);
|
||||
const int k = app.rows->cell_of(plot);
|
||||
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;
|
||||
// The weights share out *plot* widths, so the pair is measured as plots: a cell's rect minus the y
|
||||
// gutter (axis strip + title column) that only the first cell of a row has. The left cell of the pair
|
||||
// is the first one when `plot - 1` starts the row; the right cell never has a gutter, and neither does
|
||||
// a non-first left cell (its plot's strips read 0).
|
||||
// The weights share out *plot* widths, so the pair is measured as plots: each cell's `axes.w`, the
|
||||
// plotting area the plotter places a cell by. Not the cell rect: that adds the y gutter of the first
|
||||
// cell, the right gutter of the last and half a column gap, none of which the weights apply to.
|
||||
(void)app.rows->figure().layout(); // non-const: re-lays the figure, and every plot, on the current weights
|
||||
const auto plot_width = [&](int cell) {
|
||||
const RowSet::Row& r = app.rows->row(cell);
|
||||
const double gutter =
|
||||
r.plot != nullptr ? double(r.plot->layout().y_axis_width + r.plot->layout().y_title_width) : 0.0;
|
||||
return std::max(0.0, double(fl.cells[static_cast<std::size_t>(cell)].w) - gutter);
|
||||
return r.plot != nullptr ? std::max(0.0, static_cast<double>(r.plot->layout().axes.w)) : 0.0;
|
||||
};
|
||||
app.layout_col_drag_left0 = plot_width(plot - 1) / scale;
|
||||
app.layout_col_drag_right0 = plot_width(plot) / scale;
|
||||
@@ -811,15 +811,39 @@ bool layout_col_drag_begin(App& app, int plot) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Where the drag handle sits in the overlay's coordinates (logical px), or `fallback` when it cannot be
|
||||
/// asked. The handle follows the divider it drags (`sync_layout_editor()` re-places it every frame), so it
|
||||
/// is *moving* while the gesture runs.
|
||||
static double layout_col_handle_x(App& app, GtkWidget* handle, double fallback) {
|
||||
if (handle == nullptr || app.area_overlay == nullptr) return fallback;
|
||||
graphene_point_t out;
|
||||
const graphene_point_t origin = GRAPHENE_POINT_INIT(0.0f, 0.0f);
|
||||
if (!gtk_widget_compute_point(handle, app.area_overlay, &origin, &out)) return fallback;
|
||||
return static_cast<double>(out.x);
|
||||
}
|
||||
|
||||
double layout_col_total_offset(double offset, double handle_now, double handle_begin) {
|
||||
return offset + (handle_now - handle_begin);
|
||||
}
|
||||
|
||||
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")));
|
||||
app.layout_col_drag_handle_x0 = layout_col_handle_x(
|
||||
app, gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)), 0.0);
|
||||
}
|
||||
|
||||
void on_layout_col_update(GtkGestureDrag*, double offset_x, double, gpointer user) {
|
||||
void on_layout_col_update(GtkGestureDrag* gesture, double offset_x, double, gpointer user) {
|
||||
App& app = *static_cast<App*>(user);
|
||||
layout_col_apply(app, app.layout_col_drag_plot, offset_x);
|
||||
// The gesture reports its offset in the *handle's* coordinates, and the handle follows the divider, so
|
||||
// the offset shrinks by the handle's own displacement; adding that back gives the pointer's travel.
|
||||
const double now = layout_col_handle_x(
|
||||
app, gtk_event_controller_get_widget(GTK_EVENT_CONTROLLER(gesture)), app.layout_col_drag_handle_x0);
|
||||
const double scale =
|
||||
app.area != nullptr && app.area->device_scale() > 0.0f ? app.area->device_scale() : 1.0;
|
||||
layout_col_apply(app, app.layout_col_drag_plot,
|
||||
layout_col_total_offset(offset_x, now, app.layout_col_drag_handle_x0) * scale);
|
||||
}
|
||||
|
||||
void on_layout_col_end(GtkGestureDrag*, double, double, gpointer user) {
|
||||
@@ -827,6 +851,7 @@ void on_layout_col_end(GtkGestureDrag*, double, double, gpointer user) {
|
||||
app.layout_col_drag_plot = -1;
|
||||
app.layout_col_drag_left0 = 0.0;
|
||||
app.layout_col_drag_right0 = 0.0;
|
||||
app.layout_col_drag_handle_x0 = 0.0;
|
||||
app.layout_col_drag_start.clear();
|
||||
schedule_layout_save(app);
|
||||
}
|
||||
@@ -948,7 +973,7 @@ void rebuild_layout_editor(App& app) {
|
||||
const int first = app.rows->first_plot_of_row(fr);
|
||||
for (int k = 1; k < n; ++k) {
|
||||
const int plot = first + k;
|
||||
GtkWidget* b = layout_overlay_button(app, "go-next-symbolic", "drag to resize the plots");
|
||||
GtkWidget* b = layout_overlay_button(app, "udpscope-resize-cols-symbolic", "drag to resize the plots");
|
||||
gtk_widget_set_halign(b, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign(b, GTK_ALIGN_START);
|
||||
g_object_set_data(G_OBJECT(b), kLayoutColKey, GINT_TO_POINTER(plot));
|
||||
@@ -3320,6 +3345,13 @@ void note_panel_controls(App& app) {
|
||||
app.dbg_toolbar_icons += std::string(app.dbg_toolbar_icons.empty() ? "" : " ") + name + "=" +
|
||||
icon + (gtk_icon_theme_has_icon(theme, icon) ? "(ok)" : "(MISSING)");
|
||||
}
|
||||
// The layout editor's two resize handles are not toolbar buttons (they exist only while the editor is
|
||||
// up), so they are asked of the theme by name: the same silent failure, the same line.
|
||||
for (const auto& [label, icon] : {std::pair<const char*, const char*>{"resize_rows", "udpscope-resize-rows-symbolic"},
|
||||
std::pair<const char*, const char*>{"resize_cols", "udpscope-resize-cols-symbolic"}}) {
|
||||
app.dbg_toolbar_icons += std::string(app.dbg_toolbar_icons.empty() ? "" : " ") + label + "=" + icon +
|
||||
(gtk_icon_theme_has_icon(theme, icon) ? "(ok)" : "(MISSING)");
|
||||
}
|
||||
// The application icon, the same two ways and for the same reason: a resource path that was
|
||||
// never registered is not an error, it is a missing-image placeholder. This one is read on
|
||||
// the first tick like the line above, because the dump runs after the window is destroyed.
|
||||
|
||||
Reference in new issue
Block a user