diff options
Diffstat (limited to 'spline')
-rw-r--r-- | spline/axis-opts.cpp | 10 | ||||
-rw-r--r-- | spline/axis-opts.hpp | 17 | ||||
-rw-r--r-- | spline/broken/qfunctionconfiguratorplugin.h | 2 | ||||
-rw-r--r-- | spline/spline-widget.cpp | 208 | ||||
-rw-r--r-- | spline/spline-widget.hpp | 26 | ||||
-rw-r--r-- | spline/spline.cpp | 399 | ||||
-rw-r--r-- | spline/spline.hpp | 81 |
7 files changed, 381 insertions, 362 deletions
diff --git a/spline/axis-opts.cpp b/spline/axis-opts.cpp index b5f11aaa..489008e6 100644 --- a/spline/axis-opts.cpp +++ b/spline/axis-opts.cpp @@ -1,5 +1,7 @@ #include "axis-opts.hpp" +namespace axis_opts_impl { + using max_clamp = axis_opts::max_clamp; static max_clamp get_max_x(Axis k) @@ -25,14 +27,14 @@ static max_clamp get_max_y(Axis k) } axis_opts::axis_opts(QString pfx, Axis idx) : + prefix_(pfx), + axis_(idx), zero(b_settings_window, n(pfx, "zero-pos"), 0), src(b_settings_window, n(pfx, "source-index"), idx), invert(b_settings_window, n(pfx, "invert-sign"), false), altp(b_mapping_window, n(pfx, "alt-axis-sign"), false), clamp_x_(b_mapping_window, n(pfx, "max-value"), get_max_x(idx)), - clamp_y_(b_mapping_window, n(pfx, "max-output-value"), get_max_y(idx)), - prefix_(pfx), - axis_(idx) + clamp_y_(b_mapping_window, n(pfx, "max-output-value"), get_max_y(idx)) {} QString const& axis_opts::prefix() const { return prefix_; } @@ -43,3 +45,5 @@ QString axis_opts::n(QString const& pfx, QString const& name) { return QString("%1-%2").arg(pfx, name); } + +} // ns axis_opts_impl diff --git a/spline/axis-opts.hpp b/spline/axis-opts.hpp index b4475502..c773dd61 100644 --- a/spline/axis-opts.hpp +++ b/spline/axis-opts.hpp @@ -9,8 +9,14 @@ namespace axis_opts_impl { using namespace options; -struct OTR_SPLINE_EXPORT axis_opts final +class OTR_SPLINE_EXPORT axis_opts final { + QString prefix_; + Axis axis_; + + static inline QString n(QString const& pfx, QString const& name); + +public: enum max_clamp { r180 = 180, @@ -37,8 +43,8 @@ struct OTR_SPLINE_EXPORT axis_opts final }; // note, these two bundles can be the same value with no issues - bundle b_settings_window = make_bundle("opentrack-ui"); - bundle b_mapping_window = make_bundle("opentrack-mappings"); + bundle b_settings_window{ make_bundle(axis_ == Axis(-1) ? QString() : "opentrack-ui") }; + bundle b_mapping_window{ make_bundle(axis_ == Axis(-1) ? QString() : "opentrack-mappings") }; value<double> zero; value<int> src; value<bool> invert, altp; @@ -49,11 +55,6 @@ struct OTR_SPLINE_EXPORT axis_opts final QString const& prefix() const; Axis axis() const; -private: - static inline QString n(QString const& pfx, QString const& name); - - QString prefix_; - Axis axis_; }; } // ns axis_opts_impl diff --git a/spline/broken/qfunctionconfiguratorplugin.h b/spline/broken/qfunctionconfiguratorplugin.h index ca68e0e2..11b9a58c 100644 --- a/spline/broken/qfunctionconfiguratorplugin.h +++ b/spline/broken/qfunctionconfiguratorplugin.h @@ -16,7 +16,7 @@ class QFunctionConfiguratorPlugin : public QObject, public QDesignerCustomWidget Q_INTERFACES(QDesignerCustomWidgetInterface) public: - QFunctionConfiguratorPlugin(QObject *parent = 0); + QFunctionConfiguratorPlugin(QObject *parent = nullptr); bool isContainer() const; bool isInitialized() const; diff --git a/spline/spline-widget.cpp b/spline/spline-widget.cpp index 64f9f156..d329d8e2 100644 --- a/spline/spline-widget.cpp +++ b/spline/spline-widget.cpp @@ -1,10 +1,3 @@ -/* Copyright (c) 2012-2016 Stanislaw Halik <sthalik@misaki.pl> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - */ - #include "spline-widget.hpp" #include "compat/math.hpp" #include "compat/macros.hpp" @@ -19,7 +12,7 @@ #include <QDebug> -using namespace spline_detail; +namespace spline_detail { spline_widget::spline_widget(QWidget *parent) : QWidget(parent) { @@ -31,28 +24,31 @@ spline_widget::spline_widget(QWidget *parent) : QWidget(parent) spline_widget::~spline_widget() { if (connection) + { QObject::disconnect(connection); + connection = {}; + } } -void spline_widget::setConfig(base_spline* spl) +void spline_widget::set_config(base_spline* spl) { if (connection) { QObject::disconnect(connection); - connection = QMetaObject::Connection(); + connection = {}; } - _config = spl; + config = spl; if (spl) { - update_range(); - - std::shared_ptr<base_spline::base_settings> s = spl->get_settings(); - connection = connect(s.get(), &base_spline::base_settings::recomputed, + std::shared_ptr<base_settings> s = spl->get_settings(); + connection = connect(s.get(), &base_settings::recomputed, this, [this] { reload_spline(); }, Qt::QueuedConnection); } + + reload_spline(); } QColor spline_widget::colorBezier() const @@ -68,37 +64,39 @@ void spline_widget::setColorBezier(QColor const& color) void spline_widget::force_redraw() { - _background = QPixmap(); + background_img = {}; repaint(); } void spline_widget::set_preview_only(bool val) { - _preview_only = val; + preview_only = val; } bool spline_widget::is_preview_only() const { - return _preview_only; + return preview_only; } void spline_widget::drawBackground() { - QPainter painter(&_background); + QPainter painter(&background_img); painter.fillRect(rect(), widget_bg_color); { QColor bg_color(112, 154, 209); - if (!isEnabled() && !_preview_only) + if (!isEnabled() && !preview_only) bg_color = QColor(176,176,180); painter.fillRect(pixel_bounds, bg_color); } QFont font; font.setPointSize(8); + font.setStyleHint(QFont::Monospace, QFont::PreferAntialias); painter.setFont(font); - QFontMetricsF metrics(font); + const QFontMetricsF metrics(font); + const double height = metrics.height(); QColor color__(176, 190, 209, 127); @@ -107,11 +105,11 @@ void spline_widget::drawBackground() const QPen pen(color__, 1, Qt::SolidLine, Qt::FlatCap); - const int ystep = _y_step, xstep = _x_step; - const double maxx = _config->max_input(); - const double maxy = _config->max_output(); + const int ystep = (int)std::ceil(y_step_), xstep = (int)std::ceil(x_step_); + const double maxx = config->max_input(); + const double maxy = config->max_output(); - // horizontal grid + // vertical grid for (int i = 0; i <= maxy; i += ystep) { const double y = pixel_bounds.height() - i * c.y() + pixel_bounds.y(); @@ -120,13 +118,13 @@ void spline_widget::drawBackground() QPointF(pixel_bounds.x() + pixel_bounds.width(), y), pen); painter.drawText(QRectF(10, - y - metrics.height()/2., + y - height/2, pixel_bounds.left(), - metrics.height()), + height), QString::number(i)); } - // vertical grid + // horizontal grid for (int i = 0; i <= maxx; i += xstep) { const double x = pixel_bounds.x() + i * c.x(); @@ -134,21 +132,29 @@ void spline_widget::drawBackground() QPointF(x, pixel_bounds.y()), QPointF(x, pixel_bounds.y() + pixel_bounds.height()), pen); + const QString text = QString::number(i); - painter.drawText(QRectF(x - metrics.width(text)/2., - pixel_bounds.height() + 10 + metrics.height(), - metrics.width(text), - metrics.height()), + + const double width = +#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) + metrics.horizontalAdvance(text); +#else + metrics.width(text); +#endif + + painter.drawText(QRectF{x - width/2, + pixel_bounds.height() + 10 + height, + width, height}, text); } } void spline_widget::drawFunction() { - QPainter painter(&_function); + QPainter painter(&spline_img); painter.setRenderHint(QPainter::Antialiasing, true); - const points_t points = _config->get_points(); + const points_t& points = config->get_points(); if (moving_control_point_idx >= 0 && moving_control_point_idx < points.size()) @@ -156,7 +162,7 @@ void spline_widget::drawFunction() const QPen pen(Qt::white, 1, Qt::SolidLine, Qt::FlatCap); const QPointF prev_ = point_to_pixel({}); QPointF prev(iround(prev_.x()), iround(prev_.y())); - for (auto point : points) + for (const auto& point : points) { const QPointF tmp = point_to_pixel(point); drawLine(painter, prev, tmp, pen); @@ -165,7 +171,7 @@ void spline_widget::drawFunction() } const QColor color_ = progn( - if (!isEnabled() && !_preview_only) + if (!isEnabled() && !preview_only) { QColor color(spline_color); const int avg = int(float(color.red() + color.green() + color.blue())/3); @@ -187,7 +193,7 @@ void spline_widget::drawFunction() const double dpr = devicePixelRatioF(); const double line_length_pixels = std::fmax(1, 2 * dpr); const double step = std::fmax(.1, line_length_pixels / c.x()); - const double maxx = _config->max_input(); + const double maxx = config->max_input(); //#define USE_CUBIC_SPLINE #if defined USE_CUBIC_SPLINE @@ -205,9 +211,9 @@ void spline_widget::drawFunction() for (double k = 0; k < maxx; k += step*3) // NOLINT { - const auto next_1 = (double) _config->get_value_no_save(k + step*1); - const auto next_2 = (double) _config->get_value_no_save(k + step*2); - const auto next_3 = (double) _config->get_value_no_save(k + step*3); + const auto next_1 = config->get_value_no_save(k + step*1); + const auto next_2 = config->get_value_no_save(k + step*2); + const auto next_3 = config->get_value_no_save(k + step*3); QPointF b(clamp(point_to_pixel({k + step*1, next_1}))), c(clamp(point_to_pixel({k + step*2, next_2}))), @@ -221,14 +227,14 @@ void spline_widget::drawFunction() QPointF prev = point_to_pixel({}); for (double i = 0; i < maxx; i += step) // NOLINT { - const auto val = (double) _config->get_value_no_save(i); + const auto val = config->get_value_no_save(i); const QPointF cur = point_to_pixel({i, val}); painter.drawLine(prev, cur); prev = cur; } { - const double maxx = _config->max_input(); - const double maxy = double(_config->get_value_no_save(maxx)); + const double maxx = config->max_input(); + const double maxy = config->get_value_no_save(maxx); painter.drawLine(prev, point_to_pixel({ maxx, maxy })); } #endif @@ -242,7 +248,7 @@ void spline_widget::drawFunction() painter.fillRect(r2, widget_bg_color); const int alpha = !isEnabled() ? 64 : 120; - if (!_preview_only) + if (!preview_only) { for (auto const& point : points) { @@ -256,7 +262,7 @@ void spline_widget::drawFunction() void spline_widget::paintEvent(QPaintEvent *e) { - if (!_config) + if (!config) return; QPainter p(this); @@ -265,28 +271,30 @@ void spline_widget::paintEvent(QPaintEvent *e) const int W = iround(width() * dpr); const int H = iround(height() * dpr); - if (_background.size() != QSize(W, H)) + if (background_img.size() != QSize(W, H)) { - _background = QPixmap(W, H); - _background.setDevicePixelRatio(dpr); - _draw_function = true; + update_range(); + + background_img = { W, H }; + background_img.setDevicePixelRatio(dpr); drawBackground(); + draw_function = true; } - if (_draw_function) + if (draw_function) { - _draw_function = false; - _function = _background; + draw_function = false; + spline_img = background_img; drawFunction(); } - p.drawPixmap(e->rect(), _function); + p.drawPixmap(e->rect(), spline_img); // If the Tracker is active, the 'Last Point' it requested is recorded. // Show that point on the graph, with some lines to assist. // This new feature is very handy for tweaking the curves! QPointF last; - if (_config->get_last_value(last) && isEnabled()) + if (config->get_last_value(last) && isEnabled()) drawPoint(p, point_to_pixel(last), QColor(255, 0, 0, 120)); } @@ -312,14 +320,14 @@ void spline_widget::drawLine(QPainter& painter, const QPointF& start, const QPoi void spline_widget::mousePressEvent(QMouseEvent *e) { - if (!_config || !isEnabled() || !is_in_bounds(e->localPos()) || _preview_only) + if (!config || !isEnabled() || !is_in_bounds(e->localPos()) || preview_only) return; const double min_dist = min_pt_distance(); moving_control_point_idx = -1; - points_t points = _config->get_points(); + const points_t& points = config->get_points(); if (e->button() == Qt::LeftButton) { @@ -352,50 +360,50 @@ void spline_widget::mousePressEvent(QMouseEvent *e) if (!too_close) { - _config->add_point(pixel_to_point(e->localPos())); + config->add_point(pixel_to_point(e->localPos())); show_tooltip(e->pos()); } } - _draw_function = true; + draw_function = true; } if (e->button() == Qt::RightButton) { - if (_config) + if (config) { for (int i = 0; i < points.size(); i++) { if (point_within_pixel(points[i], e->localPos())) { - _config->remove_point(i); - _draw_function = true; + config->remove_point(i); + draw_function = true; break; } } } } - if (_draw_function) + if (draw_function) repaint(); } void spline_widget::mouseMoveEvent(QMouseEvent *e) { - if (_preview_only && _config) + if (preview_only && config) { show_tooltip(e->pos()); return; } - if (!_config || !isEnabled() || !isActiveWindow()) + if (!config || !isEnabled() || !isActiveWindow()) { QToolTip::hideText(); return; } const int i = moving_control_point_idx; - const points_t points = _config->get_points(); + const points_t& points = config->get_points(); const int sz = points.size(); if (i >= 0 && i < sz) @@ -428,8 +436,8 @@ void spline_widget::mouseMoveEvent(QMouseEvent *e) if ((!has_prev || check_prev()) && (!has_next || check_next())) { - _config->move_point(i, new_pt); - _draw_function = true; + config->move_point(i, new_pt); + draw_function = true; repaint(); } } @@ -456,7 +464,7 @@ void spline_widget::mouseMoveEvent(QMouseEvent *e) void spline_widget::mouseReleaseEvent(QMouseEvent *e) { - if (!_config || !isEnabled() || !isActiveWindow() || _preview_only) + if (!config || !isEnabled() || !isActiveWindow() || preview_only) return; const bool redraw = moving_control_point_idx != -1; @@ -479,7 +487,7 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e) if (redraw) { - _draw_function = true; + draw_function = true; repaint(); } } @@ -487,7 +495,7 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e) void spline_widget::reload_spline() { // don't recompute here as the value's about to be recomputed in the callee - update_range(); + background_img = {}; update(); } @@ -504,8 +512,8 @@ void spline_widget::show_tooltip(const QPoint& pos, const QPointF& value_) double x = value.x(), y = value.y(); - if (_preview_only) - y = _config->get_value_no_save(x); + if (preview_only) + y = config->get_value_no_save(x); const int x_ = iround(x), y_ = iround(y); @@ -514,16 +522,13 @@ void spline_widget::show_tooltip(const QPoint& pos, const QPointF& value_) if (std::fabs(y_ - y) < 1e-3) y = y_; - // the style on OSX has different offsets - static const bool is_fusion = + // the native OSX style doesn't look right otherwise #if defined __APPLE__ - true; + constexpr int off_x = 0, off_y = 0; #else - false; + constexpr int off_x = 25, off_y = 15; #endif - const int off_x = (is_fusion ? 25 : 0), off_y = (is_fusion ? 15 : 0); - const QPoint pix(pos.x() + off_x, pos.y() + off_y); QToolTip::showText(mapToGlobal(pix), @@ -535,8 +540,8 @@ void spline_widget::show_tooltip(const QPoint& pos, const QPointF& value_) bool spline_widget::is_in_bounds(const QPointF& pos) const { - const int grace = point_size_in_pixels * 3; - const int bottom_grace = int(point_size_in_pixels * 1.5); + const int grace = (int)std::ceil(point_size_in_pixels * 3); + const int bottom_grace = (int)std::ceil(point_size_in_pixels * 1.5); return (pos.x() + grace > pixel_bounds.left() && pos.x() - grace < pixel_bounds.right() && pos.y() + grace > pixel_bounds.top() && @@ -545,21 +550,22 @@ bool spline_widget::is_in_bounds(const QPointF& pos) const void spline_widget::update_range() { - if (!_config) + if (!config) return; const int w = width(), h = height(); const int mwl = 40, mhl = 20; const int mwr = 15, mhr = 35; - pixel_bounds = QRect(mwl, mhl, (w - mwl - mwr), (h - mhl - mhr)); - c = { pixel_bounds.width() / _config->max_input(), pixel_bounds.height() / _config->max_output() }; - _draw_function = true; - - _background = QPixmap(); - _function = QPixmap(); + pixel_bounds = { mwl, mhl, (w - mwl - mwr), (h - mhl - mhr) }; + c = { + pixel_bounds.width() / std::fmax(1, config->max_input()), + pixel_bounds.height() / std::fmax(1, config->max_output()) + }; - repaint(); + draw_function = true; + background_img = {}; + spline_img = {}; } bool spline_widget::point_within_pixel(const QPointF& pt, const QPointF& pixel) @@ -573,7 +579,7 @@ void spline_widget::focusOutEvent(QFocusEvent* e) if (moving_control_point_idx != -1) QToolTip::hideText(); moving_control_point_idx = -1; - _draw_function = true; + draw_function = true; lower(); setCursor(Qt::ArrowCursor); e->accept(); @@ -604,8 +610,8 @@ QPointF spline_widget::pixel_to_point(const QPointF& point) if (snap_y > 0) y = snap(y, snap_y); - x = clamp(x, 0, _config->max_input()); - y = clamp(y, 0, _config->max_output()); + x = clamp(x, 0, config->max_input()); + y = clamp(y, 0, config->max_output()); return { x, y }; } @@ -620,19 +626,19 @@ QPointF spline_widget::point_to_pixel(const QPointF& point) void spline_widget::resizeEvent(QResizeEvent *) { - update_range(); + reload_spline(); } bool spline_widget::is_on_pt(const QPointF& pos, int* pt) { - if (!_config) + if (!config) { if (pt) *pt = -1; return false; } - const points_t points = _config->get_points(); + const points_t& points = config->get_points(); for (int i = 0; i < points.size(); i++) { @@ -648,3 +654,17 @@ bool spline_widget::is_on_pt(const QPointF& pos, int* pt) *pt = -1; return false; } + +void spline_widget::changeEvent(QEvent* e) +{ + switch (e->type()) + { + case QEvent::EnabledChange: + background_img = {}; spline_img = {}; + update(); + break; + default: + break; + } +} +} // ns spline_detail diff --git a/spline/spline-widget.hpp b/spline/spline-widget.hpp index a6d0eb9d..b0a76e2d 100644 --- a/spline/spline-widget.hpp +++ b/spline/spline-widget.hpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016 Stanislaw Halik <sthalik@misaki.pl> +/* Copyright (c) 2012-2019 Stanislaw Halik <sthalik@misaki.pl> * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -34,12 +34,11 @@ class OTR_SPLINE_EXPORT spline_widget final : public QWidget Q_PROPERTY(int x_step READ x_step WRITE set_x_step) Q_PROPERTY(int y_step READ y_step WRITE set_y_step) - using points_t = base_spline::points_t; public: explicit spline_widget(QWidget *parent = nullptr); ~spline_widget() override; - void setConfig(base_spline* spl); + void set_config(base_spline* spl); QColor colorBezier() const; void setColorBezier(QColor const& color); @@ -48,10 +47,10 @@ public: void set_preview_only(bool val); bool is_preview_only() const; - double x_step() const { return _x_step; } - double y_step() const { return _y_step; } - void set_x_step(double val) { _x_step = std::fmax(1., val); } - void set_y_step(double val) { _y_step = std::fmax(1., val); } + double x_step() const { return x_step_; } + double y_step() const { return y_step_; } + void set_x_step(double val) { x_step_ = std::fmax(1., val); } + void set_y_step(double val) { y_step_ = std::fmax(1., val); } void set_snap(double x, double y) { snap_x = x; snap_y = y; } void get_snap(double& x, double& y) const { x = snap_x; y = snap_y; } @@ -78,6 +77,7 @@ private: bool is_on_pt(const QPointF& pos, int* pt = nullptr); void update_range(); + void changeEvent(QEvent* e) override; QPointF pixel_to_point(const QPointF& point); QPointF point_to_pixel(const QPointF& point); @@ -85,10 +85,10 @@ private: static double snap(double x, double snap_value); QPointF c; - base_spline* _config = nullptr; + base_spline* config = nullptr; - QPixmap _background; - QPixmap _function; + QPixmap background_img; + QPixmap spline_img; QColor spline_color; QColor widget_bg_color = palette().background().color(); @@ -98,12 +98,12 @@ private: QMetaObject::Connection connection; double snap_x = 0, snap_y = 0; - double _x_step = 10, _y_step = 10; + double x_step_ = 10, y_step_ = 10; int moving_control_point_idx = -1; - bool _draw_function = true, _preview_only = false; + bool draw_function = true, preview_only = false; // point's circle radius on the widget - static constexpr inline int point_size_in_pixels_ = 4; + static constexpr int point_size_in_pixels_ = 4; const double point_size_in_pixels = point_size_in_pixels_ * std::fmax(1, devicePixelRatioF() * .66); }; diff --git a/spline/spline.cpp b/spline/spline.cpp index cd3daf30..3ec3b5dc 100644 --- a/spline/spline.cpp +++ b/spline/spline.cpp @@ -1,11 +1,3 @@ -/* Copyright (c) 2012-2016, Stanislaw Halik <sthalik@misaki.pl> - - * Permission to use, copy, modify, and/or distribute this - * software for any purpose with or without fee is hereby granted, - * provided that the above copyright notice and this permission - * notice appear in all copies. - */ - #include "spline.hpp" #include "compat/math.hpp" @@ -18,14 +10,19 @@ #include <QObject> #include <QMutexLocker> -#include <QCoreApplication> #include <QPointF> #include <QSettings> #include <QString> #include <QDebug> -using namespace spline_detail; +namespace spline_detail { + +settings::~settings() = default; +base_spline_::~base_spline_() = default; +base_spline::~base_spline() = default; +spline_modify_mixin::~spline_modify_mixin() = default; +spline_settings_mixin::~spline_settings_mixin() = default; spline::spline(const QString& name, const QString& axis_name, Axis axis) { @@ -34,8 +31,7 @@ spline::spline(const QString& name, const QString& axis_name, Axis axis) spline::~spline() { - QMutexLocker l(&_mutex); - + QMutexLocker l(&mtx); disconnect_signals(); } @@ -43,101 +39,101 @@ spline::spline() : spline(QString{}, QString{}, Axis(-1)) {} void spline::set_tracking_active(bool value) { - QMutexLocker l(&_mutex); - activep = value; + std::shared_ptr<settings> S; + { + QMutexLocker l(&mtx); + S = s; + activep = value; + } + emit S->recomputed(); } bundle spline::get_bundle() { - QMutexLocker l(&_mutex); // avoid logic errors due to changes in value<t> data + QMutexLocker l(&mtx); return s->b; } void spline::clear() { + std::shared_ptr<settings> S; { - QMutexLocker l(&_mutex); + QMutexLocker l(&mtx); + S = s; s->points = {}; + points = {}; + update_interp_data(); } - - invalidate_settings(); + emit S->recomputed(); } -float spline::get_value(double x) +double spline::get_value(double x) const { - QMutexLocker foo(&_mutex); + QMutexLocker l(&mtx); - const float ret = get_value_no_save(x); - last_input_value.setX(std::fabs(x)); - last_input_value.setY(double(std::fabs(ret))); + const double ret = get_value_no_save(x); + last_input_value = { std::fabs(x), std::fabs((double)ret) }; return ret; } -float spline::get_value_no_save(double x) const +double spline::get_value_no_save(double x) const { - return const_cast<spline&>(*this).get_value_no_save_internal(x); -} - -float spline::get_value_no_save_internal(double x) -{ - QMutexLocker foo(&_mutex); + QMutexLocker l(&mtx); - float q = float(x * bucket_size_coefficient(points)); + double q = x * bucket_size_coefficient(points); int xi = (int)q; - float yi = get_value_internal(xi); - float yiplus1 = get_value_internal(xi+1); - float f = (q-xi); - float ret = yiplus1 * f + yi * (1.0f - f); // at least do a linear interpolation. + double yi = get_value_internal(xi); + double yiplus1 = get_value_internal(xi+1); + double f = (q-xi); + double ret = yiplus1 * f + yi * (1 - f); // at least do a linear interpolation. return ret; } bool spline::get_last_value(QPointF& point) { - QMutexLocker foo(&_mutex); + QMutexLocker foo(&mtx); point = last_input_value; - return activep; + return activep && point.y() >= 0; } -float spline::get_value_internal(int x) +double spline::get_value_internal(int x) const { - if (!validp) - { - update_interp_data(); - validp = true; - } - const float sign = signum(x); x = std::abs(x); - const float ret_ = data[std::min(unsigned(x), unsigned(value_count)-1u)]; + const float ret_ = data[std::min(unsigned(x), value_count - 1)]; return sign * clamp(ret_, 0, 1000); } -void spline::add_lone_point() -{ - points = { QPointF(s->opts.clamp_x_, s->opts.clamp_y_) }; - s->points = points; -} - -QPointF spline::ensure_in_bounds(const QList<QPointF>& points, int i) +void spline::ensure_in_bounds(const QList<QPointF>& points, int i, f& x, f& y) { const int sz = points.size(); if (i < 0 || sz == 0) - return {}; - - if (i < sz) - return points[i]; - - return points[sz - 1]; + { + x = 0; + y = 0; + } + else if (i < sz) + { + const QPointF& pt = points[i]; + x = (f)pt.x(); + y = (f)pt.y(); + } + else + { + const QPointF& pt = points[sz - 1]; + x = (f)pt.x(); + y = (f)pt.y(); + } } int spline::element_count(const QList<QPointF>& points, double max_input) { - const unsigned sz = points.size(); - for (unsigned k = 0; k < sz; k++) + const unsigned sz = (unsigned)points.size(); + for (int k = sz-1; k >= 0; k--) { const QPointF& pt = points[k]; - if (max_input > 1e-4 && pt.x() - 1e-2 > max_input) + if (max_input > 1e-4 && pt.x() - 1e-4 >= max_input) return k; } return sz; @@ -148,21 +144,17 @@ bool spline::sort_fn(const QPointF& one, const QPointF& two) return one.x() < two.x(); } -void spline::update_interp_data() +void spline::update_interp_data() const { points_t list = points; ensure_valid(list); const int sz = list.size(); - const double maxx = max_input(); - if (list.isEmpty()) - list.prepend({ maxx, max_output() }); + list.prepend({ max_input(), max_output() }); const double c = bucket_size_coefficient(list); - const double c_interp = c * 30; - - enum { magic_fill_value = -255 }; + const double c_ = c * c_interp; for (unsigned i = 0; i < value_count; i++) data[i] = magic_fill_value; @@ -172,35 +164,35 @@ void spline::update_interp_data() const QPointF& pt = list[0]; const double x = pt.x(); const double y = pt.y(); - const unsigned max = clamp(uround(x * c), 0, value_count-2); + const unsigned max = clamp(uround(x * c), 0, value_count-1); for (unsigned k = 0; k <= max; k++) data[k] = float(y * k / max); // no need for bresenham } else { - if (list[0].x() > 1e-2 && list[0].x() <= maxx) - list.push_front(QPointF(0, 0)); + if (list[0].x() > 1e-2) + list.push_front({}); // now this is hella expensive due to `c_interp' for (int i = 0; i < sz; i++) { - const QPointF p0 = ensure_in_bounds(list, i - 1); - const QPointF p1 = ensure_in_bounds(list, i + 0); - const QPointF p2 = ensure_in_bounds(list, i + 1); - const QPointF p3 = ensure_in_bounds(list, i + 2); - const double p0_x = p0.x(), p1_x = p1.x(), p2_x = p2.x(), p3_x = p3.x(); - const double p0_y = p0.y(), p1_y = p1.y(), p2_y = p2.y(), p3_y = p3.y(); - - const double cx[4] = { + f p0_x, p1_x, p2_x, p3_x; + f p0_y, p1_y, p2_y, p3_y; + + ensure_in_bounds(list, i - 1, p0_x, p0_y); + ensure_in_bounds(list, i + 0, p1_x, p1_y); + ensure_in_bounds(list, i + 1, p2_x, p2_y); + ensure_in_bounds(list, i + 2, p3_x, p3_y); + + const f cx[4] = { 2 * p1_x, // 1 -p0_x + p2_x, // t 2 * p0_x - 5 * p1_x + 4 * p2_x - p3_x, // t^2 -p0_x + 3 * p1_x - 3 * p2_x + p3_x, // t3 }; - const double cy[4] = - { + const f cy[4] = { 2 * p1_y, // 1 -p0_y + p2_y, // t 2 * p0_y - 5 * p1_y + 4 * p2_y - p3_y, // t^2 @@ -208,182 +200,205 @@ void spline::update_interp_data() }; // multiplier helps fill in all the x's needed - const unsigned end = int(c_interp * (p2_x - p1_x)) + 1; + const unsigned end = (unsigned)(c_ * (p2_x - p1_x)) + 1; + const f end_(end); for (unsigned k = 0; k <= end; k++) { - const double t = k / double(end); - const double t2 = t*t; - const double t3 = t*t*t; + const f t = k / end_; + const f t2 = t*t; + const f t3 = t*t*t; - const int x = int(.5 * c * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3)); - const float y = float(.5 * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3)); + const unsigned x = unsigned(f(.5) * c * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3)); + const float y = (float)(f(.5) * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3)); - if (unsigned(x) < value_count) + if (x < value_count) data[x] = y; } } } - double maxy = max_output(); + float maxy = (float)max_output(); float last = 0; - for (unsigned i = 0; i < unsigned(value_count); i++) + +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wfloat-equal" // stupid clang +#endif + + for (unsigned i = 0; i < value_count; i++) { if (data[i] == magic_fill_value) data[i] = last; - data[i] = (float)clamp(data[i], 0, maxy); + data[i] = clamp(data[i], 0, maxy); last = data[i]; } + +#ifdef __clang__ +# pragma clang diagnostic pop +#endif } void spline::remove_point(int i) { - QMutexLocker foo(&_mutex); + std::shared_ptr<settings> S; + { + QMutexLocker foo(&mtx); + S = s; - const int sz = element_count(points, max_input()); + const int sz = element_count(points, max_input()); - if (i >= 0 && i < sz) - { - points.erase(points.begin() + i); - s->points = points; - validp = false; + if (i >= 0 && i < sz) + { + points.erase(points.begin() + i); + s->points = points; + update_interp_data(); + } } + + emit S->recomputed(); } void spline::add_point(QPointF pt) { - QMutexLocker foo(&_mutex); - - points.push_back(pt); - std::stable_sort(points.begin(), points.end(), sort_fn); - s->points = points; - validp = false; + std::shared_ptr<settings> S; + { + QMutexLocker foo(&mtx); + S = s; + points.push_back(pt); + std::stable_sort(points.begin(), points.end(), sort_fn); + s->points = points; + update_interp_data(); + } + emit S->recomputed(); } void spline::add_point(double x, double y) { - add_point(QPointF(x, y)); + add_point({ x, y }); } void spline::move_point(int idx, QPointF pt) { - QMutexLocker foo(&_mutex); + std::shared_ptr<settings> S; + { + QMutexLocker foo(&mtx); + S = s; - const int sz = element_count(points, max_input()); + const int sz = element_count(points, max_input()); - if (idx >= 0 && idx < sz) - { - points[idx] = pt; - std::stable_sort(points.begin(), points.end(), sort_fn); - s->points = points; - validp = false; + if (idx >= 0 && idx < sz) + { + points[idx] = pt; + std::stable_sort(points.begin(), points.end(), sort_fn); + s->points = points; + update_interp_data(); + } } + emit S->recomputed(); } -const base_spline_::points_t& spline::get_points() const +const points_t& spline::get_points() const { - QMutexLocker foo(&_mutex); return points; } int spline::get_point_count() const { - QMutexLocker foo(&_mutex); - return element_count(points, s->opts.clamp_x_); + QMutexLocker foo(&mtx); + return element_count(points, clamp_x); } void spline::reload() { - QMutexLocker foo(&_mutex); + QMutexLocker foo(&mtx); s->b->reload(); } void spline::save() { - QMutexLocker foo(&_mutex); + QMutexLocker foo(&mtx); s->b->save(); } -void spline::invalidate_settings() +void spline::invalidate_settings_() { - // we're holding the mutex to allow signal disconnection in spline dtor - // before this slot gets called for the next time + points = s->points; + clamp_x = s->opts.clamp_x_; + clamp_y = s->opts.clamp_y_; + update_interp_data(); +} +void spline::invalidate_settings() +{ + std::shared_ptr<settings> S; { - QMutexLocker l(&_mutex); - validp = false; - points = s->points; + QMutexLocker l(&mtx); + S = s; + invalidate_settings_(); } - emit s->recomputed(); + emit S->recomputed(); } void spline::set_bundle(bundle b, const QString& axis_name, Axis axis) { - QMutexLocker l(&_mutex); + if (!b) + b = make_bundle({}); + + std::shared_ptr<settings> S; - // gets called from ctor hence the need for nullptr checks - // the sentinel settings/bundle objects don't need any further branching once created - if (!s || s->b != b) { - disconnect_signals(); + QMutexLocker l(&mtx); - if (!b) - b = make_bundle(QString{}); + disconnect_signals(); s = std::make_shared<settings>(b, axis_name, axis); - - conn_changed = QObject::connect(b.get(), &bundle_::changed, - s.get(), [&] { invalidate_settings(); }, Qt::QueuedConnection); - // this isn't strictly necessary for the spline but helps the widget - conn_maxx = QObject::connect(&s->opts.clamp_x_, value_::value_changed<int>(), - ctx.get(), [&](double) { invalidate_settings(); }, Qt::QueuedConnection); - conn_maxy = QObject::connect(&s->opts.clamp_y_, value_::value_changed<int>(), - ctx.get(), [&](double) { invalidate_settings(); }, Qt::QueuedConnection); - - invalidate_settings(); + invalidate_settings_(); + S = s; + + conn_points = QObject::connect(&s->points, value_::value_changed<QList<QPointF>>(), + ctx.get(), [this] { invalidate_settings(); }, Qt::DirectConnection); + conn_maxx = QObject::connect(&s->opts.clamp_x_, value_::value_changed<int>(), + ctx.get(), [this](double) { invalidate_settings(); }, Qt::DirectConnection); + conn_maxy = QObject::connect(&s->opts.clamp_y_, value_::value_changed<int>(), + ctx.get(), [this](double) { invalidate_settings(); }, Qt::DirectConnection); } + + emit S->recomputed(); } double spline::max_input() const { - QMutexLocker l(&_mutex); - using m = axis_opts::max_clamp; - const value<m>& clamp = s->opts.clamp_x_; - if (clamp == m::x1000 && !points.empty()) - return points[points.size() - 1].x(); - return std::fabs(clamp.to<double>()); + QMutexLocker l(&mtx); + if (clamp_x == axis_opts::x1000) + return std::fmax(1, points.empty() ? 0 : points[points.size() - 1].x()); + return std::fabs((double)clamp_x); } double spline::max_output() const { - QMutexLocker l(&_mutex); - using m = axis_opts::max_clamp; - const value<m>& clamp = s->opts.clamp_y_; - if (clamp == m::x1000 && !points.empty()) - return points[points.size() - 1].y(); - return std::fabs(clamp.to<double>()); + QMutexLocker l(&mtx); + if (clamp_y == axis_opts::x1000 && !points.empty()) + return std::fmax(1, points.empty() ? 0 : points[points.size() - 1].y()); + return std::fabs((double)clamp_y); } -void spline::ensure_valid(points_t& list) +void spline::ensure_valid(points_t& list) const { - QMutexLocker foo(&_mutex); - - // storing to s->points fires bundle::changed and that leads to an infinite loop - // thus, only store if we can't help it std::stable_sort(list.begin(), list.end(), sort_fn); - const int sz = list.size(); + const unsigned sz = (unsigned)list.size(); - QList<QPointF> all_points, tmp; - all_points.reserve(sz), tmp.reserve(sz); + QList<QPointF> tmp_points, all_points; + tmp_points.reserve(sz); all_points.reserve(sz); const double maxx = max_input(); - for (int i = 0; i < sz; i++) + for (unsigned i = 0; i < sz; i++) { QPointF& pt{list[i]}; bool overlap = false; - for (int j = i+1; j < sz; j++) + for (unsigned j = i+1; j < sz; j++) { const QPointF& pt2{list[j]}; const QPointF tmp(pt - pt2); @@ -398,57 +413,56 @@ void spline::ensure_valid(points_t& list) if (!overlap) { - tmp.append(pt); // all points total + all_points.append(pt); // all points total // points within selected limit, for use in `update_interp_data' if (pt.x() - 1e-4 <= maxx && pt.x() >= 0) - all_points.push_back(pt); + tmp_points.push_back(pt); } } - // size check guards against livelock with value<t>/bundle_ signals + // simply storing to s->points fires bundle::changed leading to a livelock + // hence only store if we can't help it - // points that are within bounds - if (tmp.size() < points.size()) + if (all_points.size() < points.size()) { - points = std::move(tmp); - // can't stuff there unconditionally - // fires signals from `value<t>' and `bundle_' otherwise + // all points that don't overlap + points = std::move(all_points); s->points = points; } - if (all_points.size() < list.size()) - // all points that don't overlap, after clamping - list = std::move(all_points); + if (tmp_points.size() < list.size()) + // points that are within currently-specified bounds + list = std::move(tmp_points); last_input_value = {}; activep = false; } -// the return value is only safe to use with no spline::set_bundle calls std::shared_ptr<base_settings> spline::get_settings() { - QMutexLocker foo(&_mutex); + QMutexLocker foo(&mtx); return std::static_pointer_cast<base_settings>(s); } std::shared_ptr<const base_settings> spline::get_settings() const { - QMutexLocker foo(&_mutex); + QMutexLocker foo(&mtx); return std::static_pointer_cast<const base_settings>(s); } double spline::bucket_size_coefficient(const QList<QPointF>& points) const { constexpr double eps = 1e-4; - const double maxx = max_input(); if (maxx < eps) return 0; - // needed to fill the buckets up to the last control point. - // space between that point and max_x doesn't matter. + // buckets are only used between 0 and the rightmost point's + // x coordinate. even if `clamp_x' is set to a much larger value, + // buckets retain more precision lower the x coordinate of the + // last point. const int sz = element_count(points, maxx); const double last_x = sz ? points[sz - 1].x() : maxx; @@ -458,26 +472,17 @@ double spline::bucket_size_coefficient(const QList<QPointF>& points) const void spline::disconnect_signals() { - if (conn_changed) + if (conn_points) { - QObject::disconnect(conn_changed); - QObject::disconnect(conn_maxx); - QObject::disconnect(conn_maxy); - - conn_changed = {}; - conn_maxx = {}; - conn_maxy = {}; + QObject::disconnect(conn_points); conn_points = {}; + QObject::disconnect(conn_maxx); conn_maxx = {}; + QObject::disconnect(conn_maxy); conn_maxy = {}; } } -namespace spline_detail { - settings::settings(bundle const& b, const QString& axis_name, Axis idx): - b(b ? b : make_bundle("")), + b(b ? b : make_bundle({})), opts(axis_name, idx) {} -settings::~settings() = default; - -} - +} // ns spline_detail diff --git a/spline/spline.hpp b/spline/spline.hpp index 3d2d6e57..a78db8dd 100644 --- a/spline/spline.hpp +++ b/spline/spline.hpp @@ -1,4 +1,4 @@ -/* Copyright (c) 2012-2016, Stanislaw Halik <sthalik@misaki.pl> +/* Copyright (c) 2012-2019, Stanislaw Halik <sthalik@misaki.pl> * Permission to use, copy, modify, and/or distribute this * software for any purpose with or without fee is hereby granted, @@ -8,18 +8,15 @@ #pragma once -#include "compat/copyable-mutex.hpp" #include "options/options.hpp" - #include "axis-opts.hpp" - #include "export.hpp" +#include "compat/mutex.hpp" #include <cstddef> #include <vector> #include <limits> #include <memory> -#include <functional> #include <QObject> #include <QPointF> @@ -28,6 +25,7 @@ namespace spline_detail { +using points_t = QList<QPointF>; using namespace options; class OTR_SPLINE_EXPORT base_settings : public QObject @@ -50,10 +48,10 @@ public: struct OTR_SPLINE_EXPORT base_spline_ { - virtual inline ~base_spline_(); + virtual ~base_spline_(); - virtual float get_value(double x) = 0; - virtual float get_value_no_save(double x) const = 0; + virtual double get_value(double x) const = 0; + virtual double get_value_no_save(double x) const = 0; [[nodiscard]] virtual bool get_last_value(QPointF& point) = 0; virtual void set_tracking_active(bool value) = 0; @@ -61,20 +59,16 @@ struct OTR_SPLINE_EXPORT base_spline_ virtual double max_input() const = 0; virtual double max_output() const = 0; - using points_t = QList<QPointF>; - - virtual points_t const& get_points() const = 0; + virtual const points_t& get_points() const = 0; virtual int get_point_count() const = 0; }; struct OTR_SPLINE_EXPORT spline_settings_mixin { - using base_settings = spline_detail::base_settings; + virtual std::shared_ptr<base_settings> get_settings() = 0; + virtual std::shared_ptr<const base_settings> get_settings() const = 0; - virtual std::shared_ptr<spline_detail::base_settings> get_settings() = 0; - virtual std::shared_ptr<const spline_detail::base_settings> get_settings() const = 0; - - virtual inline ~spline_settings_mixin(); + virtual ~spline_settings_mixin(); }; struct OTR_SPLINE_EXPORT spline_modify_mixin @@ -85,42 +79,44 @@ struct OTR_SPLINE_EXPORT spline_modify_mixin virtual void remove_point(int i) = 0; virtual void clear() = 0; - virtual inline ~spline_modify_mixin(); + virtual ~spline_modify_mixin(); }; struct OTR_SPLINE_EXPORT base_spline : base_spline_, spline_modify_mixin, spline_settings_mixin { + ~base_spline() override; }; class OTR_SPLINE_EXPORT spline : public base_spline { + using f = float; + double bucket_size_coefficient(const QList<QPointF>& points) const; - void update_interp_data(); - float get_value_internal(int x); - void add_lone_point(); - float get_value_no_save_internal(double x); + void update_interp_data() const; + double get_value_internal(int x) const; static bool sort_fn(const QPointF& one, const QPointF& two); - static QPointF ensure_in_bounds(const QList<QPointF>& points, int i); + static void ensure_in_bounds(const QList<QPointF>& points, int i, f& x, f& y); static int element_count(const QList<QPointF>& points, double max_input); void disconnect_signals(); + void invalidate_settings_(); - std::shared_ptr<spline_detail::settings> s; - QMetaObject::Connection conn_changed, conn_maxx, conn_maxy; + mutex mtx { mutex::Recursive }; + std::shared_ptr<settings> s; + QMetaObject::Connection conn_points, conn_maxx, conn_maxy; - static constexpr inline std::size_t value_count = 4096; - - std::vector<float> data = std::vector<float>(value_count, float(-16)); - - mutex _mutex { mutex::recursive }; - QPointF last_input_value; std::shared_ptr<QObject> ctx { std::make_shared<QObject>() }; - bool activep = false; - bool validp = false; + mutable QPointF last_input_value{-1, -1}; + mutable std::vector<float> data = std::vector<float>(value_count, magic_fill_value); + mutable points_t points; + mutable axis_opts::max_clamp clamp_x = axis_opts::x1000, clamp_y = axis_opts::x1000; + mutable bool activep = false; - points_t points; + static constexpr unsigned value_count = 8192; + static constexpr float magic_fill_value = -(1 << 24) + 1; + static constexpr double c_interp = 5; public: void invalidate_settings(); @@ -136,11 +132,10 @@ public: spline(const QString& name, const QString& axis_name, Axis axis); ~spline() override; - spline& operator=(const spline&) = default; spline(const spline&) = default; - float get_value(double x) override; - float get_value_no_save(double x) const override; + double get_value(double x) const override; + double get_value_no_save(double x) const override; [[nodiscard]] bool get_last_value(QPointF& point) override; void add_point(QPointF pt) override; @@ -149,24 +144,18 @@ public: void remove_point(int i) override; void clear() override; - points_t const& get_points() const override; + const points_t& get_points() const override; void set_tracking_active(bool value) override; bundle get_bundle(); - void ensure_valid(points_t& in_out); + void ensure_valid(points_t& in_out) const; - std::shared_ptr<spline_detail::base_settings> get_settings() override; - std::shared_ptr<const spline_detail::base_settings> get_settings() const override; + std::shared_ptr<base_settings> get_settings() override; + std::shared_ptr<const base_settings> get_settings() const override; int get_point_count() const override; - - using settings = spline_detail::settings; }; -inline base_spline_::~base_spline_() = default; -inline spline_modify_mixin::~spline_modify_mixin() = default; -inline spline_settings_mixin::~spline_settings_mixin() = default; - } // ns spline_detail using spline = spline_detail::spline; |