355 lines
17 KiB
C++
355 lines
17 KiB
C++
#pragma once
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
#include "../architecture/Render_Lease.h"
|
|
#include "../render/Canvas.h"
|
|
#include "../render/Render_Frame_Snapshot.h"
|
|
#include "Abs_Axis.h"
|
|
#include "blend2d/blend2d.h"
|
|
namespace renderive {
|
|
struct Abs_Axis_Render_State : Render_State, Abs_Axis_Prop {
|
|
size_t pixel_length{};
|
|
int label_precision = 2;
|
|
void scale_range(double factor, double center) {
|
|
coord_start = (coord_start - center) * factor + center;
|
|
coord_length *= factor;
|
|
}
|
|
};
|
|
struct Abs_Axis_Input_Data : Input_Data {};
|
|
struct Axis_Frame_Snapshot {
|
|
Orientation orientation = Orientation::Horizontal;
|
|
Range coord_range{0.0, 1.0};
|
|
Range pixel_range{0.0, 1.0};
|
|
[[nodiscard]] double coord_to_pixel(double coord) const {
|
|
if (coord_range.length() == 0.0)
|
|
return pixel_range.origin;
|
|
return pixel_range.origin + pixel_range.length() * (coord - coord_range.origin) / coord_range.length();
|
|
}
|
|
[[nodiscard]] double pixel_to_coord(double pixel) const {
|
|
if (pixel_range.length() == 0.0)
|
|
return coord_range.origin;
|
|
return coord_range.origin + coord_range.length() * (pixel - pixel_range.origin) / pixel_range.length();
|
|
}
|
|
[[nodiscard]] int pixel_size() const {
|
|
return static_cast<int>(std::max(1.0, pixel_range.size()));
|
|
}
|
|
};
|
|
struct Abs_Axis_Private : Typed_Render_Data<Abs_Axis, Abs_Axis_Render_State, Abs_Axis_Input_Data> {
|
|
int tick_count = 2;
|
|
[[nodiscard]] Abs_Axis_Render_State* edit_axis_state() {
|
|
return edit_state();
|
|
}
|
|
[[nodiscard]] Abs_Axis_Render_State* render_axis_state() {
|
|
return static_cast<Abs_Axis_Render_State*>(state(Renderable_State_Role::Render));
|
|
}
|
|
[[nodiscard]] Abs_Axis_Render_State* render_axis_state(const Renderable_Frame_View& frame_view) const {
|
|
return render_state(frame_view);
|
|
}
|
|
static int pixel_start_of(const Abs_Axis_Render_State* s) {
|
|
return s->orientation == Orientation::Horizontal ? s->x : s->y;
|
|
}
|
|
static Axis_Frame_Snapshot axis_frame_snapshot(const Abs_Axis_Render_State* s) {
|
|
if (!s)
|
|
return {};
|
|
double pixel_start = static_cast<double>(pixel_start_of(s));
|
|
return Axis_Frame_Snapshot{
|
|
s->orientation,
|
|
Range{s->coord_start, s->coord_start + s->coord_length},
|
|
Range{pixel_start, pixel_start + static_cast<double>(s->pixel_length)}
|
|
};
|
|
}
|
|
virtual Axis_Frame_Snapshot frame_snapshot(const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) {
|
|
return axis_frame_snapshot(render_axis_state(frame_view));
|
|
}
|
|
double get_mantissa(double input, double* magnitude = nullptr);
|
|
[[nodiscard]] double clean_mantissa(double input);
|
|
[[nodiscard]] double pick_closest(double target, const std::vector<double>& candidates);
|
|
void prepare_data(const Render_Frame_Snapshot&, const Renderable_Frame_View&) override {}
|
|
void draw(Canvas& canvas, const Render_Frame_Snapshot& snapshot, const Renderable_Frame_View& frame_view) override {
|
|
Abs_Axis_Render_State* s = render_axis_state(frame_view);
|
|
if (!s)
|
|
return;
|
|
Axis_Frame_Snapshot axis_snapshot = frame_snapshot(snapshot, frame_view);
|
|
const int pixel_start = pixel_start_of(s);
|
|
Range range = axis_snapshot.coord_range;
|
|
double tick_step = get_tick_step(range);
|
|
std::vector<double> ticks = create_frame_tick_vector(tick_step, range, s, snapshot, frame_view);
|
|
while (!ticks.empty() && !range.contains(ticks.back())) {
|
|
ticks.pop_back();
|
|
}
|
|
int sub_tick_count = get_sub_tick_count(tick_step);
|
|
std::vector<double> sub_ticks = create_frame_sub_tick_vector(sub_tick_count, ticks, range, s, snapshot, frame_view);
|
|
while (!sub_ticks.empty() && !range.contains(sub_ticks.back())) {
|
|
sub_ticks.pop_back();
|
|
}
|
|
std::vector<std::string> labels = create_frame_label_vector(ticks, s, snapshot, frame_view);
|
|
Text_Metrics tm = canvas.measure_text(!s->unit_text_font.valid() ? "0" : " ");
|
|
double h = tm.line_height();
|
|
canvas.set_pen(Pen{.color = s->color});
|
|
if (s->orientation == Orientation::Horizontal) {
|
|
canvas.draw_line({(double)s->x, (double)s->y}, {(double)(s->x + s->pixel_length), (double)s->y});
|
|
int n = (int)ticks.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
double tick = ticks[i];
|
|
double x = axis_snapshot.coord_to_pixel(tick);
|
|
canvas.draw_line({x, (double)s->y}, {x, (double)(s->y + s->tick_length)});
|
|
const std::string& label = labels[i];
|
|
PointF start{x, (double)(s->y + s->tick_length)};
|
|
canvas.save();
|
|
canvas.translate(start.x, start.y);
|
|
canvas.rotate((double)s->label_rotation_degrees * 3.14159265358979 / 180.0);
|
|
canvas.draw_text({0.0, 0.0}, label);
|
|
canvas.restore();
|
|
}
|
|
for (double tick : sub_ticks) {
|
|
double x = axis_snapshot.coord_to_pixel(tick);
|
|
canvas.draw_line({x, (double)s->y}, {x, (double)(s->y + s->sub_tick_length)});
|
|
}
|
|
}
|
|
else if (s->orientation == Orientation::Vertical) {
|
|
canvas.draw_line({(double)s->x, (double)s->y}, {(double)s->x, (double)(s->y + s->pixel_length)});
|
|
int n = (int)ticks.size();
|
|
for (int i = 0; i < n; ++i) {
|
|
double tick = ticks[i];
|
|
double y = axis_snapshot.coord_to_pixel(tick);
|
|
const std::string& label = labels[i];
|
|
canvas.draw_line({(double)s->x, y}, {(double)(s->x + s->tick_length), y});
|
|
PointF start{(double)(s->x + s->tick_length * 2), y + h / 2.0};
|
|
canvas.save();
|
|
canvas.translate(start.x, start.y);
|
|
canvas.rotate((double)s->label_rotation_degrees * 3.14159265358979 / 180.0);
|
|
canvas.draw_text({0.0, 0.0}, label);
|
|
canvas.restore();
|
|
}
|
|
for (double tick : sub_ticks) {
|
|
double y = axis_snapshot.coord_to_pixel(tick);
|
|
canvas.draw_line({(double)s->x, y}, {(double)(s->x + s->sub_tick_length), y});
|
|
}
|
|
}
|
|
if (s->unit_text.empty())
|
|
return;
|
|
Text_Metrics um = canvas.measure_text(s->unit_text_font, s->unit_text);
|
|
double w = um.width;
|
|
if (s->orientation == Orientation::Horizontal) {
|
|
double x = pixel_start + (double)s->pixel_length - w;
|
|
double y = (double)s->y - h;
|
|
RectF area(x, y, w, h);
|
|
canvas.set_brush(s->unit_text_background_brush);
|
|
canvas.fill_rect(area);
|
|
canvas.set_pen(s->unit_text_pen);
|
|
canvas.set_font(s->unit_text_font);
|
|
canvas.draw_text(area, Text_Align::Center, s->unit_text);
|
|
}
|
|
else if (s->orientation == Orientation::Vertical) {
|
|
double x = (double)s->x;
|
|
double y = (double)pixel_start;
|
|
RectF area(x, y, w, h);
|
|
canvas.set_brush(s->unit_text_background_brush);
|
|
canvas.fill_rect(area);
|
|
canvas.set_pen(s->unit_text_pen);
|
|
canvas.set_font(s->unit_text_font);
|
|
canvas.draw_text(area, Text_Align::Center, s->unit_text);
|
|
}
|
|
}
|
|
[[nodiscard]] virtual double pixel_to_coord(double pixel, Abs_Axis_Render_State* s) {
|
|
return s->coord_start + s->coord_length * (pixel - (double)pixel_start_of(s)) / (double)s->pixel_length;
|
|
}
|
|
[[nodiscard]] virtual double coord_to_pixel(double coord, Abs_Axis_Render_State* s) {
|
|
return pixel_start_of(s) + (double)s->pixel_length * (coord - s->coord_start) / s->coord_length;
|
|
}
|
|
virtual double get_tick_step(const Range& range) {
|
|
double exact_step = range.size() / double(tick_count + 1e-10);
|
|
return clean_mantissa(exact_step);
|
|
}
|
|
virtual int get_sub_tick_count(double tick_step);
|
|
virtual std::vector<double> create_tick_vector(double tick_step, const Range& range);
|
|
virtual std::vector<double> create_sub_tick_vector(int sub_tick_count, const std::vector<double>& ticks, const Range& range);
|
|
virtual std::string get_tick_label(double tick, Abs_Axis_Render_State* state);
|
|
virtual std::vector<std::string> create_label_vector(const std::vector<double>& ticks, Abs_Axis_Render_State* state);
|
|
virtual std::vector<double> create_frame_tick_vector(double tick_step, const Range& range, Abs_Axis_Render_State*, const Render_Frame_Snapshot&, const Renderable_Frame_View&) {
|
|
return create_tick_vector(tick_step, range);
|
|
}
|
|
virtual std::vector<double> create_frame_sub_tick_vector(int sub_tick_count, const std::vector<double>& ticks, const Range& range, Abs_Axis_Render_State*, const Render_Frame_Snapshot&, const Renderable_Frame_View&) {
|
|
return create_sub_tick_vector(sub_tick_count, ticks, range);
|
|
}
|
|
virtual std::vector<std::string> create_frame_label_vector(const std::vector<double>& ticks, Abs_Axis_Render_State* state, const Render_Frame_Snapshot&, const Renderable_Frame_View&) {
|
|
return create_label_vector(ticks, state);
|
|
}
|
|
virtual std::string frame_tick_label(double tick, const Render_Frame_Snapshot&, const Renderable_Frame_View& frame_view) {
|
|
return get_tick_label(tick, render_axis_state(frame_view));
|
|
}
|
|
[[nodiscard]] double edit_pixel_to_coord(double pixel) {
|
|
return pixel_to_coord(pixel, edit_axis_state());
|
|
}
|
|
[[nodiscard]] double render_pixel_to_coord(double pixel) {
|
|
return pixel_to_coord(pixel, render_axis_state());
|
|
}
|
|
[[nodiscard]] double edit_coord_to_pixel(double coord) {
|
|
return coord_to_pixel(coord, edit_axis_state());
|
|
}
|
|
[[nodiscard]] double render_coord_to_pixel(double coord) {
|
|
return coord_to_pixel(coord, render_axis_state());
|
|
}
|
|
[[nodiscard]] Range edit_coord_range() {
|
|
const auto* s = edit_axis_state();
|
|
return {s->coord_start, s->coord_start + s->coord_length};
|
|
}
|
|
[[nodiscard]] Range render_coord_range() {
|
|
const auto* s = render_axis_state();
|
|
return {s->coord_start, s->coord_start + s->coord_length};
|
|
}
|
|
[[nodiscard]] int render_pixel_size() {
|
|
return std::abs(static_cast<int>(render_axis_state()->pixel_length));
|
|
}
|
|
[[nodiscard]] std::string edit_tick_label(double tick) {
|
|
return get_tick_label(tick, edit_axis_state());
|
|
}
|
|
[[nodiscard]] std::string render_tick_label(double tick) {
|
|
return get_tick_label(tick, render_axis_state());
|
|
}
|
|
[[nodiscard]] std::vector<std::string> edit_label_vector(const std::vector<double>& ticks) {
|
|
return create_label_vector(ticks, edit_axis_state());
|
|
}
|
|
[[nodiscard]] std::vector<std::string> render_label_vector(const std::vector<double>& ticks) {
|
|
return create_label_vector(ticks, render_axis_state());
|
|
}
|
|
};
|
|
struct Axis_Mapping_2D {
|
|
Axis_Frame_Snapshot domain;
|
|
Axis_Frame_Snapshot value;
|
|
[[nodiscard]] PointF map(double domain_coord, double value_coord) const {
|
|
PointF ret{};
|
|
double domain_pixel = domain.coord_to_pixel(domain_coord);
|
|
double value_pixel = value.coord_to_pixel(value_coord);
|
|
if (domain.orientation == Orientation::Horizontal) {
|
|
ret.x = domain_pixel;
|
|
ret.y = value_pixel;
|
|
}
|
|
else {
|
|
ret.x = value_pixel;
|
|
ret.y = domain_pixel;
|
|
}
|
|
return ret;
|
|
}
|
|
[[nodiscard]] PointF map(PointF data_point) const {
|
|
return map(data_point.x, data_point.y);
|
|
}
|
|
[[nodiscard]] double domain_pixel(PointF screen_point) const {
|
|
return domain.orientation == Orientation::Horizontal ? screen_point.x : screen_point.y;
|
|
}
|
|
[[nodiscard]] double value_pixel(PointF screen_point) const {
|
|
return value.orientation == Orientation::Horizontal ? screen_point.x : screen_point.y;
|
|
}
|
|
};
|
|
struct Axis_Basis_2D {
|
|
PointF origin{};
|
|
PointF domain_vector{};
|
|
PointF value_vector{};
|
|
[[nodiscard]] static Axis_Basis_2D from_ranges(const Axis_Mapping_2D& mapping, const Range& domain_range, const Range& value_range) {
|
|
PointF origin = mapping.map(domain_range.origin, value_range.origin);
|
|
PointF domain_target = mapping.map(domain_range.target, value_range.origin);
|
|
PointF value_target = mapping.map(domain_range.origin, value_range.target);
|
|
return Axis_Basis_2D{
|
|
origin,
|
|
PointF{domain_target.x - origin.x, domain_target.y - origin.y},
|
|
PointF{value_target.x - origin.x, value_target.y - origin.y}
|
|
};
|
|
}
|
|
};
|
|
struct Axis_Render_Access final {
|
|
static Axis_Frame_Snapshot snapshot_from_state(const Abs_Axis_Render_State* s) {
|
|
return Abs_Axis_Private::axis_frame_snapshot(s);
|
|
}
|
|
static Axis_Frame_Snapshot snapshot(Abs_Axis* axis) {
|
|
const auto* s = axis->d()->render_axis_state();
|
|
return snapshot_from_state(s);
|
|
}
|
|
static const Abs_Axis_Render_State* frame_state(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) {
|
|
const auto* node = frame_snapshot.frame_node_for(axis);
|
|
if (!node)
|
|
return nullptr;
|
|
return dynamic_cast<const Abs_Axis_Render_State*>(node->frame_view.state);
|
|
}
|
|
static Axis_Frame_Snapshot snapshot(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) {
|
|
if (const auto* node = frame_snapshot.frame_node_for(axis))
|
|
return axis->d()->frame_snapshot(frame_snapshot, node->frame_view);
|
|
return snapshot(axis);
|
|
}
|
|
static Axis_Mapping_2D mapping(Abs_Axis* domain_axis, Abs_Axis* value_axis) {
|
|
return Axis_Mapping_2D{snapshot(domain_axis), snapshot(value_axis)};
|
|
}
|
|
static Axis_Mapping_2D mapping(Abs_Axis* domain_axis, Abs_Axis* value_axis, const Render_Frame_Snapshot& frame_snapshot) {
|
|
return Axis_Mapping_2D{snapshot(domain_axis, frame_snapshot), snapshot(value_axis, frame_snapshot)};
|
|
}
|
|
static bool orientations_orthogonal(Abs_Axis* domain_axis, Abs_Axis* value_axis) {
|
|
return domain_axis && value_axis && orientation(domain_axis) != orientation(value_axis);
|
|
}
|
|
static double coord_to_pixel(Abs_Axis* axis, double coord) {
|
|
return axis->d()->render_coord_to_pixel(coord);
|
|
}
|
|
static double pixel_to_coord(Abs_Axis* axis, double pixel) {
|
|
return axis->d()->render_pixel_to_coord(pixel);
|
|
}
|
|
static Range coord_range(Abs_Axis* axis) {
|
|
return axis->d()->render_coord_range();
|
|
}
|
|
static Range coord_range(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) {
|
|
Axis_Frame_Snapshot s = snapshot(axis, frame_snapshot);
|
|
return s.coord_range;
|
|
}
|
|
static int pixel_size(Abs_Axis* axis) {
|
|
return axis->d()->render_pixel_size();
|
|
}
|
|
static int pixel_size(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) {
|
|
return snapshot(axis, frame_snapshot).pixel_size();
|
|
}
|
|
static Orientation orientation(Abs_Axis* axis) {
|
|
return axis->d()->render_axis_state()->orientation;
|
|
}
|
|
static Orientation orientation(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) {
|
|
return snapshot(axis, frame_snapshot).orientation;
|
|
}
|
|
static std::string tick_label(Abs_Axis* axis, double tick) {
|
|
return axis->d()->render_tick_label(tick);
|
|
}
|
|
static std::string tick_label(Abs_Axis* axis, double tick, const Render_Frame_Snapshot& frame_snapshot) {
|
|
if (axis) {
|
|
if (const auto* node = frame_snapshot.frame_node_for(axis))
|
|
return axis->d()->frame_tick_label(tick, frame_snapshot, node->frame_view);
|
|
}
|
|
return tick_label(axis, tick);
|
|
}
|
|
static std::string unit_text(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) {
|
|
if (const auto* s = frame_state(axis, frame_snapshot))
|
|
return s->unit_text;
|
|
return axis ? axis->unit_text() : std::string{};
|
|
}
|
|
static Font unit_text_font(Abs_Axis* axis, const Render_Frame_Snapshot& frame_snapshot) {
|
|
if (const auto* s = frame_state(axis, frame_snapshot))
|
|
return s->unit_text_font;
|
|
return axis ? axis->unit_text_font() : Font{};
|
|
}
|
|
};
|
|
template <typename That>
|
|
void Abs_Axis::Builder_T<That>::set(Abs_Axis* ret) {
|
|
ret->init(parent);
|
|
Abs_Axis_Private* pd = ret->d();
|
|
Render_Edit_Lease<Abs_Axis_Private, Abs_Axis_Render_State> edit(pd);
|
|
Abs_Axis_Render_State* sc = edit.operator->();
|
|
sc->x = this->x;
|
|
sc->y = this->y;
|
|
sc->orientation = this->orientation;
|
|
sc->pixel_length = std::max<std::size_t>(1, this->pixel_size);
|
|
sc->tick_length = this->tick_length;
|
|
sc->sub_tick_length = this->sub_tick_length;
|
|
sc->color = this->color;
|
|
sc->locale = this->locale;
|
|
sc->unit_text = this->unit_text;
|
|
sc->unit_text_font = this->unit_text_font;
|
|
sc->unit_text_pen = this->unit_text_pen;
|
|
sc->unit_text_background_brush = this->unit_text_background_brush;
|
|
sc->label_rotation_degrees = this->label_rotation_degrees;
|
|
}
|
|
} // namespace renderive
|