#include "Plot_p.h" #include "../bridge/Qt_Event_Adapter.h" #include "../bridge/Qt_Image_Adapter.h" #include "../bridge/Qt_Presentation_Sink.h" #include #include #include #include #include #include #include #include namespace renderive { namespace { Color from_qcolor(const QColor& color) { return { static_cast(color.red()), static_cast(color.green()), static_cast(color.blue()), static_cast(color.alpha()) }; } QColor to_qcolor(Color color) { return QColor(color.r, color.g, color.b, color.a); } std::string to_utf8_string(const QString& text) { const QByteArray bytes = text.toUtf8(); return {bytes.constData(), static_cast(bytes.size())}; } } // namespace Abs_Plot_Private::Abs_Plot_Private(bool continuous_rendering) : core(std::make_unique()), continuous(continuous_rendering) {} Abs_Plot_Private::~Abs_Plot_Private() = default; void Abs_Plot_Private::attach_widget(Abs_Plot* plot) { presentation_sink = std::make_shared(plot); core->set_presentation_sink(presentation_sink); timer = new QTimer(plot); timer->setTimerType(Qt::PreciseTimer); QObject::connect(timer, &QTimer::timeout, plot, [this] { if (core->view_active()) (void)core->render_frame(); }); } Abs_Plot::Abs_Plot(Abs_Plot_Private* private_data) : d(private_data) { d->attach_widget(this); setAttribute(Qt::WA_OpaquePaintEvent, true); setMouseTracking(true); setFocusPolicy(Qt::NoFocus); update_render_interval(); } Abs_Plot::~Abs_Plot() { if (!d) return; if (d->timer) d->timer->stop(); d->core->deactivate_view(); delete d; d = nullptr; } void Abs_Plot::init() { d->core->init(); } Plot_Core* Abs_Plot::core() const { return d ? d->core.get() : nullptr; } std::shared_ptr Abs_Plot::root_renderable() const { return d->core->root_renderable(); } std::shared_ptr Abs_Plot::create_renderable_node( const std::shared_ptr& parent, const QString& object_name) const { return d->core->create_renderable_node(parent, to_utf8_string(object_name)); } void Abs_Plot::remove_renderable(const std::shared_ptr& renderable) { d->core->remove_renderable(renderable); } QColor Abs_Plot::background_color() const { return to_qcolor(d->core->background_color()); } void Abs_Plot::set_background_color(const QColor& color) { d->core->set_background_color(from_qcolor(color)); } void Abs_Plot::start_render_timer() { d->core->activate_view(); update_render_interval(); if (d->timer && !d->timer->isActive()) d->timer->start(); } void Abs_Plot::stop_render_timer() { if (d->timer) d->timer->stop(); d->core->deactivate_view(); } void Abs_Plot::update_render_interval() { if (!d->timer) return; const double fps = std::max(1.0, d->core->max_render_fps()); d->timer->setInterval(std::max(1, static_cast(std::lround(1000.0 / fps)))); } void Abs_Plot::paintEvent(QPaintEvent*) { d->paint_event(this); } void Abs_Plot_Private::paint_event(Abs_Plot* plot) { QPainter painter(plot); painter.fillRect(plot->rect(), plot->background_color()); core->with_frame([&painter](Image_View view) { if (!view.empty()) painter.drawImage(QPoint(0, 0), Qt_Image_Adapter::to_qimage(view)); }); draw_performance_overlay(plot, painter); painter.end(); } void Abs_Plot_Private::draw_performance_overlay(Abs_Plot* plot, QPainter& painter) { const auto overlay = core->performance_overlay(); if (!overlay || !overlay->enabled()) return; const auto snapshot = overlay->display_snapshot(); if (!snapshot || snapshot->viewport_size != core->viewport_size()) return; const auto& options = snapshot->style; QFont font = painter.font(); font.setPointSizeF(std::max(1.0, options.font.size)); font.setWeight(std::clamp(options.font.weight / 10, 0, 99)); font.setItalic(options.font.italic); painter.setFont(font); const QFontMetrics metrics(font); const int line_height = std::max(1, metrics.height() + 2); const int left_margin = std::max(0, options.left_margin); const int right_margin = std::max(0, options.right_margin); const int top_margin = std::max(0, options.top_margin); const int bottom_margin = std::max(0, options.bottom_margin); int text_width{}; for (const auto& line : snapshot->lines) text_width = std::max(text_width, metrics.horizontalAdvance(QString::fromStdString(line))); const int panel_width = std::min(plot->width(), left_margin + text_width + right_margin); const int panel_height = std::min( plot->height(), top_margin + bottom_margin + line_height * static_cast(snapshot->lines.size())); painter.fillRect(QRect(0, 0, panel_width, panel_height), to_qcolor(options.background)); int y = top_margin + metrics.ascent(); for (std::size_t index = 0; index < snapshot->lines.size(); ++index) { const QString line = QString::fromStdString(snapshot->lines[index]); if (index == 0) { painter.setPen(to_qcolor(options.section_color)); painter.drawText(left_margin, y, line); } else if (const qsizetype separator = line.indexOf(':'); separator >= 0) { const QString label = line.left(separator + 1); painter.setPen(to_qcolor(options.label_color)); painter.drawText(left_margin, y, label); painter.setPen(to_qcolor(options.value_color)); painter.drawText(left_margin + metrics.horizontalAdvance(label), y, line.mid(separator + 1)); } else { painter.setPen(to_qcolor(options.label_color)); painter.drawText(left_margin, y, line); } y += line_height; } } bool Abs_Plot::event(QEvent* event) { const auto dispatch = [this, event](const Event& translated) { d->core->dispatch_event(translated); if (!translated.is_accepted()) return false; event->accept(); return true; }; switch (event->type()) { case QEvent::Resize: { auto* resize = static_cast(event); d->core->set_viewport_size(Qt_Event_Adapter::to_size(resize->size())); d->core->dispatch_event(Qt_Event_Adapter::to_resize_event(resize)); break; } case QEvent::Show: d->core->dispatch_event(Event(Event_Type::Show)); if (d->continuous) start_render_timer(); break; case QEvent::Hide: d->core->dispatch_event(Event(Event_Type::Hide)); if (d->continuous) stop_render_timer(); break; case QEvent::Leave: { const Event translated(Event_Type::Leave); if (dispatch(translated)) return true; break; } case QEvent::Wheel: { const auto translated = Qt_Event_Adapter::to_wheel_event( static_cast(event)); if (dispatch(translated)) return true; break; } case QEvent::MouseMove: { const auto translated = Qt_Event_Adapter::to_pointer_event( static_cast(event), Event_Type::Pointer_Move); if (dispatch(translated)) return true; break; } case QEvent::MouseButtonPress: { const auto translated = Qt_Event_Adapter::to_pointer_event( static_cast(event), Event_Type::Pointer_Press); if (dispatch(translated)) return true; break; } case QEvent::MouseButtonRelease: { const auto translated = Qt_Event_Adapter::to_pointer_event( static_cast(event), Event_Type::Pointer_Release); if (dispatch(translated)) return true; break; } case QEvent::KeyPress: { const auto translated = Qt_Event_Adapter::to_key_event( static_cast(event), Event_Type::Key_Press); if (dispatch(translated)) return true; break; } case QEvent::KeyRelease: { const auto translated = Qt_Event_Adapter::to_key_event( static_cast(event), Event_Type::Key_Release); if (dispatch(translated)) return true; break; } default: break; } return QWidget::event(event); } } // namespace renderive