#pragma once #include "Gallery_Actions.h" #include "Gallery_Renderable_Types.h" #include "adminive/adminive.hpp" #include "adminive/adapters/magic_enum.hpp" #include "adminive/adapters/nlohmann_json.hpp" #include #include #include #include #include #include #include #include #include namespace renderive::web::gallery_adminive { enum class Number_Locale_Choice { Point, Comma }; enum class Color_Map_Choice { Spectrum, Ember, Grayscale }; inline Color blend(Color first, Color second, double amount) { const auto channel = [amount](std::uint8_t a, std::uint8_t b) { return static_cast(std::clamp( std::lround(a + (b - a) * amount), 0L, 255L)); }; return {channel(first.r, second.r), channel(first.g, second.g), channel(first.b, second.b), channel(first.a, second.a)}; } inline Color_Map color_map(Color_Map_Choice palette) { std::array stops{ Color{5, 10, 25, 255}, Color{20, 52, 112, 255}, Color{39, 196, 181, 255}, Color{242, 201, 76, 255}, Color{255, 76, 106, 255} }; if (palette == Color_Map_Choice::Ember) { stops = {Color{10, 5, 8, 255}, Color{62, 18, 30, 255}, Color{153, 47, 39, 255}, Color{244, 132, 48, 255}, Color{255, 238, 166, 255}}; } else if (palette == Color_Map_Choice::Grayscale) { stops = {Color{0, 0, 0, 255}, Color{52, 52, 52, 255}, Color{112, 112, 112, 255}, Color{188, 188, 188, 255}, Color{255, 255, 255, 255}}; } std::vector colors; colors.reserve(256); for (int index = 0; index < 256; ++index) { const double position = index / 255.0 * (stops.size() - 1); const auto first = static_cast(std::floor(position)); const auto second = std::min(first + 1, stops.size() - 1); colors.push_back(premultiply(blend(stops[first], stops[second], position - first))); } return Color_Map(std::move(colors)); } inline Color_Map_Choice color_map_choice(const Color_Map& value) { for (const Color_Map_Choice choice : magic_enum::enum_values()) { if (value.colors() == color_map(choice).colors()) return choice; } return Color_Map_Choice::Spectrum; } template auto editable(std::string name, std::string label, adminive::Field_Control control) { return adminive::field(std::move(name), std::move(label)) .editable().control(control); } template auto number(std::string name, std::string label) { return editable(std::move(name), std::move(label), adminive::Field_Control::number); } template auto boolean(std::string name, std::string label) { return editable(std::move(name), std::move(label), adminive::Field_Control::boolean); } template auto select(std::string name, std::string label) { return editable(std::move(name), std::move(label), adminive::Field_Control::select); } template auto color(std::string name, std::string label) { return editable(std::move(name), std::move(label), adminive::Field_Control::color); } template auto property(std::string name, std::string label, adminive::Field_Control control) { return adminive::field(std::move(name), std::move(label), Gallery_Property_Accessor{}) .editable() .unsynchronized() .control(control); } template auto readonly_property(std::string name, std::string label) { return adminive::field(std::move(name), std::move(label), Gallery_Property_Accessor{}); } template auto property_number(std::string name, std::string label) { return property(std::move(name), std::move(label), adminive::Field_Control::number); } template auto property_boolean(std::string name, std::string label) { return property(std::move(name), std::move(label), adminive::Field_Control::boolean); } template auto property_select(std::string name, std::string label) { return property(std::move(name), std::move(label), adminive::Field_Control::select); } template auto property_color(std::string name, std::string label) { return property(std::move(name), std::move(label), adminive::Field_Control::color); } template auto property_object(std::string name, std::string label) { return property(std::move(name), std::move(label), adminive::Field_Control::automatic); } template auto property_text(std::string name, std::string label) { return property(std::move(name), std::move(label), adminive::Field_Control::text); } } // namespace renderive::web::gallery_adminive namespace adminive { template struct Renderive_Validator_Metadata {}; template struct Renderive_Validator_Metadata<::Range_Validator> { static constexpr T minimum = Minimum; static constexpr T maximum = Maximum; }; template struct Value_Adapter<::Validated_Value, Json> : Renderive_Validator_Metadata { using Storage = ::Validated_Value; using value_type = T; static const T& read(const Storage& value) noexcept { return value.get(); } static void write(Storage& target, T value) { target = std::move(value); } }; template struct Value_Adapter { using value_type = std::string; static std::string read(renderive::Color value) { char result[8]{}; std::snprintf(result, sizeof(result), "#%02x%02x%02x", value.r, value.g, value.b); return result; } static void write(renderive::Color& target, const std::string& value) { if (value.size() != 7 || value.front() != '#' || !std::all_of(value.begin() + 1, value.end(), [](unsigned char character) { return std::isxdigit(character) != 0; })) throw std::invalid_argument("color must use #RRGGBB"); const auto channel = [&value](std::size_t offset) { return static_cast(std::stoul(value.substr(offset, 2), nullptr, 16)); }; target.r = channel(1); target.g = channel(3); target.b = channel(5); } }; template struct Value_Adapter { using Choice = renderive::web::gallery_adminive::Number_Locale_Choice; using value_type = Choice; static Choice read(renderive::Number_Locale value) { return value.decimal_point == ',' ? Choice::Comma : Choice::Point; } static void write(renderive::Number_Locale& target, Choice value) { target.decimal_point = value == Choice::Comma ? ',' : '.'; } }; template struct Value_Adapter, Json> { using Storage = renderive::Clamped_Property; using value_type = T; static constexpr T minimum = Minimum; static constexpr T maximum = Maximum; static T read(const Storage& value) noexcept { return value.get(); } static void write(Storage& target, T value) { target = value; } }; template struct Value_Adapter { using value_type = double; static constexpr double minimum = 0.0; static constexpr double maximum = 1.0; static constexpr double multiple_of = 0.01; static double read(const renderive::Unit_Interval& value) noexcept { return value.get(); } static void write(renderive::Unit_Interval& target, double value) { target = value; } }; template struct Value_Adapter { using Choice = renderive::web::gallery_adminive::Color_Map_Choice; using value_type = Choice; static Choice read(const renderive::Color_Map& value) { return renderive::web::gallery_adminive::color_map_choice(value); } static void write(renderive::Color_Map& target, Choice value) { target = renderive::web::gallery_adminive::color_map(value); } }; template <> struct Type_Descriptor { static auto get() { using T = renderive::Range; using namespace renderive::web::gallery_adminive; return adminive::object("range", "范围", number<&T::origin>("origin", "起点"), number<&T::target>("target", "终点")); } }; template <> struct Type_Descriptor { static auto get() { using T = renderive::Pen; using namespace renderive::web::gallery_adminive; return adminive::object("pen", "画笔", color<&T::color>("color", "颜色"), number<&T::width>("width", "宽度"), select<&T::style>("style", "线型") .enum_label("无") .enum_label("实线") .enum_label("虚线") .enum_label("点线"), select<&T::cap>("cap", "端点样式") .enum_label("平头") .enum_label("方头") .enum_label("圆头"), select<&T::join>("join", "连接样式") .enum_label("尖角") .enum_label("斜角") .enum_label("圆角")); } }; template <> struct Type_Descriptor { static auto get() { using T = renderive::Brush; using namespace renderive::web::gallery_adminive; return adminive::object("brush", "画刷", color<&T::color>("color", "颜色"), select<&T::style>("style", "填充样式") .enum_label("无") .enum_label("纯色")); } }; template <> struct Type_Descriptor { static auto get() { using T = renderive::Font; using namespace renderive::web::gallery_adminive; return adminive::object("font", "字体", number<&T::size>("size", "字号"), number<&T::weight>("weight", "字重"), boolean<&T::italic>("italic", "斜体")); } }; #define RENDERIVE_AXIS_DESCRIPTOR(GalleryType, Name, Label) \ template <> struct Type_Descriptor { \ static auto get() { \ using T = renderive::web::GalleryType; \ using B = renderive::Axis_Base_Properties; \ using P = renderive::Axis_Properties; \ using namespace renderive::web::gallery_adminive; \ return adminive::object(Name, Label, \ readonly_property("x", "X 位置"), \ readonly_property("y", "Y 位置"), \ property_select("orientation", "方向") \ .enum_label("水平") \ .enum_label("垂直"), \ readonly_property("pixel_length", "像素长度"), \ property_number("tick_length", "主刻度长度"), \ property_number("sub_tick_length", "次刻度长度"), \ property_color("color", "颜色"), \ property_select("locale", "小数点符号") \ .enum_label("点") \ .enum_label("逗号"), \ property_text("unit_text", "单位文本"), \ property_object("unit_text_font", "单位字体"), \ property_object("unit_text_pen", "单位画笔"), \ property_object("unit_text_background_brush", "单位背景"), \ property_number("label_rotation_degrees", "标签旋转角度"), \ property_object("coordinates", "坐标范围"), \ property_number("precision", "小数位数"), \ property_boolean("wheel", "滚轮缩放"), \ property_boolean("drag", "拖拽平移")); \ } \ } RENDERIVE_AXIS_DESCRIPTOR(Gallery_Axis, "axis", "数值轴"); RENDERIVE_AXIS_DESCRIPTOR(Gallery_Frequency_Axis, "frequency_axis", "频率轴"); #undef RENDERIVE_AXIS_DESCRIPTOR template <> struct Type_Descriptor { static auto get() { using T = renderive::web::Gallery_Time_Axis; using B = renderive::Axis_Base_Properties; using P = renderive::Time_Axis_Properties; using namespace renderive::web::gallery_adminive; return adminive::object("time_axis", "时间轴", readonly_property("x", "X 位置"), readonly_property("y", "Y 位置"), property_select("orientation", "方向") .enum_label("水平") .enum_label("垂直"), readonly_property("pixel_length", "像素长度"), property_number("tick_length", "主刻度长度"), property_number("sub_tick_length", "次刻度长度"), property_color("color", "颜色"), property_select("locale", "小数点符号") .enum_label("点") .enum_label("逗号"), property_text("unit_text", "单位文本"), property_object("unit_text_font", "单位字体"), property_object("unit_text_pen", "单位画笔"), property_object("unit_text_background_brush", "单位背景"), property_number("label_rotation_degrees", "标签旋转角度"), property_number("visible_count", "可见点数"), property_number("tick_label_spacing_px", "标签间距"), property_text("format", "时间格式"), property_boolean("newest_at_start", "最新数据在起点")); } }; #define RENDERIVE_GALLERY_DESCRIPTOR(GalleryType, PropertiesType, Name, Label, ...) \ template <> struct Type_Descriptor { \ static auto get() { \ using T = renderive::web::GalleryType; \ using P = renderive::PropertiesType; \ using namespace renderive::web::gallery_adminive; \ return adminive::object(Name, Label, __VA_ARGS__); \ } \ } RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Spectrum, Spectrum::Properties, "spectrum", "实时频谱", property_number("frequency_point_size", "频率点数"), property_select("partition_mode", "任务分区模式") .enum_label("自动瓶颈优化") .enum_label("固定分区"), property_number("partition_count", "固定分区数") .description("固定模式下的并行任务数;1 表示不分区") .visible_on("${$self.partition_mode == 'Fixed'}"), property_object("frequency_range", "频率范围"), property_number("center_frequency", "中心频率"), property_object("sweep_frequency_range", "扫频范围"), property_boolean("max_hold_visible", "显示最大保持"), property_boolean("min_hold_visible", "显示最小保持"), property_boolean("max_marker_visible", "显示最大值标记"), property_boolean("use_min_marker", "显示最小值标记"), property_boolean("sweep_region_visible", "显示扫频区域"), property_boolean("visible_range_only", "仅绘制可见范围"), property_select("interpolation_mode", "曲线插值") .enum_label("最近样本") .enum_label("数值线性") .enum_label("功率域线性") .enum_label("左阶梯") .enum_label("右阶梯") .enum_label("三次插值"), property_object("max_brush", "最大保持画刷"), property_object("current_brush", "当前曲线画刷"), property_object("min_brush", "最小保持画刷"), property_object("max_pen", "最大保持画笔"), property_object("current_pen", "当前曲线画笔"), property_object("min_pen", "最小保持画笔"), property_object("selected_marker_pen", "选中标记画笔"), property_object("marker_pen", "标记画笔"), property_object("middle_frequency_pen", "中心频率画笔"), property_object("sweep_region_brush", "扫频区域画刷"), property_boolean("tooltip_enabled", "启用提示框"), property_object("tooltip_font", "提示框字体"), property_object("tooltip_text_pen", "提示框文字画笔"), property_object("tooltip_background_brush", "提示框背景")); RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Waterfall, Waterfall::Properties, "waterfall", "频谱瀑布", property_object("frequency_range", "频率范围"), property_object("power_range", "功率范围"), property_number("frequency_bin_count", "频率格数"), property_select("partition_mode", "任务分区模式") .enum_label("自动瓶颈优化") .enum_label("固定分区"), property_number("partition_count", "固定分区数") .description("固定模式下的并行任务数;1 表示不分区") .visible_on("${$self.partition_mode == 'Fixed'}"), property_boolean("visible_range_only", "仅绘制可见范围"), property_select("interpolation_mode", "图像插值") .enum_label("最近邻") .enum_label("双线性") .enum_label("双三次"), property_select("color_map", "色图") .enum_label("频谱") .enum_label("火焰") .enum_label("灰度"), property_boolean("tooltip_enabled", "启用提示框"), property_object("tooltip_font", "提示框字体"), property_object("tooltip_text_pen", "提示框文字画笔"), property_object("tooltip_background_brush", "提示框背景")); RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Afterglow, Afterglow::Properties, "afterglow", "余辉频谱", property_object("frequency_range", "频率范围"), property_object("power_range", "功率范围"), property_number("frequency_point_size", "频率点数"), property_number("power_point_size", "功率点数"), property_select("partition_mode", "任务分区模式") .enum_label("自动瓶颈优化") .enum_label("固定分区"), property_number("partition_count", "固定分区数") .description("固定模式下的并行任务数;1 表示不分区") .visible_on("${$self.partition_mode == 'Fixed'}"), property_boolean("interpolate", "插值功率点"), property_number("attenuation_rate", "衰减率"), property_select("color_map", "色图") .enum_label("频谱") .enum_label("火焰") .enum_label("灰度")); RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Sweep_Spectrum, Sweep_Spectrum::Properties, "sweep_spectrum", "扫频频谱", property_object("frequency_range", "频率范围"), property_number("bins_per_block", "每块频率格数"), property_number("block_count", "扫频块数"), property_object("pen", "扫频曲线画笔"), property_object("current_frequency_pen", "当前频率画笔"), property_boolean("visible_range_only", "仅绘制可见范围"), property_select("interpolation_mode", "曲线插值") .enum_label("最近样本") .enum_label("数值线性") .enum_label("功率域线性") .enum_label("左阶梯") .enum_label("右阶梯") .enum_label("三次插值")); RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Frequency_Trace, Frequency_Trace::Properties, "frequency_trace", "频率轨迹", property_object("pen", "轨迹画笔")); RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Selection_Rectangle_Overlay, Selection_Rectangle_Overlay::Properties, "selection_overlay", "框选区域", property_object("label_font", "标签字体"), property_object("label_pen", "标签画笔"), property_object("selection_brush", "选区画刷"), property_object("selection_border_pen", "选区边框画笔")); RENDERIVE_GALLERY_DESCRIPTOR(Gallery_Constellation_Diagram, Constellation_Diagram::Properties, "constellation", "星座图", property_object("i_range", "I 轴范围"), property_object("q_range", "Q 轴范围"), property_color("point_color", "点颜色"), property_color("anchor_color", "锚点颜色"), property_number("point_lifetime_ms", "点保留时间"), property_select("type", "调制类型") .enum_label("4 PSK") .enum_label("8 PSK") .enum_label("16 PSK"), property_number("phase_offset_radians", "相位偏移")); #undef RENDERIVE_GALLERY_DESCRIPTOR } // namespace adminive namespace renderive::web::gallery_action_registry { constexpr auto axis_metadata() { return structive::type_metadata( gallery_action("axis_probe", "读取坐标映射与刻度", "coord_to_pixel / pixel_to_coord / tick_step / sub_tick_count / tick_label", "坐标轴")); } } // namespace renderive::web::gallery_action_registry namespace structive { #define RENDERIVE_TYPE_ACTIONS(Type, ...) \ template <> struct Type_Descriptor { \ static auto get() { \ using T = renderive::web::Type; \ using renderive::web::gallery_action; \ return object(type_metadata(__VA_ARGS__)); \ } \ } template <> struct Type_Descriptor { static auto get() { return object( renderive::web::gallery_action_registry::axis_metadata()); } }; template <> struct Type_Descriptor { static auto get() { return object( renderive::web::gallery_action_registry::axis_metadata()); } }; template <> struct Type_Descriptor { static auto get() { using T = renderive::web::Gallery_Time_Axis; using renderive::web::gallery_action; return object(type_metadata( gallery_action("axis_probe", "读取坐标映射与刻度", "coord_to_pixel / pixel_to_coord / tick_step / sub_tick_count / tick_label", "坐标轴"), gallery_action("append_time", "追加时间点", "Time_Axis::append_time / tick_to_time", "时间轴"))); } }; RENDERIVE_TYPE_ACTIONS(Gallery_Spectrum, gallery_action("push_samples", "推送一帧样本", "Spectrum::update_samples", "频谱"), gallery_action("power_at", "查询指定频率功率", "Spectrum::power_at", "频谱", {}, "number", "频率", 98e6), gallery_action("add_marker", "添加点标记", "Spectrum::add_custom_marker", "标记", {}, "number", "频率", 96e6), gallery_action("add_line_marker", "添加线标记", "Spectrum::add_custom_line_marker", "标记", {}, "number", "频率", 100e6), gallery_action("remove_marker", "按频率删除标记", "Spectrum::remove_custom_marker", "标记", {}, "number", "频率", 96e6), gallery_action("remove_selected_marker", "删除选中标记", "Spectrum::remove_selected_marker", "标记"), gallery_action("clear_markers", "清空标记", "Spectrum::clear_custom_markers", "标记"), gallery_action("select_marker", "选择标记索引", "Spectrum::set_selected_marker_index", "标记", {}, "number", "索引", 0.0), gallery_action("select_next_marker", "选择下一个标记", "Spectrum::select_next_marker", "标记"), gallery_action("select_previous_marker", "选择上一个标记", "Spectrum::select_previous_marker", "标记"), gallery_action("clear_marker_selection", "清除标记选择", "Spectrum::clear_marker_selection", "标记"), gallery_action("set_marker_frequency", "修改选中标记频率", "Spectrum::set_marker_frequency / set_current_marker_frequency", "标记", {}, "number", "频率", 99e6)); RENDERIVE_TYPE_ACTIONS(Gallery_Waterfall, gallery_action("append_row", "追加一行", "Waterfall::append_row", "频谱瀑布"), gallery_action("append_tick_row", "按 tick 追加一行", "Time_Axis::append_time / Waterfall::append_row(int, span)", "频谱瀑布")); RENDERIVE_TYPE_ACTIONS(Gallery_Afterglow, gallery_action("append_spectrum", "追加一帧频谱", "Afterglow::append_spectrum", "余辉频谱")); RENDERIVE_TYPE_ACTIONS(Gallery_Sweep_Spectrum, gallery_action("append_block", "追加一个扫频块", "Sweep_Spectrum::append_block", "扫频频谱")); RENDERIVE_TYPE_ACTIONS(Gallery_Frequency_Trace, gallery_action("append_sample", "追加一个样本", "Frequency_Trace::append_sample", "频率轨迹"), gallery_action("append_tick_sample", "按 tick 追加样本", "Time_Axis::append_time / Frequency_Trace::append_sample(int, double)", "频率轨迹")); RENDERIVE_TYPE_ACTIONS(Gallery_Selection_Rectangle_Overlay, gallery_action("clear_selection", "清空框选区域", "Selection_Rectangle_Overlay::clear_selected_regions", "框选区域")); RENDERIVE_TYPE_ACTIONS(Gallery_Constellation_Diagram, gallery_action("append_points", "追加一组 IQ 点", "Constellation_Diagram::append_point", "星座图"), gallery_action("fit_square", "按轴拟合正方形", "Constellation_Diagram::fit_square_to_axes", "星座图")); #undef RENDERIVE_TYPE_ACTIONS } // namespace structive namespace renderive::web { template std::size_t gallery_control_count() { using Json = nlohmann::json; const auto count_fields = [](const auto& self, const Json& fields) -> std::size_t { std::size_t result{}; for (const auto& field : fields) { if (field.value("editable", false) && !field.contains("children")) ++result; if (field.contains("children")) result += self(self, field.at("children")); } return result; }; const Json descriptor = adminive::to_descriptor_json(); return count_fields(count_fields, descriptor.at("fields")); } struct Gallery_Case_Type_Registration { std::string_view case_id; std::size_t (*control_count)(); void (*append_actions)(std::vector&); }; template Gallery_Case_Type_Registration gallery_case_types(std::string_view case_id) { return { case_id, [] { return (std::size_t{} + ... + gallery_control_count()); }, [](std::vector& actions) { (append_gallery_actions(actions), ...); } }; } inline const Gallery_Case_Type_Registration* gallery_case_registration( std::string_view case_id) { static const std::array registrations{ gallery_case_types("axis_lab"), gallery_case_types("spectrum"), gallery_case_types("afterglow"), gallery_case_types("sweep_spectrum"), gallery_case_types("waterfall"), gallery_case_types("frequency_trace"), gallery_case_types("selection_overlay"), gallery_case_types("constellation") }; const auto found = std::find_if( registrations.begin(), registrations.end(), [case_id](const auto& registration) { return registration.case_id == case_id; }); return found == registrations.end() ? nullptr : &*found; } inline std::size_t gallery_renderable_control_count(std::string_view case_id) { const auto* registration = gallery_case_registration(case_id); return registration ? registration->control_count() : 0; } inline void append_gallery_renderable_actions( std::string_view case_id, std::vector& actions) { if (const auto* registration = gallery_case_registration(case_id)) registration->append_actions(actions); } } // namespace renderive::web