功能比较完善的一版
This commit is contained in:
@@ -154,6 +154,29 @@ template <Axis_Object Object>
|
||||
void Abs_Axis::Private::bind_private_crtp(Object* object) {
|
||||
Prev_Private::bind_private_crtp(object);
|
||||
dispatch = &Private::dispatch_for<Object>();
|
||||
this->event_routing_distance_run = [](const Root* root, const Event& event) -> std::optional<double> {
|
||||
if (event.type != Event_Type::wheel && event.type != Event_Type::pointer_press
|
||||
&& event.type != Event_Type::pointer_move && event.type != Event_Type::pointer_release) return std::nullopt;
|
||||
const auto* pointer = dynamic_cast<const Pointer_Event_Capability*>(&event);
|
||||
if (!pointer) return std::nullopt;
|
||||
const auto* axis = static_cast<const Object*>(root);
|
||||
const auto& private_data = static_cast<const typename Object::Private&>(*axis->d);
|
||||
const auto& layout = static_cast<const Prop&>(*private_data.current);
|
||||
const Point_F first = layout.position;
|
||||
const Point_F second = layout.orientation == Axis_Orientation::horizontal
|
||||
? Point_F{first.x + layout.pixel_length, first.y}
|
||||
: Point_F{first.x, first.y + layout.pixel_length};
|
||||
const double segment_x = second.x - first.x;
|
||||
const double segment_y = second.y - first.y;
|
||||
const double length_squared = segment_x * segment_x + segment_y * segment_y;
|
||||
const double projection = length_squared > 0.0
|
||||
? std::clamp(((pointer->position_x() - first.x) * segment_x
|
||||
+ (pointer->position_y() - first.y) * segment_y) / length_squared, 0.0, 1.0)
|
||||
: 0.0;
|
||||
const double nearest_x = first.x + segment_x * projection;
|
||||
const double nearest_y = first.y + segment_y * projection;
|
||||
return std::hypot(pointer->position_x() - nearest_x, pointer->position_y() - nearest_y);
|
||||
};
|
||||
}
|
||||
inline void Abs_Axis::Private::prepare_data(Attached auto* object) {
|
||||
using Object = std::remove_pointer_t<decltype(object)>;
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace aethera::render_2d::detail {
|
||||
/* 保留无评分接收者的绘制层级顺序,再将有评分的候选者按距离从近到远排列。 */
|
||||
struct Nearest_Event_Target_Rule {
|
||||
template <typename Target, typename Score>
|
||||
static void apply(std::vector<Target>& targets, Score&& score) {
|
||||
std::stable_sort(targets.begin(), targets.end(), [&](const Target& left, const Target& right) {
|
||||
const std::optional<double> left_score = score(left);
|
||||
const std::optional<double> right_score = score(right);
|
||||
if (!left_score) return right_score.has_value();
|
||||
if (!right_score) return false;
|
||||
return *left_score < *right_score;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include "common/Curve_Plot.hpp"
|
||||
#include <cmath>
|
||||
#include <sstream>
|
||||
namespace aethera::render_2d {
|
||||
struct Selection_Rectangle_Overlay::Private : Prev_Private {
|
||||
@@ -62,10 +63,18 @@ void Selection_Rectangle_Overlay::Private::handle_event(Object* object, const Ev
|
||||
const auto* pointer = dynamic_cast<const Pointer_Event_Capability*>(&event);
|
||||
if (!pointer) return;
|
||||
const Point_F point{pointer->position_x(), pointer->position_y()};
|
||||
if (event.type == Event_Type::pointer_press && pointer->pointer_button() == Mouse_Button::left) { dragging = true; drag_origin = point; drag_current = point; object->template mark_dirty<Paint_Tag>(); event.accept(); return; }
|
||||
if (event.type == Event_Type::pointer_press && pointer->pointer_button() == Mouse_Button::left) {
|
||||
const auto modifiers = static_cast<std::underlying_type_t<Keyboard_Modifier>>(pointer->keyboard_modifiers());
|
||||
const auto control = static_cast<std::underlying_type_t<Keyboard_Modifier>>(Keyboard_Modifier::control);
|
||||
if ((modifiers & control) == 0) object->template set<&Prop::selected_regions>(std::vector<Rect_F>{});
|
||||
dragging = true; drag_origin = point; drag_current = point; object->template mark_dirty<Paint_Tag>(); event.accept(); return;
|
||||
}
|
||||
if (event.type == Event_Type::pointer_move && dragging) { drag_current = point; object->template mark_dirty<Paint_Tag>(); event.accept(); return; }
|
||||
if (event.type != Event_Type::pointer_release || !dragging) return;
|
||||
dragging = false; drag_current = point;
|
||||
if (std::hypot(drag_current.x - drag_origin.x, drag_current.y - drag_origin.y) < 3.0) {
|
||||
object->template mark_dirty<Paint_Tag>(); event.accept(); return;
|
||||
}
|
||||
const Axis_Coordinate first_x = horizontal_axis->point_to_coordinate(drag_origin);
|
||||
const Axis_Coordinate second_x = horizontal_axis->point_to_coordinate(drag_current);
|
||||
const Axis_Coordinate first_y = vertical_axis->point_to_coordinate(drag_origin);
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
#pragma once
|
||||
#include "common/Curve_Plot.hpp"
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
namespace aethera::render_2d {
|
||||
struct Sweep_Spectrum::Private : Prev_Private {
|
||||
struct Prepared {
|
||||
std::vector<detail::Curve_Prepared> partitions{}; /* Prepare 子图各分块的曲线输出。 */
|
||||
std::vector<Plot_Value> values{}; /* 已提交扫描块拼接后的连续功率值。 */
|
||||
Axis_Range domain{}; /* 当前已到达数据对应的频率区间。 */
|
||||
Point_F marker_first{}; /* 当前扫描位置线的首端点。 */
|
||||
Point_F marker_second{}; /* 当前扫描位置线的末端点。 */
|
||||
Size canvas{}; /* 当前 Scene viewport 的像素尺寸。 */
|
||||
@@ -58,11 +60,19 @@ template <Attached Object>
|
||||
void Sweep_Spectrum::Private::prepare_frame(Object* object) {
|
||||
const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>(); prepared = {}; prepared.partitions.resize(graph_partition_count); prepared.canvas = scene->template read_prop<Render_Scene_2D::Base_Tag>().viewport;
|
||||
if (prepared.canvas.empty() || frequency_layout.orientation == power_layout.orientation) return; for (const auto& block : state.blocks) prepared.values.insert(prepared.values.end(), block.begin(), block.end()); if (prepared.values.empty()) return;
|
||||
prepared.marker_first = detail::map_plot_point(frequency_axis, state.frequency_range.target, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation); prepared.marker_second = detail::map_plot_point(frequency_axis, state.frequency_range.target, power_axis, power_state.coordinate_range.target, frequency_layout.orientation); prepared.valid = true;
|
||||
const std::size_t bins_per_block = state.bins_per_block > 0
|
||||
? state.bins_per_block : state.blocks.front().size();
|
||||
const std::size_t expected_points = std::max<std::size_t>(1, bins_per_block * std::max<std::size_t>(1, state.block_count));
|
||||
const double progress = expected_points > 1
|
||||
? std::clamp(static_cast<double>(prepared.values.size() - 1) / static_cast<double>(expected_points - 1), 0.0, 1.0)
|
||||
: 1.0;
|
||||
const auto latest_frequency = state.frequency_range.origin + state.frequency_range.length() * progress;
|
||||
prepared.domain = {state.frequency_range.origin, latest_frequency};
|
||||
prepared.marker_first = detail::map_plot_point(frequency_axis, latest_frequency, power_axis, power_state.coordinate_range.origin, frequency_layout.orientation); prepared.marker_second = detail::map_plot_point(frequency_axis, latest_frequency, power_axis, power_state.coordinate_range.target, frequency_layout.orientation); prepared.valid = true;
|
||||
}
|
||||
template <Attached Object>
|
||||
void Sweep_Spectrum::Private::prepare_partition(Object* object, Plot_Partition_Count index) {
|
||||
if (!prepared.valid) return; const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& frequency_state = frequency_axis->template read_prop<Numeric_Axis::Base_Tag>(); const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>(); const auto range = detail::curve_partition_range(prepared.values.size(), index, prepared.partitions.size(), state.frequency_range);
|
||||
if (!prepared.valid) return; const auto& state = object->template read_prop<Sweep_Spectrum::Base_Tag>(); const auto& frequency_layout = frequency_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& power_layout = power_axis->template read_prop<Abs_Axis::Base_Tag>(); const auto& frequency_state = frequency_axis->template read_prop<Numeric_Axis::Base_Tag>(); const auto& power_state = power_axis->template read_prop<Numeric_Axis::Base_Tag>(); const auto range = detail::curve_partition_range(prepared.values.size(), index, prepared.partitions.size(), prepared.domain);
|
||||
prepared.partitions[index] = detail::prepare_curve(std::span<const Plot_Value>(prepared.values).subspan(range.first_sample, range.sample_count), range.domain, state.interpolation_mode, state.visible_range_only, frequency_state.coordinate_range, power_state.coordinate_range, frequency_axis, power_axis, frequency_layout.orientation, power_layout.orientation);
|
||||
}
|
||||
template <Attached Object>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#pragma once
|
||||
#include "../event/Event_Routing_Rules.hpp"
|
||||
#include <stdexcept>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
@@ -277,8 +278,13 @@ void Render_Scene_2D::Private::dispatch_event(Object* object, const Event& event
|
||||
if (auto* renderable = view.object(node)) order.push_back(renderable);
|
||||
});
|
||||
if (!result) throw std::logic_error("render scene paint graph became invalid during event dispatch");
|
||||
for (auto current = order.rbegin(); current != order.rend() && !event.is_accepted(); ++current)
|
||||
(*current)->dispatch_event(event);
|
||||
std::vector<Renderable*> delivery_order(order.rbegin(), order.rend());
|
||||
detail::Nearest_Event_Target_Rule::apply(delivery_order,
|
||||
[&](const Renderable* target) { return target->event_routing_distance(event); });
|
||||
for (auto* target : delivery_order) {
|
||||
if (event.is_accepted()) break;
|
||||
target->dispatch_event(event);
|
||||
}
|
||||
}
|
||||
template <Attached Object>
|
||||
const Render_Scene_2D::Private::Dispatch& Render_Scene_2D::Private::dispatch_for() {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <render_2D/axis/Axis.hpp>
|
||||
#include <render_2D/event/Event.hpp>
|
||||
#include <render_2D/event/Event_Routing_Rules.hpp>
|
||||
#include <render_2D/scene/Render_Scene_2D.hpp>
|
||||
#include <gtest/gtest.h>
|
||||
namespace {
|
||||
@@ -12,6 +13,15 @@ std::unique_ptr<Object> build_axis() {
|
||||
return std::move(result).value();
|
||||
}
|
||||
}
|
||||
TEST(event_routing_rule, keeps_layered_targets_before_nearest_scored_targets) {
|
||||
std::vector<int> targets{1, 2, 3, 4};
|
||||
aethera::render_2d::detail::Nearest_Event_Target_Rule::apply(targets, [](const int target) -> std::optional<double> {
|
||||
if (target == 2) return 5.0;
|
||||
if (target == 4) return 1.0;
|
||||
return std::nullopt;
|
||||
});
|
||||
EXPECT_EQ(targets, (std::vector<int>{1, 3, 4, 2}));
|
||||
}
|
||||
TEST(axis_dispatch, numeric_axis_public_shell_reads_final_private_state) {
|
||||
using Object = Impl<Numeric_Axis>;
|
||||
auto axis = build_axis<Object>();
|
||||
@@ -104,6 +114,46 @@ TEST(axis_event, numeric_axis_wheel_zoom_and_drag_update_authoritative_range) {
|
||||
axis->advance();
|
||||
EXPECT_EQ(axis->coordinate_range(), (Axis_Range{-0.4, 8.6}));
|
||||
}
|
||||
TEST(axis_event, scene_routes_pointer_interaction_to_the_nearest_axis_segment) {
|
||||
using Axis_Object = Impl<Numeric_Axis>;
|
||||
using Scene_Object = Impl<Render_Scene_2D>;
|
||||
initialize_runtime(2);
|
||||
auto horizontal = build_axis<Axis_Object>();
|
||||
auto vertical = build_axis<Axis_Object>();
|
||||
auto scene = build_axis<Scene_Object>();
|
||||
horizontal->set<&Abs_Axis::Prop::position>(Point_F{20.0, 180.0});
|
||||
horizontal->set<&Abs_Axis::Prop::canvas_size>(Size{200, 200});
|
||||
horizontal->set<&Abs_Axis::Prop::pixel_length>(160.0);
|
||||
horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 10.0});
|
||||
vertical->set<&Abs_Axis::Prop::position>(Point_F{20.0, 180.0});
|
||||
vertical->set<&Abs_Axis::Prop::canvas_size>(Size{200, 200});
|
||||
vertical->set<&Abs_Axis::Prop::pixel_length>(-160.0);
|
||||
vertical->set<&Abs_Axis::Prop::orientation>(Axis_Orientation::vertical);
|
||||
vertical->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 10.0});
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(Size{200, 200});
|
||||
ASSERT_TRUE((scene->edit_dependency_graph<Prepare_Data_Tag, Paint_Tag>([&](auto& prepare, auto& paint) {
|
||||
prepare.add(horizontal.get()); prepare.add(vertical.get());
|
||||
paint.add(horizontal.get()); paint.add(vertical.get());
|
||||
}).has_value()));
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
|
||||
Wheel_Event near_vertical;
|
||||
near_vertical.position = {22.0, 80.0};
|
||||
near_vertical.angle_delta_y = 120.0;
|
||||
scene->dispatch_event(near_vertical);
|
||||
scene->render();
|
||||
EXPECT_DOUBLE_EQ(horizontal->coordinate_range().size(), 10.0);
|
||||
EXPECT_DOUBLE_EQ(vertical->coordinate_range().size(), 9.0);
|
||||
|
||||
Wheel_Event near_horizontal;
|
||||
near_horizontal.position = {120.0, 178.0};
|
||||
near_horizontal.angle_delta_y = 120.0;
|
||||
scene->dispatch_event(near_horizontal);
|
||||
scene->render();
|
||||
EXPECT_DOUBLE_EQ(horizontal->coordinate_range().size(), 9.0);
|
||||
EXPECT_DOUBLE_EQ(vertical->coordinate_range().size(), 9.0);
|
||||
}
|
||||
TEST(axis_state, invalid_numeric_range_is_rejected_without_poisoning_pending_state) {
|
||||
using Object = Impl<Numeric_Axis>;
|
||||
auto axis = build_axis<Object>();
|
||||
|
||||
@@ -15,6 +15,15 @@ std::unique_ptr<Object> build_object(Args&&... args) {
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
template <typename Scene, typename... Renderables>
|
||||
std::unique_ptr<Scene> build_scene(Renderables*... renderables) {
|
||||
typename Scene::Builder builder;
|
||||
(builder.add_renderable(renderables), ...);
|
||||
auto result = builder.build();
|
||||
if (!result) std::terminate();
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
template <typename Axis>
|
||||
void configure_axis(Axis* axis, Axis_Orientation orientation, Point_F position, Axis_Pixel_Length length, Size canvas) {
|
||||
axis->template set<&Abs_Axis::Prop::orientation>(orientation);
|
||||
@@ -33,7 +42,6 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) {
|
||||
using Sweep = Impl<Sweep_Spectrum>;
|
||||
initialize_runtime(2);
|
||||
const Size canvas{160, 120};
|
||||
auto scene = build_object<Scene>();
|
||||
auto frequency = build_object<Frequency>();
|
||||
auto power = build_object<Numeric>();
|
||||
auto time = build_object<Time>();
|
||||
@@ -45,8 +53,8 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) {
|
||||
frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||
power->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-100.0, 0.0});
|
||||
value->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||
auto trace = build_object<Trace>(scene.get(), time.get(), value.get());
|
||||
auto sweep = build_object<Sweep>(scene.get(), frequency.get(), power.get());
|
||||
auto trace = build_object<Trace>(time.get(), value.get());
|
||||
auto sweep = build_object<Sweep>(frequency.get(), power.get());
|
||||
trace->set<&Frequency_Trace::Prop::partition_mode>(Plot_Partition_Mode::fixed);
|
||||
trace->set<&Frequency_Trace::Prop::partition_count>(2u);
|
||||
trace->append_sample(0, 10.0);
|
||||
@@ -57,6 +65,7 @@ TEST(plottable_migration, curve_plots_share_partitioned_rendering) {
|
||||
sweep->set<&Sweep_Spectrum::Prop::partition_count>(2u);
|
||||
const std::array<Plot_Value, 4> block{-90.0, -60.0, -30.0, -10.0};
|
||||
sweep->append_block(block);
|
||||
auto scene = build_scene<Scene>(trace.get(), sweep.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
@@ -75,7 +84,6 @@ TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) {
|
||||
using Fall = Impl<Waterfall>;
|
||||
initialize_runtime(2);
|
||||
const Size canvas{160, 120};
|
||||
auto scene = build_object<Scene>();
|
||||
auto frequency = build_object<Frequency>();
|
||||
auto power = build_object<Numeric>();
|
||||
auto time = build_object<Time>();
|
||||
@@ -84,8 +92,8 @@ TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) {
|
||||
configure_axis(time.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
||||
frequency->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||
power->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-100.0, 0.0});
|
||||
auto glow = build_object<Glow>(scene.get(), frequency.get(), power.get());
|
||||
auto waterfall = build_object<Fall>(scene.get(), frequency.get(), time.get());
|
||||
auto glow = build_object<Glow>(frequency.get(), power.get());
|
||||
auto waterfall = build_object<Fall>(frequency.get(), time.get());
|
||||
glow->set<&Afterglow::Prop::frequency_range>(Axis_Range{0.0, 100.0});
|
||||
glow->set<&Afterglow::Prop::power_range>(Axis_Range{-100.0, 0.0});
|
||||
glow->set<&Afterglow::Prop::power_point_size>(16u);
|
||||
@@ -95,6 +103,7 @@ TEST(plottable_migration, raster_plots_share_partitioned_color_blocks) {
|
||||
glow->append_spectrum(row);
|
||||
waterfall->append_row(0, row);
|
||||
waterfall->append_row(1, row);
|
||||
auto scene = build_scene<Scene>(glow.get(), waterfall.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
@@ -111,18 +120,18 @@ TEST(plottable_migration, direct_overlay_and_constellation_build_and_render) {
|
||||
using Overlay = Impl<Selection_Rectangle_Overlay>;
|
||||
initialize_runtime(2);
|
||||
const Size canvas{160, 120};
|
||||
auto scene = build_object<Scene>();
|
||||
auto horizontal = build_object<Numeric>();
|
||||
auto vertical = build_object<Numeric>();
|
||||
configure_axis(horizontal.get(), Axis_Orientation::horizontal, {20.0, 100.0}, 120.0, canvas);
|
||||
configure_axis(vertical.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
||||
horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-1.0, 1.0});
|
||||
vertical->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{-1.0, 1.0});
|
||||
auto diagram = build_object<Diagram>(scene.get(), horizontal.get(), vertical.get());
|
||||
auto overlay = build_object<Overlay>(scene.get(), horizontal.get(), vertical.get());
|
||||
auto diagram = build_object<Diagram>(horizontal.get(), vertical.get());
|
||||
auto overlay = build_object<Overlay>(horizontal.get(), vertical.get());
|
||||
diagram->set<&Constellation_Diagram::Prop::i_range>(Axis_Range{-1.0, 1.0});
|
||||
diagram->set<&Constellation_Diagram::Prop::q_range>(Axis_Range{-1.0, 1.0});
|
||||
diagram->append_point({0.25, -0.25});
|
||||
auto scene = build_scene<Scene>(diagram.get(), overlay.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
@@ -130,3 +139,46 @@ TEST(plottable_migration, direct_overlay_and_constellation_build_and_render) {
|
||||
EXPECT_EQ(overlay->read_state<Renderable::Base_Tag>().prepare_task_count, 0u);
|
||||
EXPECT_EQ(overlay->read_state<Renderable::Base_Tag>().paint_task_count, 1u);
|
||||
}
|
||||
|
||||
TEST(selection_overlay, control_extends_selection_and_plain_click_clears_it) {
|
||||
using Scene = Impl<Render_Scene_2D>;
|
||||
using Numeric = Impl<Numeric_Axis>;
|
||||
using Overlay = Impl<Selection_Rectangle_Overlay>;
|
||||
initialize_runtime(2);
|
||||
const Size canvas{160, 120};
|
||||
auto horizontal = build_object<Numeric>();
|
||||
auto vertical = build_object<Numeric>();
|
||||
configure_axis(horizontal.get(), Axis_Orientation::horizontal, {20.0, 100.0}, 120.0, canvas);
|
||||
configure_axis(vertical.get(), Axis_Orientation::vertical, {20.0, 100.0}, -80.0, canvas);
|
||||
horizontal->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||
vertical->set<&Numeric_Axis::Prop::coordinate_range>(Axis_Range{0.0, 100.0});
|
||||
auto overlay = build_object<Overlay>(horizontal.get(), vertical.get());
|
||||
auto scene = build_scene<Scene>(overlay.get());
|
||||
scene->set<&Render_Scene_2D::Prop::viewport>(canvas);
|
||||
scene->activate_view();
|
||||
scene->render();
|
||||
|
||||
const auto drag = [&](Point_F first, Point_F second, Keyboard_Modifier modifiers) {
|
||||
Pointer_Event press(Event_Type::pointer_press);
|
||||
press.position = first;
|
||||
press.button = Mouse_Button::left;
|
||||
press.modifiers = modifiers;
|
||||
overlay->dispatch_event(press);
|
||||
Pointer_Event move(Event_Type::pointer_move);
|
||||
move.position = second;
|
||||
move.modifiers = modifiers;
|
||||
overlay->dispatch_event(move);
|
||||
Pointer_Event release(Event_Type::pointer_release);
|
||||
release.position = second;
|
||||
release.button = Mouse_Button::left;
|
||||
release.modifiers = modifiers;
|
||||
overlay->dispatch_event(release);
|
||||
scene->render();
|
||||
};
|
||||
drag({30.0, 90.0}, {60.0, 60.0}, Keyboard_Modifier::none);
|
||||
EXPECT_EQ(overlay->selected_regions().size(), 1u);
|
||||
drag({70.0, 90.0}, {100.0, 60.0}, Keyboard_Modifier::control);
|
||||
EXPECT_EQ(overlay->selected_regions().size(), 2u);
|
||||
drag({120.0, 40.0}, {120.0, 40.0}, Keyboard_Modifier::none);
|
||||
EXPECT_TRUE(overlay->selected_regions().empty());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user