// The GLFW host's event routing in figure mode: which row's controller a pointer pixel // reaches. // // This is the adapter's own logic and not GLFW's -- a figure is several plots stacked in // one framebuffer, and the host has to decide which of them a gesture was aimed at -- so it // is tested here rather than through a window. Nothing below calls `run()`: the case needs // no GL context, no display and no window, only the rects `Figure::layout()` computes and // the controllers the host builds from them. #include "harness.hpp" #include #include namespace { /// A figure of `rows` rows over a known rect, with an x and a y axis in every row (so /// each row lays out as a plot rather than as an empty rect), handed to a host. struct Routed { explicit Routed(plot::u32 rows, plot::f32 gap = 0.0f) : figure(rows) { figure.set_viewport({0.0f, 0.0f, 300.0f, 600.0f}); figure.set_row_gap(gap); for (plot::u32 row = 0; row < rows; ++row) { plot::Plot& p = figure.plot(row); p.add_x_axis({"t", {0.0, 1.0}, false, plot::AxisPlace::Bottom, false}); p.add_y_axis({"v", {-1.0, 1.0}, false, plot::AxisPlace::Left, false}); } host.set_figure(&figure); figure.layout(); } plot::Figure figure; plot::glfw::Host host; }; } // namespace PLOT_TEST(the_host_routes_a_pixel_to_the_row_it_is_in) { Routed r(3u); const std::vector rows = r.figure.layout().rows; PLOT_CHECK(rows.size() == 3u); for (plot::u32 row = 0; row < 3u; ++row) { PLOT_CHECK(r.host.row_controller(row) != nullptr); const plot::f32 x = rows[row].x + 0.5f * rows[row].w; const plot::f32 y = rows[row].y + 0.5f * rows[row].h; PLOT_CHECK(&r.host.controller_at(x, y) == r.host.row_controller(row)); } // Above the first row and below the last one (the figure's own margins) are still the // nearest row rather than a dropped event: a drag that leaves the data area must end // where it should. PLOT_CHECK(&r.host.controller_at(150.0f, 0.0f) == r.host.row_controller(0u)); PLOT_CHECK(&r.host.controller_at(150.0f, 599.0f) == r.host.row_controller(2u)); } PLOT_TEST(the_host_routes_a_pixel_in_a_row_gap_to_the_nearest_row) { // A gap is what `set_row_gap()` asks for. Rows tile the figure at the default of 0, so // this is the only way a pixel can land in none of them -- and the routing must then // answer with the row it is *near*, not with row 0, which is what a "first row that // matches, else whatever `row` started at" loop would do. Routed r(3u, 40.0f); const std::vector rows = r.figure.layout().rows; PLOT_CHECK(rows.size() == 3u); for (plot::u32 row = 0; row + 1u < 3u; ++row) { const plot::f32 gap = rows[row + 1u].y - rows[row].bottom(); PLOT_CHECK(gap > 0.0f); const plot::f32 middle = rows[row].bottom() + 0.5f * gap; // Just past a row's bottom edge the gap pixel is that row's... PLOT_CHECK(&r.host.controller_at(150.0f, rows[row].bottom() + 1.0f) == r.host.row_controller(row)); // ... and just above the next row's top edge it is the next row's. PLOT_CHECK(&r.host.controller_at(150.0f, rows[row + 1u].y - 1.0f) == r.host.row_controller(row + 1u)); // The middle of the gap goes to the nearer of the two rows -- and, whatever it picks, // to a row of *this* figure rather than to a default: this is the assertion the // "first match, else whatever `row` started at" loop failed, since it answered row 0 // for every gap pixel. plot::input::Controller& in_gap = r.host.controller_at(150.0f, middle); PLOT_CHECK(&in_gap == r.host.row_controller(row) || &in_gap == r.host.row_controller(row + 1u)); PLOT_CHECK(&in_gap != r.host.row_controller(2u) || row + 1u == 2u); } } PLOT_TEST(the_host_keeps_its_own_controller_while_no_figure_is_set) { plot::glfw::Host host; PLOT_CHECK(host.figure() == nullptr); PLOT_CHECK(host.row_controller(0u) == nullptr); // With no figure the routing is the single-plot loop's: one controller for every pixel. PLOT_CHECK(&host.controller_at(10.0f, 10.0f) == &host.controller()); PLOT_CHECK(&host.controller_at(1000.0f, 1000.0f) == &host.controller()); plot::Figure figure(2u); figure.set_viewport({0.0f, 0.0f, 300.0f, 600.0f}); host.set_figure(&figure); PLOT_CHECK(host.figure() == &figure); PLOT_CHECK(host.row_controller(0u) != nullptr); PLOT_CHECK(host.row_controller(2u) == nullptr); // a row that does not exist // And clearing it goes back to the single-plot answer. host.set_figure(nullptr); PLOT_CHECK(host.figure() == nullptr); PLOT_CHECK(host.row_controller(0u) == nullptr); PLOT_CHECK(&host.controller_at(10.0f, 10.0f) == &host.controller()); }