改结构
This commit is contained in:
+239
-247
@@ -15,6 +15,7 @@
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
@@ -30,18 +31,6 @@ using Frequency_Axis_Object = Impl<Frequency_Axis>;
|
||||
using Numeric_Axis_Object = Impl<Numeric_Axis>;
|
||||
using Time_Axis_Object = Impl<Time_Axis>;
|
||||
|
||||
template <typename Builder>
|
||||
auto finish_build(Builder&& builder) {
|
||||
auto result = builder.build();
|
||||
if (!result) throw std::logic_error("gallery scene topology validation failed");
|
||||
return std::move(result).value();
|
||||
}
|
||||
|
||||
template <typename Object, typename Builder>
|
||||
std::shared_ptr<Object> finish_shared(Builder&& builder) {
|
||||
return std::shared_ptr<Object>(finish_build(std::forward<Builder>(builder)));
|
||||
}
|
||||
|
||||
template <typename Integer>
|
||||
void append_binary(std::string& output, Integer value) {
|
||||
const auto start = output.size();
|
||||
@@ -93,14 +82,15 @@ struct Prop_Write {
|
||||
};
|
||||
using Plot_Input = std::variant<Plot_Event, Schema_Query, Prop_Write>;
|
||||
|
||||
template <typename... Owned_Objects>
|
||||
class Scene_View_Model final : public Plot::Scene_View {
|
||||
public:
|
||||
Scene_View_Model(std::vector<std::shared_ptr<void>> owned_objects,
|
||||
std::unique_ptr<detail::Renderable_Descriptor> value_descriptor,
|
||||
std::function<void(const Plot_Event&)> value_update)
|
||||
: objects(std::move(owned_objects)),
|
||||
descriptor(std::move(value_descriptor)),
|
||||
update_scene(std::move(value_update)) {}
|
||||
Scene_View_Model(std::unique_ptr<detail::Renderable_Descriptor> value_descriptor,
|
||||
std::function<void(const Plot_Event&)> value_update,
|
||||
Owned_Objects... owned_objects)
|
||||
: descriptor(std::move(value_descriptor)),
|
||||
update_scene(std::move(value_update)),
|
||||
objects(std::move(owned_objects)...) {}
|
||||
|
||||
nlohmann::json schema() const override { return descriptor->schema(); }
|
||||
nlohmann::json write_prop(std::string_view key, const nlohmann::json& value) override {
|
||||
@@ -109,40 +99,40 @@ public:
|
||||
void update(const Plot_Event& event) override { update_scene(event); }
|
||||
|
||||
private:
|
||||
std::vector<std::shared_ptr<void>> objects;
|
||||
std::unique_ptr<detail::Renderable_Descriptor> descriptor;
|
||||
std::function<void(const Plot_Event&)> update_scene;
|
||||
std::tuple<Owned_Objects...> objects;
|
||||
};
|
||||
|
||||
template <auto Member, structive::Fixed_String Key>
|
||||
using Prop_Field = detail::Prop_Field<Member, Key>;
|
||||
template <auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
using Prop_Field = detail::Prop_Field<Member, Key, Description>;
|
||||
|
||||
template <typename Definition, auto Member, structive::Fixed_String Key>
|
||||
using State_Field = detail::State_Field<typename Definition::Base_Tag, Member, Key>;
|
||||
template <typename Definition, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
using State_Field = detail::State_Field<typename Definition::Base_Tag, Member, Key, Description>;
|
||||
|
||||
template <typename... Fields, typename Object>
|
||||
template <typename... Fields, typename Object, typename... Owned_Objects>
|
||||
std::unique_ptr<Plot::Scene_View> make_scene_view(
|
||||
Object& object,
|
||||
std::vector<std::shared_ptr<void>> owners,
|
||||
std::function<void(const Plot_Event&)> update) {
|
||||
std::function<void(const Plot_Event&)> update,
|
||||
Owned_Objects&&... owned_objects) {
|
||||
using Definition = typename Object::Attached_Object;
|
||||
using Tag = typename Definition::Base_Tag;
|
||||
using State = typename Definition::State;
|
||||
using Adapter = detail::Renderable_Adapter<Object, Fields...,
|
||||
detail::State_Field<Tag, &State::prepare_dirty, "prepare_dirty">,
|
||||
detail::State_Field<Tag, &State::paint_dirty, "paint_dirty">,
|
||||
detail::State_Field<Tag, &State::prepare_executed, "prepare_executed">,
|
||||
detail::State_Field<Tag, &State::paint_executed, "paint_executed">,
|
||||
detail::State_Field<Tag, &State::prepare_graph_rebuilt, "prepare_graph_rebuilt">,
|
||||
detail::State_Field<Tag, &State::paint_graph_rebuilt, "paint_graph_rebuilt">,
|
||||
detail::State_Field<Tag, &State::prepare_task_count, "prepare_task_count">,
|
||||
detail::State_Field<Tag, &State::paint_task_count, "paint_task_count">,
|
||||
detail::State_Field<Tag, &State::prepare_execution_time_ns, "prepare_execution_time_ns">,
|
||||
detail::State_Field<Tag, &State::paint_execution_time_ns, "paint_execution_time_ns">>;
|
||||
return std::make_unique<Scene_View_Model>(
|
||||
std::move(owners),
|
||||
detail::State_Field<Tag, &State::prepare_dirty, "prepare_dirty", "Whether source changes require the prepare stage to run again.">,
|
||||
detail::State_Field<Tag, &State::paint_dirty, "paint_dirty", "Whether prepared visual data requires the paint stage to run again.">,
|
||||
detail::State_Field<Tag, &State::prepare_executed, "prepare_executed", "Whether the prepare stage executed during the latest scene cycle.">,
|
||||
detail::State_Field<Tag, &State::paint_executed, "paint_executed", "Whether the paint stage executed during the latest scene cycle.">,
|
||||
detail::State_Field<Tag, &State::prepare_graph_rebuilt, "prepare_graph_rebuilt", "Whether the prepare task graph was rebuilt during the latest cycle.">,
|
||||
detail::State_Field<Tag, &State::paint_graph_rebuilt, "paint_graph_rebuilt", "Whether the paint task graph was rebuilt during the latest cycle.">,
|
||||
detail::State_Field<Tag, &State::prepare_task_count, "prepare_task_count", "Number of tasks in the current prepare execution graph.">,
|
||||
detail::State_Field<Tag, &State::paint_task_count, "paint_task_count", "Number of tasks in the current paint execution graph.">,
|
||||
detail::State_Field<Tag, &State::prepare_execution_time_ns, "prepare_execution_time_ns", "Measured prepare-stage execution time in nanoseconds.">,
|
||||
detail::State_Field<Tag, &State::paint_execution_time_ns, "paint_execution_time_ns", "Measured paint-stage execution time in nanoseconds.">>;
|
||||
return std::make_unique<Scene_View_Model<std::remove_cvref_t<Owned_Objects>...>>(
|
||||
detail::make_renderable_descriptor(Adapter{object}),
|
||||
std::move(update));
|
||||
std::move(update),
|
||||
std::forward<Owned_Objects>(owned_objects)...);
|
||||
}
|
||||
|
||||
Frequency_Axis_Object::Builder frequency_axis_builder(Size canvas) {
|
||||
@@ -185,31 +175,25 @@ void resize_axes(Size viewport, Axes*... axes) {
|
||||
(axes->template set<&Abs_Axis::Prop::canvas_size>(viewport), ...);
|
||||
}
|
||||
|
||||
std::unique_ptr<Scene_2D> make_scene_2d() {
|
||||
constexpr Size canvas{720, 420};
|
||||
Scene_2D::Builder scene_builder;
|
||||
scene_builder
|
||||
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
||||
.set(&Render_Scene_2D::Prop::view_active, true);
|
||||
return finish_build(std::move(scene_builder));
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Plot> make_spectrum_plot(asio::any_io_executor executor) {
|
||||
constexpr Size canvas{720, 420};
|
||||
auto scene = make_scene_2d();
|
||||
auto frequency = finish_shared<Frequency_Axis_Object>(frequency_axis_builder(canvas));
|
||||
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas));
|
||||
Impl<Spectrum>::Builder builder(scene.get(), frequency.get(), vertical.get());
|
||||
builder
|
||||
auto scene = *Scene_2D::Builder{}
|
||||
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
||||
.set(&Render_Scene_2D::Prop::view_active, true)
|
||||
.build();
|
||||
auto frequency = *frequency_axis_builder(canvas).build();
|
||||
auto vertical = *numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas).build();
|
||||
auto spectrum = *Impl<Spectrum>::Builder(scene.get(), frequency.get(), vertical.get())
|
||||
.set(&Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0})
|
||||
.set(&Spectrum::Prop::max_hold_visible, true);
|
||||
auto object = finish_shared<Impl<Spectrum>>(std::move(builder));
|
||||
object->mark_dirty<Prepare_Data_Tag>();
|
||||
object->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = object.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
.set(&Spectrum::Prop::max_hold_visible, true)
|
||||
.build();
|
||||
spectrum->mark_dirty<Prepare_Data_Tag>();
|
||||
spectrum->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = spectrum.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
||||
std::array<double, 256> samples{};
|
||||
for (std::size_t i = 0; i < samples.size(); ++i) {
|
||||
@@ -220,120 +204,126 @@ std::shared_ptr<Plot> make_spectrum_plot(asio::any_io_executor executor) {
|
||||
}
|
||||
raw->update_samples(samples);
|
||||
};
|
||||
std::vector<std::shared_ptr<void>> owners{frequency, vertical, object};
|
||||
auto view = make_scene_view<
|
||||
Prop_Field<&Spectrum::Prop::center_frequency, "center_frequency">,
|
||||
Prop_Field<&Spectrum::Prop::partition_count, "partition_count">,
|
||||
Prop_Field<&Spectrum::Prop::max_hold_visible, "max_hold_visible">,
|
||||
Prop_Field<&Spectrum::Prop::min_hold_visible, "min_hold_visible">,
|
||||
Prop_Field<&Spectrum::Prop::max_marker_visible, "max_marker_visible">,
|
||||
Prop_Field<&Spectrum::Prop::min_marker_visible, "min_marker_visible">,
|
||||
Prop_Field<&Spectrum::Prop::sweep_region_visible, "sweep_region_visible">,
|
||||
Prop_Field<&Spectrum::Prop::visible_range_only, "visible_range_only">,
|
||||
Prop_Field<&Spectrum::Prop::frequency_range, "frequency_range">,
|
||||
Prop_Field<&Spectrum::Prop::sweep_frequency_range, "sweep_frequency_range">,
|
||||
Prop_Field<&Spectrum::Prop::partition_mode, "partition_mode">,
|
||||
Prop_Field<&Spectrum::Prop::interpolation_mode, "interpolation_mode">,
|
||||
Prop_Field<&Spectrum::Prop::max_brush, "max_brush">,
|
||||
Prop_Field<&Spectrum::Prop::current_brush, "current_brush">,
|
||||
Prop_Field<&Spectrum::Prop::min_brush, "min_brush">,
|
||||
Prop_Field<&Spectrum::Prop::max_pen, "max_pen">,
|
||||
Prop_Field<&Spectrum::Prop::current_pen, "current_pen">,
|
||||
Prop_Field<&Spectrum::Prop::min_pen, "min_pen">,
|
||||
Prop_Field<&Spectrum::Prop::selected_marker_pen, "selected_marker_pen">,
|
||||
Prop_Field<&Spectrum::Prop::marker_pen, "marker_pen">,
|
||||
Prop_Field<&Spectrum::Prop::middle_frequency_pen, "middle_frequency_pen">,
|
||||
Prop_Field<&Spectrum::Prop::sweep_region_brush, "sweep_region_brush">,
|
||||
Prop_Field<&Spectrum::Prop::custom_markers, "custom_markers">,
|
||||
Prop_Field<&Spectrum::Prop::selected_marker, "selected_marker">,
|
||||
State_Field<Spectrum, &Spectrum::State::sample_count, "sample_count">,
|
||||
State_Field<Spectrum, &Spectrum::State::rendered_point_count, "rendered_point_count">,
|
||||
State_Field<Spectrum, &Spectrum::State::selectable_marker_count, "selectable_marker_count">>(
|
||||
*object, std::move(owners), std::move(update));
|
||||
Prop_Field<&Spectrum::Prop::center_frequency, "center_frequency", "Frequency placed at the visual center of the spectrum axis.">,
|
||||
Prop_Field<&Spectrum::Prop::partition_count, "partition_count", "Number of partitions used to prepare and render spectrum samples.">,
|
||||
Prop_Field<&Spectrum::Prop::max_hold_visible, "max_hold_visible", "Shows the accumulated maximum-hold spectrum curve when enabled.">,
|
||||
Prop_Field<&Spectrum::Prop::min_hold_visible, "min_hold_visible", "Shows the accumulated minimum-hold spectrum curve when enabled.">,
|
||||
Prop_Field<&Spectrum::Prop::max_marker_visible, "max_marker_visible", "Displays the marker attached to the strongest visible sample.">,
|
||||
Prop_Field<&Spectrum::Prop::min_marker_visible, "min_marker_visible", "Displays the marker attached to the weakest visible sample.">,
|
||||
Prop_Field<&Spectrum::Prop::sweep_region_visible, "sweep_region_visible", "Highlights the configured sweep-frequency interval on the plot.">,
|
||||
Prop_Field<&Spectrum::Prop::visible_range_only, "visible_range_only", "Restricts sample preparation to the frequency range currently visible on the axis.">,
|
||||
Prop_Field<&Spectrum::Prop::frequency_range, "frequency_range", "Maps the complete input sample span onto frequency coordinates.">,
|
||||
Prop_Field<&Spectrum::Prop::sweep_frequency_range, "sweep_frequency_range", "Defines the frequency interval rendered as the sweep region.">,
|
||||
Prop_Field<&Spectrum::Prop::partition_mode, "partition_mode", "Selects how samples are divided between preparation tasks.">,
|
||||
Prop_Field<&Spectrum::Prop::interpolation_mode, "interpolation_mode", "Selects the interpolation algorithm used between adjacent spectrum samples.">,
|
||||
Prop_Field<&Spectrum::Prop::max_brush, "max_brush", "Fill brush used for the maximum-hold area.">,
|
||||
Prop_Field<&Spectrum::Prop::current_brush, "current_brush", "Fill brush used for the current spectrum area.">,
|
||||
Prop_Field<&Spectrum::Prop::min_brush, "min_brush", "Fill brush used for the minimum-hold area.">,
|
||||
Prop_Field<&Spectrum::Prop::max_pen, "max_pen", "Stroke style used for the maximum-hold curve.">,
|
||||
Prop_Field<&Spectrum::Prop::current_pen, "current_pen", "Stroke style used for the current spectrum curve.">,
|
||||
Prop_Field<&Spectrum::Prop::min_pen, "min_pen", "Stroke style used for the minimum-hold curve.">,
|
||||
Prop_Field<&Spectrum::Prop::selected_marker_pen, "selected_marker_pen", "Stroke style used to emphasize the currently selected marker.">,
|
||||
Prop_Field<&Spectrum::Prop::marker_pen, "marker_pen", "Default stroke style used for unselected spectrum markers.">,
|
||||
Prop_Field<&Spectrum::Prop::middle_frequency_pen, "middle_frequency_pen", "Stroke style used for the center-frequency indicator.">,
|
||||
Prop_Field<&Spectrum::Prop::sweep_region_brush, "sweep_region_brush", "Fill brush used to highlight the sweep-frequency interval.">,
|
||||
Prop_Field<&Spectrum::Prop::custom_markers, "custom_markers", "User-defined marker positions and presentation data.">,
|
||||
Prop_Field<&Spectrum::Prop::selected_marker, "selected_marker", "Index of the custom marker currently selected for interaction.">,
|
||||
State_Field<Spectrum, &Spectrum::State::sample_count, "sample_count", "Number of input spectrum samples available in the latest update.">,
|
||||
State_Field<Spectrum, &Spectrum::State::rendered_point_count, "rendered_point_count", "Number of curve points emitted by the latest render preparation.">,
|
||||
State_Field<Spectrum, &Spectrum::State::selectable_marker_count, "selectable_marker_count", "Number of markers currently eligible for selection.">>(
|
||||
*spectrum, std::move(update), std::move(frequency), std::move(vertical), std::move(spectrum));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
std::shared_ptr<Plot> make_frequency_trace_plot(asio::any_io_executor executor) {
|
||||
constexpr Size canvas{720, 420};
|
||||
auto scene = make_scene_2d();
|
||||
auto time = finish_shared<Time_Axis_Object>(time_axis_builder(
|
||||
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas));
|
||||
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-1.2, 1.2}, canvas));
|
||||
Impl<Frequency_Trace>::Builder builder(scene.get(), time.get(), vertical.get());
|
||||
auto object = finish_shared<Impl<Frequency_Trace>>(std::move(builder));
|
||||
object->mark_dirty<Prepare_Data_Tag>();
|
||||
object->mark_dirty<Paint_Tag>();
|
||||
auto tick = std::make_shared<std::uint64_t>();
|
||||
auto update = [raw = object.get(), time = time.get(), vertical = vertical.get(), tick](const Plot_Event& event) {
|
||||
auto scene = *Scene_2D::Builder{}
|
||||
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
||||
.set(&Render_Scene_2D::Prop::view_active, true)
|
||||
.build();
|
||||
auto time = *time_axis_builder(
|
||||
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, canvas).build();
|
||||
auto vertical = *numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-1.2, 1.2}, canvas).build();
|
||||
auto trace = *Impl<Frequency_Trace>::Builder(scene.get(), time.get(), vertical.get()).build();
|
||||
trace->mark_dirty<Prepare_Data_Tag>();
|
||||
trace->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = trace.get(), time = time.get(), vertical = vertical.get(), tick = std::uint64_t{}](const Plot_Event& event) mutable {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, time, vertical);
|
||||
raw->append_sample((*tick)++, std::sin(event.time_milliseconds * 0.0025) * 0.8
|
||||
raw->append_sample(tick++, std::sin(event.time_milliseconds * 0.0025) * 0.8
|
||||
+ std::sin(event.time_milliseconds * 0.0007) * 0.2);
|
||||
};
|
||||
std::vector<std::shared_ptr<void>> owners{time, vertical, object, tick};
|
||||
auto view = make_scene_view<
|
||||
Prop_Field<&Frequency_Trace::Prop::partition_count, "partition_count">,
|
||||
Prop_Field<&Frequency_Trace::Prop::pen, "pen">,
|
||||
Prop_Field<&Frequency_Trace::Prop::partition_mode, "partition_mode">,
|
||||
Prop_Field<&Frequency_Trace::Prop::samples, "samples">,
|
||||
State_Field<Frequency_Trace, &Frequency_Trace::State::sample_count, "sample_count">,
|
||||
State_Field<Frequency_Trace, &Frequency_Trace::State::rendered_point_count, "rendered_point_count">>(
|
||||
*object, std::move(owners), std::move(update));
|
||||
Prop_Field<&Frequency_Trace::Prop::partition_count, "partition_count", "Number of partitions used to prepare the time-ordered trace.">,
|
||||
Prop_Field<&Frequency_Trace::Prop::pen, "pen", "Stroke style used to draw the frequency trace.">,
|
||||
Prop_Field<&Frequency_Trace::Prop::partition_mode, "partition_mode", "Selects how trace samples are divided between preparation tasks.">,
|
||||
Prop_Field<&Frequency_Trace::Prop::samples, "samples", "Complete time-ordered collection of frequency trace samples.">,
|
||||
State_Field<Frequency_Trace, &Frequency_Trace::State::sample_count, "sample_count", "Number of samples retained by the current trace.">,
|
||||
State_Field<Frequency_Trace, &Frequency_Trace::State::rendered_point_count, "rendered_point_count", "Number of points emitted for the latest trace frame.">>(
|
||||
*trace, std::move(update), std::move(time), std::move(vertical), std::move(trace));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
std::shared_ptr<Plot> make_sweep_spectrum_plot(asio::any_io_executor executor) {
|
||||
constexpr Size canvas{720, 420};
|
||||
auto scene = make_scene_2d();
|
||||
auto frequency = finish_shared<Frequency_Axis_Object>(frequency_axis_builder(canvas));
|
||||
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas));
|
||||
Impl<Sweep_Spectrum>::Builder builder(scene.get(), frequency.get(), vertical.get());
|
||||
builder.set(&Sweep_Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0});
|
||||
auto object = finish_shared<Impl<Sweep_Spectrum>>(std::move(builder));
|
||||
object->mark_dirty<Prepare_Data_Tag>();
|
||||
object->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = object.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
auto scene = *Scene_2D::Builder{}
|
||||
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
||||
.set(&Render_Scene_2D::Prop::view_active, true)
|
||||
.build();
|
||||
auto frequency = *frequency_axis_builder(canvas).build();
|
||||
auto vertical = *numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas).build();
|
||||
auto sweep = *Impl<Sweep_Spectrum>::Builder(scene.get(), frequency.get(), vertical.get())
|
||||
.set(&Sweep_Spectrum::Prop::frequency_range, Axis_Range{0.0, 100.0})
|
||||
.build();
|
||||
sweep->mark_dirty<Prepare_Data_Tag>();
|
||||
sweep->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = sweep.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
||||
std::array<double, 64> values{};
|
||||
for (std::size_t i = 0; i < values.size(); ++i)
|
||||
values[i] = -90.0 + 35.0 * std::sin(i * 0.08 + event.time_milliseconds * 0.002);
|
||||
raw->append_block(values);
|
||||
};
|
||||
std::vector<std::shared_ptr<void>> owners{frequency, vertical, object};
|
||||
auto view = make_scene_view<
|
||||
Prop_Field<&Sweep_Spectrum::Prop::bins_per_block, "bins_per_block">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::block_count, "block_count">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::partition_count, "partition_count">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::visible_range_only, "visible_range_only">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::frequency_range, "frequency_range">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::partition_mode, "partition_mode">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::pen, "pen">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::current_frequency_pen, "current_frequency_pen">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::interpolation_mode, "interpolation_mode">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::blocks, "blocks">,
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::stored_block_count, "stored_block_count">,
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::stored_point_count, "stored_point_count">,
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::rendered_point_count, "rendered_point_count">>(
|
||||
*object, std::move(owners), std::move(update));
|
||||
Prop_Field<&Sweep_Spectrum::Prop::bins_per_block, "bins_per_block", "Number of frequency bins stored in each incoming sweep block.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::block_count, "block_count", "Number of blocks required to compose one complete sweep.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::partition_count, "partition_count", "Number of partitions used during sweep preparation.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::visible_range_only, "visible_range_only", "Restricts preparation to the frequency interval visible on the axis.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::frequency_range, "frequency_range", "Maps the complete sweep span onto frequency coordinates.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::partition_mode, "partition_mode", "Selects how sweep blocks are divided between preparation tasks.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::pen, "pen", "Stroke style used for the completed sweep curve.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::current_frequency_pen, "current_frequency_pen", "Stroke style used for the current sweep-frequency indicator.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::interpolation_mode, "interpolation_mode", "Selects interpolation between adjacent sweep bins.">,
|
||||
Prop_Field<&Sweep_Spectrum::Prop::blocks, "blocks", "Current collection of incremental sweep blocks.">,
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::stored_block_count, "stored_block_count", "Number of sweep blocks currently retained.">,
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::stored_point_count, "stored_point_count", "Total number of frequency points retained across all blocks.">,
|
||||
State_Field<Sweep_Spectrum, &Sweep_Spectrum::State::rendered_point_count, "rendered_point_count", "Number of points emitted for the latest sweep frame.">>(
|
||||
*sweep, std::move(update), std::move(frequency), std::move(vertical), std::move(sweep));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
std::shared_ptr<Plot> make_afterglow_plot(asio::any_io_executor executor) {
|
||||
constexpr Size canvas{720, 420};
|
||||
auto scene = make_scene_2d();
|
||||
auto frequency = finish_shared<Frequency_Axis_Object>(frequency_axis_builder(canvas));
|
||||
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas));
|
||||
Impl<Afterglow>::Builder builder(scene.get(), frequency.get(), vertical.get());
|
||||
builder
|
||||
auto scene = *Scene_2D::Builder{}
|
||||
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
||||
.set(&Render_Scene_2D::Prop::view_active, true)
|
||||
.build();
|
||||
auto frequency = *frequency_axis_builder(canvas).build();
|
||||
auto vertical = *numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-110.0, 0.0}, canvas).build();
|
||||
auto afterglow = *Impl<Afterglow>::Builder(scene.get(), frequency.get(), vertical.get())
|
||||
.set(&Afterglow::Prop::frequency_range, Axis_Range{0.0, 100.0})
|
||||
.set(&Afterglow::Prop::power_range, Axis_Range{-110.0, 0.0})
|
||||
.set(&Afterglow::Prop::power_point_size, 96);
|
||||
auto object = finish_shared<Impl<Afterglow>>(std::move(builder));
|
||||
object->mark_dirty<Prepare_Data_Tag>();
|
||||
object->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = object.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
.set(&Afterglow::Prop::power_point_size, 96)
|
||||
.build();
|
||||
afterglow->mark_dirty<Prepare_Data_Tag>();
|
||||
afterglow->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = afterglow.get(), frequency = frequency.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, vertical);
|
||||
std::array<double, 192> values{};
|
||||
for (std::size_t i = 0; i < values.size(); ++i)
|
||||
@@ -342,130 +332,135 @@ std::shared_ptr<Plot> make_afterglow_plot(asio::any_io_executor executor) {
|
||||
- 0.18 * std::sin(event.time_milliseconds * 0.0008), 2.0));
|
||||
raw->append_spectrum(values);
|
||||
};
|
||||
std::vector<std::shared_ptr<void>> owners{frequency, vertical, object};
|
||||
auto view = make_scene_view<
|
||||
Prop_Field<&Afterglow::Prop::frequency_point_size, "frequency_point_size">,
|
||||
Prop_Field<&Afterglow::Prop::power_point_size, "power_point_size">,
|
||||
Prop_Field<&Afterglow::Prop::partition_count, "partition_count">,
|
||||
Prop_Field<&Afterglow::Prop::interpolate, "interpolate">,
|
||||
Prop_Field<&Afterglow::Prop::attenuation_rate, "attenuation_rate">,
|
||||
Prop_Field<&Afterglow::Prop::frequency_range, "frequency_range">,
|
||||
Prop_Field<&Afterglow::Prop::power_range, "power_range">,
|
||||
Prop_Field<&Afterglow::Prop::partition_mode, "partition_mode">,
|
||||
Prop_Field<&Afterglow::Prop::color_map, "color_map">,
|
||||
Prop_Field<&Afterglow::Prop::spectra, "spectra">,
|
||||
State_Field<Afterglow, &Afterglow::State::history_count, "history_count">,
|
||||
State_Field<Afterglow, &Afterglow::State::latest_spectrum_point_count, "latest_spectrum_point_count">,
|
||||
State_Field<Afterglow, &Afterglow::State::rendered_cell_count, "rendered_cell_count">>(
|
||||
*object, std::move(owners), std::move(update));
|
||||
Prop_Field<&Afterglow::Prop::frequency_point_size, "frequency_point_size", "Number of frequency cells allocated across each afterglow row.">,
|
||||
Prop_Field<&Afterglow::Prop::power_point_size, "power_point_size", "Number of power cells allocated along the vertical afterglow range.">,
|
||||
Prop_Field<&Afterglow::Prop::partition_count, "partition_count", "Number of partitions used to prepare afterglow history.">,
|
||||
Prop_Field<&Afterglow::Prop::interpolate, "interpolate", "Enables interpolation when mapping samples into the afterglow grid.">,
|
||||
Prop_Field<&Afterglow::Prop::attenuation_rate, "attenuation_rate", "Controls how quickly historical energy fades between updates.">,
|
||||
Prop_Field<&Afterglow::Prop::frequency_range, "frequency_range", "Maps input samples onto the afterglow frequency axis.">,
|
||||
Prop_Field<&Afterglow::Prop::power_range, "power_range", "Defines the minimum and maximum power represented by the color grid.">,
|
||||
Prop_Field<&Afterglow::Prop::partition_mode, "partition_mode", "Selects how afterglow cells are divided between preparation tasks.">,
|
||||
Prop_Field<&Afterglow::Prop::color_map, "color_map", "Maps accumulated energy values to rendered colors.">,
|
||||
Prop_Field<&Afterglow::Prop::spectra, "spectra", "Spectrum history currently retained for afterglow rendering.">,
|
||||
State_Field<Afterglow, &Afterglow::State::history_count, "history_count", "Number of spectrum frames retained in afterglow history.">,
|
||||
State_Field<Afterglow, &Afterglow::State::latest_spectrum_point_count, "latest_spectrum_point_count", "Number of samples in the most recently appended spectrum.">,
|
||||
State_Field<Afterglow, &Afterglow::State::rendered_cell_count, "rendered_cell_count", "Number of colored cells emitted for the latest frame.">>(
|
||||
*afterglow, std::move(update), std::move(frequency), std::move(vertical), std::move(afterglow));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
std::shared_ptr<Plot> make_waterfall_plot(asio::any_io_executor executor) {
|
||||
constexpr Size canvas{720, 420};
|
||||
auto scene = make_scene_2d();
|
||||
auto frequency = finish_shared<Frequency_Axis_Object>(frequency_axis_builder(canvas));
|
||||
auto time = finish_shared<Time_Axis_Object>(time_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas));
|
||||
Impl<Waterfall>::Builder builder(scene.get(), frequency.get(), time.get());
|
||||
builder
|
||||
auto scene = *Scene_2D::Builder{}
|
||||
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
||||
.set(&Render_Scene_2D::Prop::view_active, true)
|
||||
.build();
|
||||
auto frequency = *frequency_axis_builder(canvas).build();
|
||||
auto time = *time_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, canvas).build();
|
||||
auto waterfall = *Impl<Waterfall>::Builder(scene.get(), frequency.get(), time.get())
|
||||
.set(&Waterfall::Prop::frequency_range, Axis_Range{0.0, 100.0})
|
||||
.set(&Waterfall::Prop::power_range, Axis_Range{-110.0, 0.0});
|
||||
auto object = finish_shared<Impl<Waterfall>>(std::move(builder));
|
||||
object->mark_dirty<Prepare_Data_Tag>();
|
||||
object->mark_dirty<Paint_Tag>();
|
||||
auto tick = std::make_shared<std::uint64_t>();
|
||||
auto update = [raw = object.get(), frequency = frequency.get(), time = time.get(), tick](const Plot_Event& event) {
|
||||
.set(&Waterfall::Prop::power_range, Axis_Range{-110.0, 0.0})
|
||||
.build();
|
||||
waterfall->mark_dirty<Prepare_Data_Tag>();
|
||||
waterfall->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = waterfall.get(), frequency = frequency.get(), time = time.get(), tick = std::uint64_t{}](const Plot_Event& event) mutable {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, frequency, time);
|
||||
std::array<double, 192> values{};
|
||||
for (std::size_t i = 0; i < values.size(); ++i)
|
||||
values[i] = -100.0 + 70.0 * std::exp(-240.0 * std::pow(
|
||||
static_cast<double>(i) / values.size() - 0.5
|
||||
- 0.22 * std::sin(event.time_milliseconds * 0.0006), 2.0));
|
||||
raw->append_row((*tick)++, values);
|
||||
raw->append_row(tick++, values);
|
||||
};
|
||||
std::vector<std::shared_ptr<void>> owners{frequency, time, object, tick};
|
||||
auto view = make_scene_view<
|
||||
Prop_Field<&Waterfall::Prop::tooltip_enabled, "tooltip_enabled">,
|
||||
Prop_Field<&Waterfall::Prop::tooltip_font, "tooltip_font">,
|
||||
Prop_Field<&Waterfall::Prop::tooltip_text_pen, "tooltip_text_pen">,
|
||||
Prop_Field<&Waterfall::Prop::tooltip_background_brush, "tooltip_background_brush">,
|
||||
Prop_Field<&Waterfall::Prop::frequency_bin_count, "frequency_bin_count">,
|
||||
Prop_Field<&Waterfall::Prop::partition_count, "partition_count">,
|
||||
Prop_Field<&Waterfall::Prop::visible_range_only, "visible_range_only">,
|
||||
Prop_Field<&Waterfall::Prop::frequency_range, "frequency_range">,
|
||||
Prop_Field<&Waterfall::Prop::power_range, "power_range">,
|
||||
Prop_Field<&Waterfall::Prop::partition_mode, "partition_mode">,
|
||||
Prop_Field<&Waterfall::Prop::interpolation_mode, "interpolation_mode">,
|
||||
Prop_Field<&Waterfall::Prop::color_map, "color_map">,
|
||||
Prop_Field<&Waterfall::Prop::rows, "rows">,
|
||||
State_Field<Waterfall, &Waterfall::State::row_count, "row_count">,
|
||||
State_Field<Waterfall, &Waterfall::State::stored_point_count, "stored_point_count">,
|
||||
State_Field<Waterfall, &Waterfall::State::rendered_cell_count, "rendered_cell_count">>(
|
||||
*object, std::move(owners), std::move(update));
|
||||
Prop_Field<&Waterfall::Prop::tooltip_enabled, "tooltip_enabled", "Enables value inspection tooltips over waterfall cells.">,
|
||||
Prop_Field<&Waterfall::Prop::tooltip_font, "tooltip_font", "Font used to render waterfall tooltip text.">,
|
||||
Prop_Field<&Waterfall::Prop::tooltip_text_pen, "tooltip_text_pen", "Pen used to draw tooltip text and its foreground color.">,
|
||||
Prop_Field<&Waterfall::Prop::tooltip_background_brush, "tooltip_background_brush", "Brush used to fill the tooltip background panel.">,
|
||||
Prop_Field<&Waterfall::Prop::frequency_bin_count, "frequency_bin_count", "Number of frequency bins expected in each waterfall row.">,
|
||||
Prop_Field<&Waterfall::Prop::partition_count, "partition_count", "Number of partitions used to prepare waterfall cells.">,
|
||||
Prop_Field<&Waterfall::Prop::visible_range_only, "visible_range_only", "Restricts preparation to frequencies visible on the current axis.">,
|
||||
Prop_Field<&Waterfall::Prop::frequency_range, "frequency_range", "Maps row samples onto waterfall frequency coordinates.">,
|
||||
Prop_Field<&Waterfall::Prop::power_range, "power_range", "Defines the power interval mapped through the waterfall color map.">,
|
||||
Prop_Field<&Waterfall::Prop::partition_mode, "partition_mode", "Selects how waterfall rows are divided between preparation tasks.">,
|
||||
Prop_Field<&Waterfall::Prop::interpolation_mode, "interpolation_mode", "Selects interpolation when samples are mapped to raster cells.">,
|
||||
Prop_Field<&Waterfall::Prop::color_map, "color_map", "Maps sample power values to waterfall colors.">,
|
||||
Prop_Field<&Waterfall::Prop::rows, "rows", "Time-ordered collection of spectrum rows retained by the waterfall.">,
|
||||
State_Field<Waterfall, &Waterfall::State::row_count, "row_count", "Number of waterfall rows currently retained.">,
|
||||
State_Field<Waterfall, &Waterfall::State::stored_point_count, "stored_point_count", "Total number of spectrum points retained across all rows.">,
|
||||
State_Field<Waterfall, &Waterfall::State::rendered_cell_count, "rendered_cell_count", "Number of raster cells emitted for the latest frame.">>(
|
||||
*waterfall, std::move(update), std::move(frequency), std::move(time), std::move(waterfall));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
std::shared_ptr<Plot> make_constellation_plot(asio::any_io_executor executor) {
|
||||
constexpr Size canvas{720, 420};
|
||||
auto scene = make_scene_2d();
|
||||
auto horizontal = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
||||
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, {-1.2, 1.2}, canvas));
|
||||
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-1.2, 1.2}, canvas));
|
||||
Impl<Constellation_Diagram>::Builder builder(scene.get(), horizontal.get(), vertical.get());
|
||||
builder
|
||||
auto scene = *Scene_2D::Builder{}
|
||||
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
||||
.set(&Render_Scene_2D::Prop::view_active, true)
|
||||
.build();
|
||||
auto horizontal = *numeric_axis_builder(
|
||||
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, {-1.2, 1.2}, canvas).build();
|
||||
auto vertical = *numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {-1.2, 1.2}, canvas).build();
|
||||
auto constellation = *Impl<Constellation_Diagram>::Builder(scene.get(), horizontal.get(), vertical.get())
|
||||
.set(&Constellation_Diagram::Prop::i_range, Axis_Range{-1.2, 1.2})
|
||||
.set(&Constellation_Diagram::Prop::q_range, Axis_Range{-1.2, 1.2});
|
||||
auto object = finish_shared<Impl<Constellation_Diagram>>(std::move(builder));
|
||||
object->mark_dirty<Prepare_Data_Tag>();
|
||||
object->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = object.get(), horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
.set(&Constellation_Diagram::Prop::q_range, Axis_Range{-1.2, 1.2})
|
||||
.build();
|
||||
constellation->mark_dirty<Prepare_Data_Tag>();
|
||||
constellation->mark_dirty<Paint_Tag>();
|
||||
auto update = [raw = constellation.get(), horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, horizontal, vertical);
|
||||
const double phase = event.time_milliseconds * 0.003;
|
||||
raw->append_point({std::cos(phase) * 0.82 + 0.04 * std::sin(phase * 7.0),
|
||||
std::sin(phase) * 0.82 + 0.04 * std::cos(phase * 5.0)});
|
||||
};
|
||||
std::vector<std::shared_ptr<void>> owners{horizontal, vertical, object};
|
||||
auto view = make_scene_view<
|
||||
Prop_Field<&Constellation_Diagram::Prop::point_lifetime_ms, "point_lifetime_ms">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::type, "type">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::phase_offset_radians, "phase_offset_radians">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::i_range, "i_range">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::q_range, "q_range">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::point_color, "point_color">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::anchor_color, "anchor_color">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::points, "points">,
|
||||
State_Field<Constellation_Diagram, &Constellation_Diagram::State::point_count, "point_count">>(
|
||||
*object, std::move(owners), std::move(update));
|
||||
Prop_Field<&Constellation_Diagram::Prop::point_lifetime_ms, "point_lifetime_ms", "Time in milliseconds that an appended constellation point remains visible.">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::type, "type", "Selects the modulation constellation used to generate reference anchors.">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::phase_offset_radians, "phase_offset_radians", "Rotates constellation points and anchors by the specified phase angle.">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::i_range, "i_range", "Defines the horizontal in-phase coordinate interval.">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::q_range, "q_range", "Defines the vertical quadrature coordinate interval.">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::point_color, "point_color", "Color used to render received I/Q samples.">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::anchor_color, "anchor_color", "Color used to render ideal modulation anchors.">,
|
||||
Prop_Field<&Constellation_Diagram::Prop::points, "points", "Current time-stamped collection of received I/Q samples.">,
|
||||
State_Field<Constellation_Diagram, &Constellation_Diagram::State::point_count, "point_count", "Number of constellation samples currently retained.">>(
|
||||
*constellation, std::move(update), std::move(horizontal), std::move(vertical), std::move(constellation));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
std::shared_ptr<Plot> make_selection_overlay_plot(asio::any_io_executor executor) {
|
||||
constexpr Size canvas{720, 420};
|
||||
auto scene = make_scene_2d();
|
||||
auto horizontal = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
||||
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, {0.0, 100.0}, canvas));
|
||||
auto vertical = finish_shared<Numeric_Axis_Object>(numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {0.0, 100.0}, canvas));
|
||||
Impl<Selection_Rectangle_Overlay>::Builder builder(scene.get(), horizontal.get(), vertical.get());
|
||||
auto object = finish_shared<Impl<Selection_Rectangle_Overlay>>(std::move(builder));
|
||||
object->mark_dirty<Prepare_Data_Tag>();
|
||||
object->mark_dirty<Paint_Tag>();
|
||||
auto scene = *Scene_2D::Builder{}
|
||||
.set(&Render_Scene_2D::Prop::viewport, canvas)
|
||||
.set(&Render_Scene_2D::Prop::background, Color{7, 13, 24, 255})
|
||||
.set(&Render_Scene_2D::Prop::view_active, true)
|
||||
.build();
|
||||
auto horizontal = *numeric_axis_builder(
|
||||
Axis_Orientation::horizontal, {64.0, 370.0}, 620.0, {0.0, 100.0}, canvas).build();
|
||||
auto vertical = *numeric_axis_builder(
|
||||
Axis_Orientation::vertical, {64.0, 370.0}, -320.0, {0.0, 100.0}, canvas).build();
|
||||
auto selection = *Impl<Selection_Rectangle_Overlay>::Builder(scene.get(), horizontal.get(), vertical.get()).build();
|
||||
selection->mark_dirty<Prepare_Data_Tag>();
|
||||
selection->mark_dirty<Paint_Tag>();
|
||||
auto update = [horizontal = horizontal.get(), vertical = vertical.get()](const Plot_Event& event) {
|
||||
resize_axes({static_cast<int>(event.width), static_cast<int>(event.height)}, horizontal, vertical);
|
||||
};
|
||||
std::vector<std::shared_ptr<void>> owners{horizontal, vertical, object};
|
||||
auto view = make_scene_view<
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::label_font, "label_font">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::label_pen, "label_pen">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::selection_brush, "selection_brush">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::selection_border_pen, "selection_border_pen">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::selected_regions, "selected_regions">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::label_font, "label_font", "Font used for labels attached to selected regions.">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::label_pen, "label_pen", "Pen used to draw selected-region label text.">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::selection_brush, "selection_brush", "Brush used to fill selected rectangular regions.">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::selection_border_pen, "selection_border_pen", "Pen used to draw selected-region borders.">,
|
||||
Prop_Field<&Selection_Rectangle_Overlay::Prop::selected_regions, "selected_regions", "Collection of selected rectangles expressed in axis coordinates.">,
|
||||
State_Field<Selection_Rectangle_Overlay,
|
||||
&Selection_Rectangle_Overlay::State::selected_region_count,
|
||||
"selected_region_count">>(
|
||||
*object, std::move(owners), std::move(update));
|
||||
"selected_region_count",
|
||||
"Number of rectangular regions currently selected.">>(
|
||||
*selection, std::move(update), std::move(horizontal), std::move(vertical), std::move(selection));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
|
||||
@@ -597,30 +592,27 @@ void Plot::async_write_prop(std::string key, nlohmann::json value, Json_Handler
|
||||
|
||||
std::shared_ptr<Plot> make_datoviz_point_plot(asio::any_io_executor executor) {
|
||||
using Visual_Object = Impl<Point_Visual>;
|
||||
Visual_Object::Builder visual_builder;
|
||||
auto visual = finish_shared<Visual_Object>(std::move(visual_builder));
|
||||
auto visual = *Visual_Object::Builder{}.build();
|
||||
static_cast<void>(visual->update_items({
|
||||
render_3d::Point{.position = {-0.55F, -0.2F, 0.0F}, .color = Color::red_color(), .diameter_px = 24.0F},
|
||||
render_3d::Point{.position = {0.0F, 0.5F, 0.0F}, .color = Color::green_color(), .diameter_px = 30.0F},
|
||||
render_3d::Point{.position = {0.55F, -0.1F, 0.0F}, .color = Color{42, 120, 255, 255}, .diameter_px = 26.0F}}));
|
||||
visual->advance();
|
||||
Scene_3D::Builder scene_builder(visual.get());
|
||||
scene_builder
|
||||
auto scene = *Scene_3D::Builder(visual.get())
|
||||
.set(&Render_Scene_3D::Prop::viewport, Extent{720, 420})
|
||||
.set(&Render_Scene_3D::Prop::view_active, true);
|
||||
auto scene = finish_build(std::move(scene_builder));
|
||||
.set(&Render_Scene_3D::Prop::view_active, true)
|
||||
.build();
|
||||
using Adapter = detail::Renderable_Adapter<Visual_Object,
|
||||
detail::Prop_Field<&Point_Visual::Prop::transform, "transform">,
|
||||
detail::Prop_Field<&Point_Visual::Prop::visible, "visible">,
|
||||
detail::Prop_Field<&Point_Visual::Prop::depth_test, "depth_test">,
|
||||
detail::Prop_Field<&Point_Visual::Prop::items, "items">,
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::item_count, "item_count">,
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::prepared_item_count, "prepared_item_count">,
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::prepared_revision, "prepared_revision">>;
|
||||
std::vector<std::shared_ptr<void>> owners{visual};
|
||||
auto view = std::make_unique<Scene_View_Model>(
|
||||
std::move(owners), detail::make_renderable_descriptor(Adapter{*visual}),
|
||||
[](const Plot_Event&) {});
|
||||
detail::Prop_Field<&Point_Visual::Prop::transform, "transform", "World transform applied to every point in the visual.">,
|
||||
detail::Prop_Field<&Point_Visual::Prop::visible, "visible", "Controls whether the point visual participates in scene rendering.">,
|
||||
detail::Prop_Field<&Point_Visual::Prop::depth_test, "depth_test", "Enables depth testing when point fragments are rendered.">,
|
||||
detail::Prop_Field<&Point_Visual::Prop::items, "items", "Complete collection of 3D points submitted to the visual.">,
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::item_count, "item_count", "Number of point items currently published by the visual.">,
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::prepared_item_count, "prepared_item_count", "Number of point items prepared for the latest GPU submission.">,
|
||||
detail::State_Field<Point_Visual::Base_Tag, &Point_Visual::State::prepared_revision, "prepared_revision", "Property revision represented by the currently prepared GPU data.">>;
|
||||
auto view = std::make_unique<Scene_View_Model<decltype(visual)>>(
|
||||
detail::make_renderable_descriptor(Adapter{*visual}),
|
||||
[](const Plot_Event&) {}, std::move(visual));
|
||||
return std::make_shared<Plot>(std::move(executor), std::move(scene), std::move(view));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@ enum class Renderable_Field_Role {
|
||||
template <Renderable_Field_Role Role>
|
||||
struct Renderable_Field_Role_Attribute;
|
||||
|
||||
template <auto Member, structive::Fixed_String Key>
|
||||
template <auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
struct Prop_Field;
|
||||
|
||||
template <typename Tag, auto Member, structive::Fixed_String Key>
|
||||
template <typename Tag, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
struct State_Field;
|
||||
|
||||
template <typename Object, typename... Fields>
|
||||
|
||||
@@ -89,18 +89,20 @@ struct Renderable_Field_Role_Attribute {
|
||||
template <Renderable_Field_Role Role>
|
||||
inline constexpr Renderable_Field_Role_Attribute<Role> renderable_field_role{};
|
||||
|
||||
template <auto Member, structive::Fixed_String Key>
|
||||
template <auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
struct Prop_Field {
|
||||
static constexpr auto member = Member;
|
||||
static constexpr auto key = Key;
|
||||
static constexpr auto description = Description;
|
||||
static constexpr auto role = Renderable_Field_Role::prop;
|
||||
};
|
||||
|
||||
template <typename Tag, auto Member, structive::Fixed_String Key>
|
||||
template <typename Tag, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
struct State_Field {
|
||||
using tag_type = Tag;
|
||||
static constexpr auto member = Member;
|
||||
static constexpr auto key = Key;
|
||||
static constexpr auto description = Description;
|
||||
static constexpr auto role = Renderable_Field_Role::state;
|
||||
};
|
||||
|
||||
@@ -128,12 +130,13 @@ private:
|
||||
template <typename Adapter, typename Field>
|
||||
struct Renderable_Field_Accessor;
|
||||
|
||||
template <typename Object, typename... Fields, auto Member, structive::Fixed_String Key>
|
||||
struct Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Field<Member, Key>> {
|
||||
template <typename Object, typename... Fields, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
struct Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Field<Member, Key, Description>> {
|
||||
using object_type = Renderable_Adapter<Object, Fields...>;
|
||||
using value_type = typename structive::Member_Pointer_Traits<decltype(Member)>::value_type;
|
||||
using storage_identity = void;
|
||||
using dependency_spec = structive::No_Property_Dependencies;
|
||||
static constexpr auto description = Description;
|
||||
static constexpr bool readable = true;
|
||||
static constexpr bool writable = true;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
@@ -142,12 +145,13 @@ struct Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Fie
|
||||
void write(object_type& adapter, value_type value) const;
|
||||
};
|
||||
|
||||
template <typename Object, typename... Fields, typename Tag, auto Member, structive::Fixed_String Key>
|
||||
struct Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, State_Field<Tag, Member, Key>> {
|
||||
template <typename Object, typename... Fields, typename Tag, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
struct Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, State_Field<Tag, Member, Key, Description>> {
|
||||
using object_type = Renderable_Adapter<Object, Fields...>;
|
||||
using value_type = typename structive::Member_Pointer_Traits<decltype(Member)>::value_type;
|
||||
using storage_identity = void;
|
||||
using dependency_spec = structive::No_Property_Dependencies;
|
||||
static constexpr auto description = Description;
|
||||
static constexpr bool readable = true;
|
||||
static constexpr bool writable = false;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
@@ -170,18 +174,18 @@ namespace aethera::web::detail {
|
||||
template <typename Object, typename... Fields>
|
||||
Renderable_Adapter<Object, Fields...>::Renderable_Adapter(Object& value) : object(&value) {}
|
||||
|
||||
template <typename Object, typename... Fields, auto Member, structive::Fixed_String Key>
|
||||
auto Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Field<Member, Key>>::read(const object_type& adapter) const -> value_type {
|
||||
template <typename Object, typename... Fields, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
auto Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Field<Member, Key, Description>>::read(const object_type& adapter) const -> value_type {
|
||||
return adapter.object->template get<Member>();
|
||||
}
|
||||
|
||||
template <typename Object, typename... Fields, auto Member, structive::Fixed_String Key>
|
||||
void Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Field<Member, Key>>::write(object_type& adapter, value_type value) const {
|
||||
template <typename Object, typename... Fields, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
void Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, Prop_Field<Member, Key, Description>>::write(object_type& adapter, value_type value) const {
|
||||
adapter.object->template set<Member>(std::move(value));
|
||||
}
|
||||
|
||||
template <typename Object, typename... Fields, typename Tag, auto Member, structive::Fixed_String Key>
|
||||
auto Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, State_Field<Tag, Member, Key>>::read(const object_type& adapter) const -> value_type {
|
||||
template <typename Object, typename... Fields, typename Tag, auto Member, structive::Fixed_String Key, structive::Fixed_String Description>
|
||||
auto Renderable_Field_Accessor<Renderable_Adapter<Object, Fields...>, State_Field<Tag, Member, Key, Description>>::read(const object_type& adapter) const -> value_type {
|
||||
value_type result{};
|
||||
adapter.object->template access_state<Tag>([&](const auto& state) { result = state.*Member; });
|
||||
return result;
|
||||
@@ -225,7 +229,8 @@ template <typename Adapter>
|
||||
using Field = std::remove_cvref_t<decltype(field)>;
|
||||
using Value = typename Field::value_type;
|
||||
const auto role = field.template attribute<Renderable_Field_Role_Category>().value;
|
||||
nlohmann::json item{{"key", field.key()}, {"type", json_value_type<Value>()}};
|
||||
nlohmann::json item{{"key", field.key()}, {"type", json_value_type<Value>()},
|
||||
{"description", std::string(Field::accessor_type::description.view())}};
|
||||
const auto value = field.accessor.read(adapter);
|
||||
item["value"] = adminive::encode_json_value<nlohmann::json>(value);
|
||||
if constexpr (std::is_enum_v<Value>) {
|
||||
|
||||
@@ -10,7 +10,7 @@ type Plot = {
|
||||
schema: string;
|
||||
};
|
||||
type Option = {value: string; label: string};
|
||||
type Field = {key: string; type: "boolean" | "integer" | "number" | "select" | "array" | "object"; value: unknown; options?: Option[]};
|
||||
type Field = {key: string; description: string; type: "boolean" | "integer" | "number" | "select" | "array" | "object"; value: unknown; options?: Option[]};
|
||||
type Schema = {prop: Field[]; state: Field[]};
|
||||
|
||||
const protocolHeaderSize = 24;
|
||||
@@ -65,14 +65,14 @@ function JsonControl({field, onChange}: {field: Field; onChange: (value: unknown
|
||||
try { onChange(JSON.parse(draft)); setInvalid(false); }
|
||||
catch { setInvalid(true); }
|
||||
};
|
||||
return <label className="control controlJson"><span>{field.key}</span><textarea value={draft} onChange={event => setDraft(event.target.value)} onBlur={apply}/><small className={invalid ? "error" : ""}>{invalid ? "Invalid JSON" : "Changes apply when focus leaves the field."}</small></label>;
|
||||
return <label className="control controlJson" title={field.description}><span>{field.key}</span><textarea value={draft} onChange={event => setDraft(event.target.value)} onBlur={apply}/><small className={invalid ? "error" : ""}>{invalid ? "Invalid JSON" : "Changes apply when focus leaves the field."}</small></label>;
|
||||
}
|
||||
|
||||
function FieldControl({field, onChange}: {field: Field; onChange: (value: unknown) => void}) {
|
||||
if (field.type === "object" || field.type === "array") return <JsonControl field={field} onChange={onChange}/>;
|
||||
if (field.type === "boolean") return <label className="control controlBoolean"><span>{field.key}</span><input type="checkbox" checked={Boolean(field.value)} onChange={event => onChange(event.target.checked)}/></label>;
|
||||
if (field.type === "select") return <label className="control"><span>{field.key}</span><select value={String(field.value ?? "")} onChange={event => onChange(event.target.value)}>{field.options?.map(option => <option key={option.value} value={option.value}>{option.label}</option>)}</select></label>;
|
||||
return <label className="control"><span>{field.key}</span><input type="number" value={String(field.value ?? "")} onChange={event => onChange(Number(event.target.value))}/></label>;
|
||||
if (field.type === "boolean") return <label className="control controlBoolean" title={field.description}><span>{field.key}</span><input type="checkbox" checked={Boolean(field.value)} onChange={event => onChange(event.target.checked)}/></label>;
|
||||
if (field.type === "select") return <label className="control" title={field.description}><span>{field.key}</span><select value={String(field.value ?? "")} onChange={event => onChange(event.target.value)}>{field.options?.map(option => <option key={option.value} value={option.value}>{option.label}</option>)}</select></label>;
|
||||
return <label className="control" title={field.description}><span>{field.key}</span><input type="number" value={String(field.value ?? "")} onChange={event => onChange(Number(event.target.value))}/></label>;
|
||||
}
|
||||
|
||||
function StateValue({value}: {value: unknown}) {
|
||||
@@ -99,7 +99,7 @@ function InspectorSidebar({plot, onClose}: {plot: Plot; onClose: () => void}) {
|
||||
}).then(response => response.json());
|
||||
if (result.success) setSchema(current => current ? {...current, prop: current.prop.map(item => item.key === field.key ? {...item, value: result.value} : item)} : current);
|
||||
};
|
||||
return <><button className="backdrop" aria-label="Close inspector" onClick={onClose}/><aside className="sidebar" aria-label={`${plot.title} inspector`}><header><div><span className="eyebrow">{plot.category} · {plot.dimension}</span><h2>{plot.title}</h2></div><button className="close" onClick={onClose} aria-label="Close">×</button></header><div className="tabs"><button className={tab === "prop" ? "active" : ""} onClick={() => setTab("prop")}>Editable Prop <span>{schema?.prop.length ?? 0}</span></button><button className={tab === "state" ? "active" : ""} onClick={() => setTab("state")}>Published State <span>{schema?.state.length ?? 0}</span></button></div><div className="sidebarBody">{busy && !schema ? <p className="muted">Loading schema…</p> : tab === "prop" ? <section className="propGrid">{schema?.prop.map(field => <FieldControl key={field.key} field={field} onChange={value => void update(field, value)}/>)}</section> : <section><div className="stateHeading"><p>Current published State</p><button onClick={() => void loadSchema()}>Refresh</button></div><dl className="stateList">{schema?.state.map(field => <div key={field.key}><dt>{field.key}</dt><dd><StateValue value={field.value}/></dd></div>)}</dl></section>}</div></aside></>;
|
||||
return <><button className="backdrop" aria-label="Close inspector" onClick={onClose}/><aside className="sidebar" aria-label={`${plot.title} inspector`}><header><div><span className="eyebrow">{plot.category} · {plot.dimension}</span><h2>{plot.title}</h2></div><button className="close" onClick={onClose} aria-label="Close">×</button></header><div className="tabs"><button className={tab === "prop" ? "active" : ""} onClick={() => setTab("prop")}>Editable Prop <span>{schema?.prop.length ?? 0}</span></button><button className={tab === "state" ? "active" : ""} onClick={() => setTab("state")}>Published State <span>{schema?.state.length ?? 0}</span></button></div><div className="sidebarBody">{busy && !schema ? <p className="muted">Loading schema…</p> : tab === "prop" ? <section className="propGrid">{schema?.prop.map(field => <FieldControl key={field.key} field={field} onChange={value => void update(field, value)}/>)}</section> : <section><div className="stateHeading"><p>Current published State</p><button onClick={() => void loadSchema()}>Refresh</button></div><dl className="stateList">{schema?.state.map(field => <div key={field.key} title={field.description}><dt>{field.key}</dt><dd><StateValue value={field.value}/></dd></div>)}</dl></section>}</div></aside></>;
|
||||
}
|
||||
|
||||
const PlotCard = memo(function PlotCard({plot, onInspect}: {plot: Plot; onInspect: (plot: Plot) => void}) {
|
||||
|
||||
Reference in New Issue
Block a user