#pragma once #include "Abs_Axis.h" #include "Axis_Format.h" #include "../render/Blend2D_Cache.h" #include #include #include #include #include #include #include #include namespace renderive::detail { template requires std::is_base_of_v class Axis_State_Strategy : public Double_State_Strategy> { using State_Observer = Observer_State; using Base = Double_State_Strategy; public: Axis_State_Strategy(::Scene_Base& scene, State state) : Base(state, With_Observer{State_Observer(Renderable_State_Observer(this))}, scene) {} template Value> Axis_State_Strategy& set(Value&& value) { Base::template set(std::forward(value)); using Member_Value = decltype(std::declval().*Member); if constexpr (Paint_Only_Property_Value) this->paint_changed(); else this->changed(); return *this; } template requires std::invocable Axis_State_Strategy& update(Update&& value) { Base::update(std::forward(value)); this->changed(); return *this; } [[nodiscard]] Axis_Transform transform() const final { return Base::read([this](const State& state) { return make_transform(state, coordinate_range(state)); }); } [[nodiscard]] Axis_Transform transform(const Render_State_View& view) const noexcept final { const auto& state = view.get(static_cast(*this)); return make_transform(state, coordinate_range(state)); } [[nodiscard]] double tick_step(Range range) const final { return Base::read([this, range](const State& state) { return calculate_tick_step(range, state); }); } [[nodiscard]] std::string tick_label(double tick) const final { return Base::read([this, tick](const State& state) { return format_tick_label(tick, state); }); } protected: [[nodiscard]] virtual Range coordinate_range(const State& state) const noexcept = 0; [[nodiscard]] virtual double calculate_tick_step(Range range, const State&) const { const double raw = range.size() / 5.0; if (!(raw > 0.0) || !std::isfinite(raw)) return 1.0; const double scale = std::pow(10.0, std::floor(std::log10(raw))); const double normalized = raw / scale; const double nice = normalized <= 1.0 ? 1.0 : normalized <= 2.0 ? 2.0 : normalized <= 5.0 ? 5.0 : 10.0; return nice * scale; } [[nodiscard]] virtual std::string format_tick_label(double tick, const State& state) const { int precision = 2; if constexpr (requires { state.precision; }) precision = state.precision.get(); return localized_axis_number(tick, precision, state.locale); } private: static Axis_Transform make_transform(const State& state, Range coordinates) noexcept { return { coordinates, state.orientation == Orientation::Horizontal ? static_cast(state.x) : static_cast(state.y), static_cast(state.pixel_length), state.orientation }; } struct Prepared_Line { PointF first; PointF second; }; struct Prepared_Label { PointF position; std::string text; }; struct Axis_Prepare_Buffer { std::vector lines; std::vector labels; PointF unit_position; double unit_width{}; bool valid{}; }; void prepare_frame(const Prepare_Render_Context& context) final { const auto& view = context.frame.render_state; const auto& state = view.get(static_cast(*this)); auto& output = prepare_buffer_; output = {}; if (state.pixel_length == 0) return; const Range coordinates = coordinate_range(state); const Axis_Transform axis = make_transform(state, coordinates); const double step = calculate_tick_step(coordinates, state); if (!(step > 0.0)) return; const PointF first{static_cast(state.x), static_cast(state.y)}; const PointF last = state.orientation == Orientation::Horizontal ? PointF{first.x + state.pixel_length, first.y} : PointF{first.x, first.y + state.pixel_length}; output.lines.push_back({first, last}); const auto [low, high] = std::minmax(coordinates.origin, coordinates.target); const double initial = std::ceil(low / step) * step; int tick_index{}; for (double tick = initial; tick <= high + step * 1e-6 && tick_index < 1000; tick += step, ++tick_index) { const double pixel = axis.coord_to_pixel(tick); PointF tick_start; PointF tick_end; PointF label; if (state.orientation == Orientation::Horizontal) { tick_start = {pixel, static_cast(state.y)}; tick_end = {pixel, static_cast(state.y + state.tick_length)}; label = {pixel + 2.0, static_cast(state.y + state.tick_length + 2)}; } else { tick_start = {static_cast(state.x), pixel}; tick_end = {static_cast(state.x + state.tick_length), pixel}; label = {static_cast(state.x + state.tick_length + 2), pixel - 7.0}; } output.lines.push_back({tick_start, tick_end}); output.labels.push_back({label, format_tick_label(tick, state)}); const int subdivisions = std::max(0, this->sub_tick_count(step)); for (int sub_index = 1; sub_index <= subdivisions; ++sub_index) { const double sub_tick = tick + step * sub_index / (subdivisions + 1.0); if (sub_tick >= high) break; const double sub_pixel = axis.coord_to_pixel(sub_tick); if (state.orientation == Orientation::Horizontal) { output.lines.push_back({ {sub_pixel, static_cast(state.y)}, {sub_pixel, static_cast(state.y + state.sub_tick_length)}}); } else { output.lines.push_back({ {static_cast(state.x), sub_pixel}, {static_cast(state.x + state.sub_tick_length), sub_pixel}}); } } } if (!state.unit_text.empty()) { output.unit_position = {last.x + 4.0, last.y + 4.0}; output.unit_width = std::max(4.0, state.unit_text.size() * state.unit_text_font.size * 0.65); } output.valid = true; } void paint(Painter& painter, const Paint_Render_Context& context) final { const auto& output = prepare_buffer_; if (!output.valid) return; const auto& view = context.frame.render_state; const auto& state = view.get(static_cast(*this)); const Pen axis_pen{state.color, 1.0}; for (const auto& line : output.lines) painter.line(line.first, line.second, axis_pen); for (const auto& label : output.labels) painter.text(label.position, label.text, state.unit_text_font, state.unit_text_pen, state.label_rotation_degrees); if (!state.unit_text.empty()) { painter.rect({output.unit_position.x - 2.0, output.unit_position.y - 2.0, output.unit_width + 4.0, state.unit_text_font.size * 1.5 + 4.0}, Pen{.style = Line_Style::None}, state.unit_text_background_brush); painter.text(output.unit_position, state.unit_text, state.unit_text_font, state.unit_text_pen); } } Axis_Prepare_Buffer prepare_buffer_; }; } // namespace renderive::detail