summaryrefslogtreecommitdiffhomepage
path: root/spline
diff options
context:
space:
mode:
Diffstat (limited to 'spline')
-rw-r--r--spline/CMakeLists.txt1
-rw-r--r--spline/axis-opts.cpp15
-rw-r--r--spline/axis-opts.hpp29
-rw-r--r--spline/broken/qfunctionconfiguratorplugin.h2
-rw-r--r--spline/lang/de_DE.ts4
-rw-r--r--spline/lang/zh_CN.ts2
-rw-r--r--spline/spline-widget.cpp493
-rw-r--r--spline/spline-widget.hpp76
-rw-r--r--spline/spline.cpp575
-rw-r--r--spline/spline.hpp124
10 files changed, 714 insertions, 607 deletions
diff --git a/spline/CMakeLists.txt b/spline/CMakeLists.txt
index 7d427601..731c0b6e 100644
--- a/spline/CMakeLists.txt
+++ b/spline/CMakeLists.txt
@@ -1,2 +1 @@
otr_module(spline BIN)
-target_link_libraries(opentrack-spline)
diff --git a/spline/axis-opts.cpp b/spline/axis-opts.cpp
index d7616939..a2b4941d 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,21 +27,24 @@ 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),
+ invert_post(b_settings_window, n(pfx, "invert-sign-post"), false),
+ invert_pre(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_; }
Axis axis_opts::axis() const { return axis_; }
-QString axis_opts::n(QString pfx, QString name)
+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 2f0d8432..437a5faa 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,
@@ -23,7 +29,11 @@ struct OTR_SPLINE_EXPORT axis_opts final
r15 = 15,
r10 = 10,
+ t600 = 600,
+ t300 = 300,
+ t150 = 150,
t100 = 100,
+ t75 = 75,
t30 = 30,
t20 = 20,
t15 = 15,
@@ -32,16 +42,20 @@ struct OTR_SPLINE_EXPORT axis_opts final
o_r180 = -180,
o_r90 = -90,
o_t75 = -75,
+ o_t100 = -100,
+ o_t150 = -150,
+ o_t300 = -300,
+ o_t600 = -600,
x1000 = 1000,
};
// 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;
+ value<bool> invert_pre, invert_post, altp;
value<max_clamp> clamp_x_, clamp_y_;
double max_clamp_x() const { return std::fabs(clamp_x_.to<double>()); }
double max_clamp_y() const { return std::fabs(clamp_y_.to<double>()); }
@@ -49,13 +63,8 @@ struct OTR_SPLINE_EXPORT axis_opts final
QString const& prefix() const;
Axis axis() const;
-private:
- static inline QString n(QString pfx, QString name);
-
- QString prefix_;
- Axis axis_;
};
} // ns axis_opts_impl
-using axis_opts_impl::axis_opts;
+using axis_opts = axis_opts_impl::axis_opts;
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/lang/de_DE.ts b/spline/lang/de_DE.ts
new file mode 100644
index 00000000..1552582e
--- /dev/null
+++ b/spline/lang/de_DE.ts
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+</TS>
diff --git a/spline/lang/zh_CN.ts b/spline/lang/zh_CN.ts
index 6401616d..e5ca8aa9 100644
--- a/spline/lang/zh_CN.ts
+++ b/spline/lang/zh_CN.ts
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
-<TS version="2.1">
+<TS version="2.1" language="zh_CN">
</TS>
diff --git a/spline/spline-widget.cpp b/spline/spline-widget.cpp
index 638b67a7..46e2095c 100644
--- a/spline/spline-widget.cpp
+++ b/spline/spline-widget.cpp
@@ -1,62 +1,59 @@
-/* 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 <algorithm>
+
#include <QPainter>
#include <QPainterPath>
-#include <QPaintEvent>
-#include <QPen>
#include <QPixmap>
-#include <QList>
-#include <QPoint>
#include <QString>
-#include <QRect>
-#include <QApplication>
-#include <QStyle>
-#include <QMouseEvent>
+#include <QToolTip>
+#include <QtEvents>
+#include <QPainterPath>
#include <QDebug>
-#include <cmath>
-#include <algorithm>
+#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
+# define OTR_OBSOLETE_QT_WORKAROUND
+#endif
+
+namespace spline_detail {
spline_widget::spline_widget(QWidget *parent) : QWidget(parent)
{
setMouseTracking(true);
- setFocusPolicy(Qt::ClickFocus);
+ //setFocusPolicy(Qt::ClickFocus);
setCursor(Qt::ArrowCursor);
}
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(), &spline::base_settings::recomputed,
- this, [this]() { reload_spline(); },
+ std::shared_ptr<base_settings> s = spl->get_settings();
+ connection = connect(&*s, &base_settings::recomputed,
+ this, [this] { reload_spline(); },
Qt::QueuedConnection);
}
+
+ reload_spline();
}
QColor spline_widget::colorBezier() const
@@ -64,7 +61,7 @@ QColor spline_widget::colorBezier() const
return spline_color;
}
-void spline_widget::setColorBezier(QColor color)
+void spline_widget::setColorBezier(QColor const& color)
{
spline_color = color;
repaint();
@@ -72,37 +69,38 @@ void spline_widget::setColorBezier(QColor 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);
- QFontMetrics metrics(font);
+ const QFontMetricsF metrics(font);
QColor color__(176, 190, 209, 127);
@@ -111,66 +109,80 @@ void spline_widget::drawBackground()
const QPen pen(color__, 1, Qt::SolidLine, Qt::FlatCap);
- const int ystep = _y_step, xstep = _x_step;
- const qreal maxx = _config->max_input();
- const qreal 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();
+#ifndef OTR_OBSOLETE_QT_WORKAROUND
+ double space_width = metrics.horizontalAdvance(' ');
+#else
+ double space_width = metrics.boundingRect(' ').right();
+#endif
+ painter.setPen(palette().text().color());
- // horizontal grid
+ // vertical grid
for (int i = 0; i <= maxy; i += ystep)
{
- const int y = int(pixel_bounds.height() - i * c.y() + pixel_bounds.y());
+ const double y = pixel_bounds.height() - i * c.y() + pixel_bounds.y();
drawLine(painter,
- QPoint(pixel_bounds.x(), y),
- QPoint(pixel_bounds.x() + pixel_bounds.width(), y),
+ QPointF(pixel_bounds.x(), y),
+ QPointF(pixel_bounds.x() + pixel_bounds.width(), y),
pen);
- painter.drawText(QRectF(10,
- y - metrics.height()/2.,
- pixel_bounds.left(),
- metrics.height()),
- QString::number(i));
+ QString text = QString::number(i);
+ QRectF rect = metrics.boundingRect(text);
+#ifndef OTR_OBSOLETE_QT_WORKAROUND
+ double advance = metrics.horizontalAdvance(text);
+#else
+ double advance = rect.right();
+#endif
+ painter.drawText(QPointF(pixel_bounds.x() - advance - space_width,
+ y - rect.height()/2 - rect.top()),
+ text);
}
- // vertical grid
+ // horizontal grid
for (int i = 0; i <= maxx; i += xstep)
{
- const int x = iround(pixel_bounds.x() + i * c.x());
+ const double x = pixel_bounds.x() + i * c.x();
drawLine(painter,
- QPoint(x, pixel_bounds.y()),
- QPoint(x, pixel_bounds.y() + pixel_bounds.height()),
+ 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()),
+ QRectF rect = metrics.boundingRect(text);
+#ifndef OTR_OBSOLETE_QT_WORKAROUND
+ double advance = metrics.horizontalAdvance(text);
+#else
+ double advance = metrics.boundingRect(text).right();
+#endif
+ painter.drawText(QPointF(x - advance/2 - rect.left(),
+ pixel_bounds.bottom() + metrics.lineSpacing()),
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())
{
const QPen pen(Qt::white, 1, Qt::SolidLine, Qt::FlatCap);
- const QPointF prev_ = point_to_pixel(QPointF(0, 0));
- QPoint prev(iround(prev_.x()), iround(prev_.y()));
- for (int i = 0; i < points.size(); i++)
+ QPointF prev = point_to_pixel({});
+ for (const auto& point : points)
{
- const QPointF tmp_ = point_to_pixel(points[i]);
- const QPoint tmp(iround(tmp_.x()), iround(tmp_.y()));
+ const QPointF tmp = point_to_pixel(point);
drawLine(painter, prev, tmp, pen);
prev = tmp;
}
}
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,57 +199,58 @@ void spline_widget::drawFunction()
}
);
- painter.setPen(QPen(color_, 1.75, Qt::SolidLine, Qt::FlatCap));
+ painter.setPen(QPen(color_, 2, Qt::SolidLine, Qt::FlatCap));
-//#define DEBUG_SPLINE
-#ifndef DEBUG_SPLINE
- constexpr double step_ = 5;
-
- const double maxx = _config->max_input();
- const double step = step_ / c.x();
+ const double dpr = screen_dpi();
+ const double line_length_pixels = std::fmax(1, dpr);
+ const double step = std::fmax(.1, line_length_pixels / c.x());
+ const double maxx = config->max_input();
+#define USE_CUBIC_SPLINE
+#if defined USE_CUBIC_SPLINE
QPainterPath path;
- path.moveTo(point_to_pixel(QPointF(0, 0)));
+ path.moveTo(point_to_pixel({}));
- const double max_x_pixel = point_to_pixel_(QPointF(maxx, 0)).x();
+ const double max_x_pixel = point_to_pixel({maxx, 0}).x();
- auto check = [=](const QPointF& val) {
- return val.x() < max_x_pixel
+ auto clamp = [=](const QPointF& val) {
+ return val.x() <= max_x_pixel
? val
- : QPointF(max_x_pixel, val.y());
+ : QPointF{max_x_pixel, val.y()};
};
- for (double k = 0; k < maxx; k += step*3)
- {
- const float next_1(_config->get_value_no_save(k + step*1));
- const float next_2(_config->get_value_no_save(k + step*2));
- const float next_3(_config->get_value_no_save(k + step*3));
+ const auto fn = [&] (double k) {
+ 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(check(point_to_pixel_(QPointF(k + step*1, qreal(next_1))))),
- c(check(point_to_pixel_(QPointF(k + step*2, qreal(next_2))))),
- d(check(point_to_pixel_(QPointF(k + step*3, qreal(next_3)))));
+ QPointF b(clamp(point_to_pixel({k + step*1, next_1}))),
+ c(clamp(point_to_pixel({k + step*2, next_2}))),
+ d(clamp(point_to_pixel({k + step*3, next_3})));
- path.cubicTo(b, c, d);
- }
+ path.cubicTo(b, c, d);
+ };
+
+ for (double k = 0; k < maxx; k += step*3) // NOLINT
+ fn(k);
+
+ fn(maxx);
painter.drawPath(path);
#else
- constexpr int line_length_pixels = 3;
- const qreal max = _config->maxInput();
- const qreal step = clamp(line_length_pixels / c.x(), 5e-2, max);
- QPointF prev = point_to_pixel(QPoint(0, 0));
- for (qreal i = 0; i < max; i += step)
+ QPointF prev = point_to_pixel({});
+ for (double i = 0; i < maxx; i += step) // NOLINT
{
- const qreal val = qreal(_config->get_value_no_save(i));
- const QPointF cur = point_to_pixel(QPointF(i, val));
+ const auto val = config->get_value_no_save(i);
+ const QPointF cur = point_to_pixel({i, val});
painter.drawLine(prev, cur);
prev = cur;
}
{
- const qreal maxx = _config->maxInput();
- const qreal maxy = qreal(_config->get_value_no_save(maxx));
- painter.drawLine(QPointF(prev), point_to_pixel_(QPointF(maxx, maxy)));
+ const double maxx = config->max_input();
+ const double maxy = config->get_value_no_save(maxx);
+ painter.drawLine(prev, point_to_pixel({ maxx, maxy }));
}
#endif
@@ -250,51 +263,59 @@ void spline_widget::drawFunction()
painter.fillRect(r2, widget_bg_color);
const int alpha = !isEnabled() ? 64 : 120;
- if (!_preview_only)
+ if (!preview_only)
{
- for (int i = 0; i < points.size(); i++)
+ for (auto const& point : points)
{
drawPoint(painter,
- point_to_pixel(points[i]),
+ point_to_pixel(point),
QColor(200, 200, 210, alpha),
isEnabled() ? QColor(50, 100, 120, 200) : QColor(200, 200, 200, 96));
}
}
}
+QSize spline_widget::minimumSizeHint() const
+{
+ const double dpi = screen_dpi();
+ return { iround(800 * dpi), iround(250 * dpi) };
+}
+
void spline_widget::paintEvent(QPaintEvent *e)
{
- if (!_config)
+ if (!config)
return;
QPainter p(this);
const double dpr = devicePixelRatioF();
- const int W = int(width() * dpr);
- const int H = int(height() * dpr);
+ 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));
}
@@ -303,13 +324,13 @@ void spline_widget::drawPoint(QPainter& painter, const QPointF& pos, const QColo
painter.save();
painter.setPen(QPen(border, 1, Qt::SolidLine, Qt::PenCapStyle::FlatCap));
painter.setBrush(colBG);
- painter.drawEllipse(QRectF(pos.x() - point_size,
- pos.y() - point_size,
- point_size*2, point_size*2));
+ painter.drawEllipse(QRectF{pos.x() - point_size_in_pixels,
+ pos.y() - point_size_in_pixels,
+ point_size_in_pixels*2, point_size_in_pixels*2});
painter.restore();
}
-void spline_widget::drawLine(QPainter& painter, const QPoint& start, const QPoint& end, const QPen& pen)
+void spline_widget::drawLine(QPainter& painter, const QPointF& start, const QPointF& end, const QPen& pen)
{
painter.save();
painter.setPen(pen);
@@ -320,17 +341,14 @@ void spline_widget::drawLine(QPainter& painter, const QPoint& start, const QPoin
void spline_widget::mousePressEvent(QMouseEvent *e)
{
- if (!_config || !isEnabled() || !is_in_bounds(e->pos()) || _preview_only)
- {
- clearFocus();
+ if (!config || !isEnabled() || !is_in_bounds(e->localPos()) || preview_only)
return;
- }
- const int point_pixel_closeness_limit = get_closeness_limit();
+ 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)
{
@@ -338,7 +356,7 @@ void spline_widget::mousePressEvent(QMouseEvent *e)
for (int i = 0; i < points.size(); i++)
{
- if (point_within_pixel(points[i], e->pos()))
+ if (point_within_pixel(points[i], e->localPos()))
{
is_touching_point = true;
moving_control_point_idx = i;
@@ -349,13 +367,12 @@ void spline_widget::mousePressEvent(QMouseEvent *e)
if (!is_touching_point)
{
bool too_close = false;
- const QPoint pos = e->pos();
+ const QPointF pos = pixel_to_point(e->localPos());
- for (int i = 0; i < points.size(); i++)
+ for (QPointF const& point : points)
{
- const QPointF pt = point_to_pixel(points[i]);
- const qreal x = std::fabs(pt.x() - pos.x());
- if (point_pixel_closeness_limit >= x)
+ const double x = std::fabs(point.x() - pos.x());
+ if (min_dist > x)
{
too_close = true;
break;
@@ -364,93 +381,91 @@ void spline_widget::mousePressEvent(QMouseEvent *e)
if (!too_close)
{
- _config->add_point(pixel_coord_to_point(e->pos()));
+ 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)
{
- int found_pt = -1;
for (int i = 0; i < points.size(); i++)
{
- if (point_within_pixel(points[i], e->pos()))
+ if (point_within_pixel(points[i], e->localPos()))
{
- found_pt = i;
+ config->remove_point(i);
+ draw_function = true;
break;
}
}
-
- if (found_pt != -1)
- {
- _config->remove_point(found_pt);
- _draw_function = true;
- }
}
}
- 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());
- clearFocus();
return;
}
- if (!_config || !isEnabled() || !isActiveWindow() || (moving_control_point_idx != -1 && !hasFocus()))
+ if (!config || !isEnabled() || !isActiveWindow())
{
- clearFocus();
+ 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)
{
- const int point_closeness_limit = get_closeness_limit();
- bool overlap = false;
+ const double min_dist = min_pt_distance();
+ QPointF new_pt = pixel_to_point(e->localPos());
+
+ const bool has_prev = i > 0, has_next = i + 1 < points.size();
- const QPointF pix_ = point_to_pixel(pixel_coord_to_point(e->pos()));
- const QPoint pix(int(pix_.x()), int(pix_.y()));
+ auto check_next = [&] {
+ return points[i+1].x() - new_pt.x() >= min_dist;
+ };
- QPointF new_pt = pixel_coord_to_point(e->pos());
+ auto check_prev = [&] {
+ return new_pt.x() - points[i-1].x() >= min_dist;
+ };
- if (i + 1 < points.size())
+ if (has_prev && !check_prev())
{
- overlap |= points[i+1].x() - new_pt.x() < point_closeness_limit;
+ new_pt.rx() = points[i-1].x() + min_dist + 1e-4;
}
- if (i > 0)
+
+ if (has_next && !check_next())
{
- overlap |= new_pt.x() - points[i-1].x() < point_closeness_limit;
+ new_pt.rx() = points[i+1].x() - min_dist - 1e-4;
}
- if (overlap)
- new_pt = QPointF(points[i].x(), new_pt.y());
-
- _config->move_point(i, new_pt);
-
- _draw_function = true;
- repaint();
-
setCursor(Qt::ClosedHandCursor);
- show_tooltip(pix, new_pt);
+ show_tooltip(point_to_pixel(new_pt).toPoint(), new_pt);
+
+ if ((!has_prev || check_prev()) && (!has_next || check_next()))
+ {
+ config->move_point(i, new_pt);
+ draw_function = true;
+ repaint();
+ }
}
else if (sz)
{
int i;
- bool is_on_point = is_on_pt(e->pos(), &i);
+ bool is_on_point = is_on_pt(e->localPos(), &i);
if (is_on_point)
{
@@ -460,7 +475,7 @@ void spline_widget::mouseMoveEvent(QMouseEvent *e)
else
{
setCursor(Qt::ArrowCursor);
- if (is_in_bounds(e->pos()))
+ if (is_in_bounds(e->localPos()))
show_tooltip(e->pos());
else
QToolTip::hideText();
@@ -470,11 +485,8 @@ void spline_widget::mouseMoveEvent(QMouseEvent *e)
void spline_widget::mouseReleaseEvent(QMouseEvent *e)
{
- if (!_config || !isEnabled() || !isActiveWindow() || !hasFocus() || _preview_only)
- {
- clearFocus();
+ if (!config || !isEnabled() || !isActiveWindow() || preview_only)
return;
- }
const bool redraw = moving_control_point_idx != -1;
moving_control_point_idx = -1;
@@ -482,13 +494,13 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e)
if (e->button() == Qt::LeftButton)
{
{
- if (is_on_pt(e->pos(), nullptr))
+ if (is_on_pt(e->localPos(), nullptr))
setCursor(Qt::CrossCursor);
else
setCursor(Qt::ArrowCursor);
}
- if (is_in_bounds(e->pos()))
+ if (is_in_bounds(e->localPos()))
show_tooltip(e->pos());
else
QToolTip::hideText();
@@ -496,7 +508,7 @@ void spline_widget::mouseReleaseEvent(QMouseEvent *e)
if (redraw)
{
- _draw_function = true;
+ draw_function = true;
repaint();
}
}
@@ -504,49 +516,53 @@ 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();
}
-int spline_widget::get_closeness_limit()
+double spline_widget::min_pt_distance() const
{
- return iround(std::fmax(snap_x, 1));
+ double pt = 3*point_size_in_pixels / c.x();
+ pt = snap(pt, snap_x);
+ return pt;
}
void spline_widget::show_tooltip(const QPoint& pos, const QPointF& value_)
{
- const QPointF value = QPoint(0, 0) == value_ ? pixel_coord_to_point(pos) : value_;
+ const QPointF value = value_.isNull() ? pixel_to_point(pos) : 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);
- using std::fabs;
-
- if (fabs(x_ - x) < 1e-3)
+ if (std::fabs(x_ - x) < 1e-3)
x = x_;
- if (fabs(y_ - y) < 1e-3)
+ if (std::fabs(y_ - y) < 1e-3)
y = y_;
- const bool is_fusion = QStringLiteral("fusion") == QApplication::style()->objectName();
- const int add_x = (is_fusion ? 25 : 0), add_y = (is_fusion ? 15 : 0);
+ // the native OSX style doesn't look right otherwise
+#if defined __APPLE__
+ constexpr int off_x = 0, off_y = 0;
+#else
+ constexpr int off_x = 25, off_y = 15;
+#endif
- const QPoint pix(pos.x() + add_x, pos.y() + add_y);
+ const QPoint pix(pos.x() + off_x, pos.y() + off_y);
QToolTip::showText(mapToGlobal(pix),
- QStringLiteral("value: %1x%2").arg(x, 0, 'f', 2).arg(y, 0, 'f', 2),
+ QString{"value: %1x%2"}.arg(x, 0, 'f', 2).arg(y, 0, 'f', 2),
this,
rect(),
0);
}
-bool spline_widget::is_in_bounds(const QPoint& pos) const
+bool spline_widget::is_in_bounds(const QPointF& pos) const
{
- constexpr int grace = point_size * 3;
- constexpr int bottom_grace = int(point_size * 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() &&
@@ -555,27 +571,28 @@ bool spline_widget::is_in_bounds(const QPoint& 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 = QPointF(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 QPoint &pixel)
+bool spline_widget::point_within_pixel(const QPointF& pt, const QPointF& pixel)
{
const QPointF tmp = pixel - point_to_pixel(pt);
- return QPointF::dotProduct(tmp, tmp) < point_size * point_size;
+ return QPointF::dotProduct(tmp, tmp) < point_size_in_pixels * point_size_in_pixels;
}
void spline_widget::focusOutEvent(QFocusEvent* e)
@@ -583,76 +600,66 @@ 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();
}
-QPointF spline_widget::pixel_coord_to_point(const QPoint& point)
+double spline_widget::snap(double x, double snap_value)
{
- qreal x = (point.x() - pixel_bounds.x()) / c.x();
- qreal y = (pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y();
-
- constexpr int c = 1000;
-
- if (snap_x > 0)
+ if (snap_value > 0)
{
- x += snap_x * .5;
- x -= std::fmod(x, snap_x);
+ constexpr int c = 1000;
+ x += snap_value * .5;
+ x -= std::fmod(x, snap_value);
// truncate after few decimal places to reduce rounding errors.
- // round upward to nearest.
+ // round upward.
x = int(x * c + .5/c) / double(c);
}
- if (snap_y > 0)
- {
- y += snap_y * .5;
- y -= std::fmod(y, snap_y);
- // idem
- y = int(y * c + .5/c) / double(c);
- }
-
- if (x < 0)
- x = 0;
- if (x > _config->max_input())
- x = _config->max_input();
-
- if (y < 0)
- y = 0;
- if (y > _config->max_output())
- y = _config->max_output();
- return QPointF(x, y);
+ return x;
}
-QPointF spline_widget::point_to_pixel_(const QPointF& point)
+QPointF spline_widget::pixel_to_point(const QPointF& point)
{
- return QPointF(pixel_bounds.x() + point.x() * c.x(),
- pixel_bounds.y() + pixel_bounds.height() - point.y() * c.y());
+ double x = (point.x() - pixel_bounds.x()) / c.x();
+ double y = (pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y();
+
+ if (snap_x > 0)
+ x = snap(x, snap_x);
+ if (snap_y > 0)
+ y = snap(y, snap_y);
+
+ x = std::clamp(x, 0., config->max_input());
+ y = std::clamp(y, 0., config->max_output());
+
+ return { x, y };
}
-QPoint spline_widget::point_to_pixel(const QPointF& point)
+QPointF spline_widget::point_to_pixel(const QPointF& point)
{
- QPointF pt(point_to_pixel_(point));
-
- return QPoint(iround(pt.x()), iround(pt.y()));
+ return {
+ pixel_bounds.x() + point.x() * c.x(),
+ pixel_bounds.y() + pixel_bounds.height() - point.y() * c.y()
+ };
}
void spline_widget::resizeEvent(QResizeEvent *)
{
- update_range();
+ reload_spline();
}
-bool spline_widget::is_on_pt(const QPoint& pos, int* pt)
+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++)
{
@@ -668,3 +675,17 @@ bool spline_widget::is_on_pt(const QPoint& 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 12d21970..610baf43 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
@@ -11,23 +11,23 @@
#include "spline.hpp"
#include "api/plugin-api.hpp"
+#include "compat/qt-dpi.hpp"
#include "options/options.hpp"
-using namespace options;
#include "export.hpp"
+#include <cmath>
+
#include <QWidget>
-#include <QRect>
-#include <QPoint>
-#include <QPointF>
-#include <QToolTip>
-#include <QShowEvent>
-#include <QFocusEvent>
#include <QMetaObject>
#include <QDebug>
-class OTR_SPLINE_EXPORT spline_widget final : public QWidget
+namespace spline_detail {
+
+using namespace options;
+
+class OTR_SPLINE_EXPORT spline_widget final : public QWidget, public screen_dpi_mixin<spline_widget>
{
Q_OBJECT
Q_PROPERTY(QColor colorBezier READ colorBezier WRITE setColorBezier)
@@ -35,27 +35,28 @@ 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 = spline::points_t;
public:
- spline_widget(QWidget *parent = 0);
- ~spline_widget();
+ 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 color);
+ void setColorBezier(QColor const& color);
void force_redraw();
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; }
+
+ QSize minimumSizeHint() const override;
public slots:
void reload_spline();
protected slots:
@@ -64,33 +65,35 @@ protected slots:
void mouseMoveEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
private:
- int get_closeness_limit();
+ double min_pt_distance() const;
void show_tooltip(const QPoint& pos, const QPointF& value = QPointF(0, 0));
- bool is_in_bounds(const QPoint& pos) const;
+ bool is_in_bounds(const QPointF& pos) const;
void drawBackground();
void drawFunction();
void drawPoint(QPainter& painter, const QPointF& pt, const QColor& colBG, const QColor& border = QColor(50, 100, 120, 200));
- void drawLine(QPainter& painter, const QPoint& start, const QPoint& end, const QPen& pen);
- bool point_within_pixel(const QPointF& pt, const QPoint& pixel);
+ void drawLine(QPainter& painter, const QPointF& start, const QPointF& end, const QPen& pen);
+ bool point_within_pixel(const QPointF& pt, const QPointF& pixel);
void focusOutEvent(QFocusEvent*e) override;
void resizeEvent(QResizeEvent *) override;
- bool is_on_pt(const QPoint& pos, int* pt = nullptr);
+ bool is_on_pt(const QPointF& pos, int* pt = nullptr);
void update_range();
- QPointF pixel_coord_to_point(const QPoint& point);
+ void changeEvent(QEvent* e) override;
+
+ QPointF pixel_to_point(const QPointF& point);
+ QPointF point_to_pixel(const QPointF& point);
- QPointF point_to_pixel_(const QPointF& point);
- QPoint point_to_pixel(const QPointF& point);
+ 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();
+ QColor widget_bg_color = palette().window().color();
// bounds of the rectangle user can interact with
QRect pixel_bounds;
@@ -98,9 +101,16 @@ 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 int point_size_in_pixels_ = 5;
- static constexpr inline int point_size = 4;
+ const double point_size_in_pixels = point_size_in_pixels_ * screen_dpi();
};
+
+} // ns spline_detail
+
+using spline_widget = spline_detail::spline_widget;
diff --git a/spline/spline.cpp b/spline/spline.cpp
index fc77bf8b..466a9a7f 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,200 +10,211 @@
#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) :
- axis(axis)
+spline::spline(const QString& name, const QString& axis_name, Axis axis)
{
set_bundle(options::make_bundle(name), axis_name, axis);
}
spline::~spline()
{
- QMutexLocker l(&_mutex);
-
- if (connection)
- {
- QObject::disconnect(connection);
- QObject::disconnect(conn_maxx);
- QObject::disconnect(conn_maxy);
- connection = QMetaObject::Connection();
- conn_maxx = QMetaObject::Connection();
- conn_maxy = QMetaObject::Connection();
- }
+ QMutexLocker l(&mtx);
+ disconnect_signals();
}
-spline::spline() : spline("", "", Axis(-1)) {}
+spline::spline() : spline(QString{}, QString{}, Axis(-1)) {}
-void spline::set_tracking_active(bool value)
+void spline::set_tracking_active(bool value) const
{
- QMutexLocker l(&_mutex);
- activep = value;
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker l(&mtx);
+ if (value == activep)
+ return;
+ activep = value;
+ S = s;
+ }
+ 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()
{
- QMutexLocker l(&_mutex);
- s->points = points_t();
- validp = false;
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker l(&mtx);
+ S = s;
+ s->points = {};
+ points = {};
+ update_interp_data();
+ }
+ 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
-{
- return const_cast<spline&>(*this).get_value_no_save_internal(x);
-}
-
-float spline::get_value_no_save_internal(double x)
+double spline::get_value_no_save(double x) const
{
- QMutexLocker foo(&_mutex);
+ QMutexLocker l(&mtx);
- float q = float(x * bucket_size_coefficient(s->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;
}
-warn_result_unused bool spline::get_last_value(QPointF& point)
+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);
+ const auto sign = (f)signum(x);
x = std::abs(x);
- const float ret_ = data[std::min(unsigned(x), unsigned(value_count)-1u)];
- return sign * clamp(ret_, 0, 1000);
-}
-
-void spline::add_lone_point()
-{
- points_t points;
- points.push_back(QPointF(s->opts.clamp_x_, s->opts.clamp_y_));
-
- s->points = points;
+ const auto ret_ = data[std::min(unsigned(x), value_count - 1)];
+ return (double)(sign * std::clamp(ret_, (f)0, (f)1000));
}
-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 QPointF(0, 0);
-
- 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 int sz = 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;
}
-bool spline::sort_fn(const QPointF& one, const QPointF& two)
+bool spline::sort_fn(QPointF one, QPointF two)
{
return one.x() < two.x();
}
-void spline::update_interp_data()
+void spline::update_interp_data() const
{
- points_t points = s->points;
- ensure_valid(points);
- const int sz = points.size();
+ points_t list = points;
+ ensure_valid(list);
+ int sz = list.size();
- const double maxx = max_input();
-
- if (sz == 0)
- points.prepend(QPointF(maxx, max_output()));
+ if (list.isEmpty())
+ list.prepend({ max_input(), max_output() });
- std::stable_sort(points.begin(), points.begin() + sz, sort_fn);
-
- const double c = bucket_size_coefficient(points);
- const double c_interp = c * 30;
+ const double c = bucket_size_coefficient(list);
+ const double c_ = c * c_interp;
+ const f cf = (f)c, c_f = (f)c_;
for (unsigned i = 0; i < value_count; i++)
- data[i] = -16;
+ data[i] = magic_fill_value;
- if (sz < 2)
+ if (sz < 2) // lerp only
{
- if (points[0].x() - 1e-2 < maxx)
+ const QPointF& pt = list[0];
+ const double x = pt.x();
+ const double y = pt.y();
+ const unsigned max = std::clamp((unsigned)iround(x * c), 1u, value_count-1);
+
+ for (unsigned k = 0; k <= max; k++)
+ data[k] = f(y * k / max); // no need for bresenham
+ }
+ else if (sz == 2 && list[0].y() < 1e-6)
+ {
+ unsigned start = std::clamp((unsigned)iround(list[0].x() * c), 1u, value_count-1);
+ unsigned end = std::clamp((unsigned)iround(list[1].x() * c), 2u, value_count-1);
+ unsigned max = end - start;
+ for (unsigned x = 0; x < start; x++)
+ data[x] = 0;
+ for (unsigned x = 0; x < max; x++)
+ data[start + x] = (f)(list[1].y() * x / max);
+ }
+ else
+ {
+ if (list[0].x() > 1e-6)
{
- const double x = points[0].x();
- const double y = points[0].y();
- const unsigned max = (unsigned)clamp(iround(x * c), 1, value_count-1);
- for (unsigned k = 0; k <= max; k++)
+ double zero_pos = 0;
+ while (list.size() > 1 && list[0].y() <= 1e-6)
{
- if (k < value_count)
- data[unsigned(k)] = float(y * k / max);
+ zero_pos = list[0].x();
+ list.pop_front();
}
+ list.push_front({zero_pos, 0});
+ sz = list.size();
}
- }
- else
- {
- if (points[0].x() > 1e-2 && points[0].x() <= maxx)
- points.push_front(QPointF(0, 0));
+ // now this is hella expensive due to `c_interp'
for (int i = 0; i < sz; i++)
{
- const QPointF p0 = ensure_in_bounds(points, i - 1);
- const QPointF p1 = ensure_in_bounds(points, i + 0);
- const QPointF p2 = ensure_in_bounds(points, i + 1);
- const QPointF p3 = ensure_in_bounds(points, 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
@@ -219,275 +222,315 @@ 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_f * (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 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));
-
- if (unsigned(x) < value_count)
- data[x] = y;
+ const f t = k / end_;
+ const f t2 = t*t;
+ const f t3 = t*t*t;
+
+ const auto x = (unsigned)(f(.5) * cf * (cx[0] + cx[1] * t + cx[2] * t2 + cx[3] * t3));
+ const auto y = (f)(f(.5) * (cy[0] + cy[1] * t + cy[2] * t2 + cy[3] * t3));
+
+ switch (int ret = std::fpclassify(y))
+ {
+ case FP_INFINITE:
+ case FP_NAN:
+ case FP_SUBNORMAL:
+ eval_once(qDebug() << "spline: fpclassify" << y
+ << "returned" << ret
+ << "for bundle" << s->b->name());
+ continue;
+ case FP_ZERO:
+ case FP_NORMAL:
+ if (x < value_count)
+ data[x] = y;
+ break;
+ default:
+ unreachable();
+ }
}
}
}
- float last = 0;
- for (unsigned i = 0; i < unsigned(value_count); i++)
+ auto maxy = (f)max_output();
+ auto last = (f)0;
+
+#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] == -16)
+ if (data[i] == magic_fill_value)
data[i] = last;
+ data[i] = std::clamp(data[i], (f)0, (f)maxy);
last = data[i];
}
+
+ // make sure empty places stay empty (see #1341)
+ if (auto it = std::find_if(list.cbegin(), list.cend(),
+ [](QPointF x) { return x.x() >= (f)1e-6 && x.y() >= (f)1e-6; });
+ it != list.cend() && it != list.cbegin())
+ {
+ it--;
+ unsigned max = std::clamp((unsigned)iround(it->x() * c), 0u, value_count-1);
+
+ for (unsigned x = 0; x < max; x++)
+ data[x] = 0;
+ }
+#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;
- points_t points = s->points;
- 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_t points = s->points;
- 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);
-
- points_t points = s->points;
+ 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;
- // we don't allow points to be reordered, but sort due to possible caller logic error
- 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();
}
-spline::points_t spline::get_points() const
+const points_t& spline::get_points() const
{
- QMutexLocker foo(&_mutex);
- return s->points;
+ return points;
}
int spline::get_point_count() const
{
- QMutexLocker foo(&_mutex);
- return element_count(s->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
-
- QMutexLocker l(&_mutex);
- validp = false;
+ points = s->points;
+ clamp_x = s->opts.clamp_x_;
+ clamp_y = s->opts.clamp_y_;
+ update_interp_data();
+}
- emit s->recomputed();
+void spline::invalidate_settings()
+{
+ std::shared_ptr<settings> S;
+ {
+ QMutexLocker l(&mtx);
+ S = s;
+ invalidate_settings_();
+ }
+ emit S->recomputed();
}
void spline::set_bundle(bundle b, const QString& axis_name, Axis axis)
{
- QMutexLocker l(&_mutex);
+ if (!b)
+ b = make_bundle({});
- // 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)
- {
- s = std::make_shared<settings>(b, axis_name, axis);
+ std::shared_ptr<settings> S;
- if (connection)
- {
- QObject::disconnect(connection);
- QObject::disconnect(conn_maxx);
- QObject::disconnect(conn_maxy);
- }
-
- if (b)
- {
- connection = QObject::connect(b.get(), &bundle_::changed,
- s.get(), [&]() { invalidate_settings(); });
-
- // this isn't strictly necessary for the spline but helps the widget
- conn_maxx = QObject::connect(&s->opts.clamp_x_, base_value::value_changed<int>(),
- ctx.get(), [&](double) { invalidate_settings(); });
- conn_maxy = QObject::connect(&s->opts.clamp_y_, base_value::value_changed<int>(),
- ctx.get(), [&](double) { invalidate_settings(); });
- }
+ {
+ QMutexLocker l(&mtx);
- validp = false;
+ disconnect_signals();
+ s = std::make_shared<settings>(b, axis_name, axis);
+ invalidate_settings_();
+ S = s;
+
+ conn_points = QObject::connect(&s->points, value_::value_changed<QList<QPointF>>(),
+ &*ctx, [this] { invalidate_settings(); }, Qt::DirectConnection);
+ conn_maxx = QObject::connect(&s->opts.clamp_x_, value_::value_changed<int>(),
+ &*ctx, [this](double) { invalidate_settings(); }, Qt::DirectConnection);
+ conn_maxy = QObject::connect(&s->opts.clamp_y_, value_::value_changed<int>(),
+ &*ctx, [this](double) { invalidate_settings(); }, Qt::DirectConnection);
}
+
+ emit S->recomputed();
}
double spline::max_input() const
{
- QMutexLocker l(&_mutex);
- if (s)
- {
- using m = axis_opts::max_clamp;
- const value<m>& clamp = s->opts.clamp_x_;
- const QList<QPointF> points = s->points;
- if (clamp == m::x1000 && points.size())
- return points[points.size() - 1].x();
- return s ? std::fabs(clamp.to<double>()) : 0;
- }
- return 0;
+ 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);
- if (s)
- {
- using m = axis_opts::max_clamp;
- const value<m>& clamp = s->opts.clamp_y_;
- const QList<QPointF> points = s->points;
- if (clamp == m::x1000 && points.size())
- return points[points.size() - 1].y();
- return s ? std::fabs(clamp.to<double>()) : 0;
- }
- return 0;
+ 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(QList<QPointF>& the_points)
+void spline::ensure_valid(points_t& list) const
{
- QMutexLocker foo(&_mutex);
-
- QList<QPointF> list = the_points;
-
- // 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> ret_list, ret_list_2;
- ret_list.reserve(sz), ret_list_2.reserve(sz);
+ QList<QPointF> tmp_points, all_points;
+ tmp_points.reserve(sz); all_points.reserve(sz);
- const double maxx = max_input(), maxy = max_output();
+ const double maxx = max_input();
- for (int i = 0; i < sz; i++)
+ for (unsigned i = 0; i < sz; i++)
{
- QPointF& pt(list[i]);
+ QPointF& pt{list[i]};
- const bool overlap = progn(
- for (int j = 0; j < i; j++)
+ bool overlap = false;
+ for (unsigned j = i+1; j < sz; j++)
+ {
+ const QPointF& pt2{list[j]};
+ const QPointF tmp(pt - pt2);
+ const double dist_sq = QPointF::dotProduct(tmp, tmp);
+ constexpr double min_dist = 1e-4;
+ if (dist_sq < min_dist)
{
- const QPointF& pt2(list[j]);
- const QPointF tmp(pt - pt2);
- const double dist_sq = QPointF::dotProduct(tmp, tmp);
- const double overlap = maxx / 500.;
- if (dist_sq < overlap * overlap)
- return true;
+ overlap = true;
+ break;
}
- return false;
- );
+ }
if (!overlap)
- ret_list_2.append(pt);
-
- if (pt.x() - 1e-2 < maxx && pt.x() >= 0 &&
- pt.y() - 1e-2 < maxy && pt.y() >= 0 && !overlap)
{
- ret_list.push_back(pt);
+ 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)
+ tmp_points.push_back(pt);
}
}
- if (ret_list != the_points)
+ // simply storing to s->points fires bundle::changed leading to a livelock
+ // hence only store if we can't help it
+
+ if (all_points.size() < points.size())
{
- s->points = std::move(ret_list_2);
- the_points = std::move(ret_list);
+ // all points that don't overlap
+ points = std::move(all_points);
+ s->points = points;
}
- last_input_value = QPointF(0, 0);
+ 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;
- return clamp((value_count-1) / clamp(last_x, eps, maxx), 0., (value_count-1));
+ return std::clamp((value_count-1) / std::clamp(last_x, eps, maxx), 0., (value_count-1.));
}
-namespace spline_detail {
-
-settings::settings(bundle b, const QString& axis_name, Axis idx):
- b(b ? b : make_bundle("")),
- points(b, "points", {}),
- opts(axis_name, idx)
-{}
-
-settings::~settings()
+void spline::disconnect_signals()
{
+ if (conn_points)
+ {
+ QObject::disconnect(conn_points); conn_points = {};
+ QObject::disconnect(conn_maxx); conn_maxx = {};
+ QObject::disconnect(conn_maxy); conn_maxy = {};
+ }
}
-}
+settings::settings(bundle const& b, const QString& axis_name, Axis idx):
+ b(b ? b : make_bundle({})),
+ opts(axis_name, idx)
+{}
+} // ns spline_detail
diff --git a/spline/spline.hpp b/spline/spline.hpp
index a3532855..780442b9 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,19 +8,15 @@
#pragma once
-#include "compat/copyable-mutex.hpp"
#include "options/options.hpp"
-using namespace options;
-
#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>
@@ -29,6 +25,9 @@ using namespace options;
namespace spline_detail {
+using points_t = QList<QPointF>;
+using namespace options;
+
class OTR_SPLINE_EXPORT base_settings : public QObject
{
Q_OBJECT
@@ -41,34 +40,43 @@ class OTR_SPLINE_EXPORT settings final : public base_settings
{
public:
bundle b;
- value<QList<QPointF>> points;
+ value<QList<QPointF>> points { b, "points", {} };
axis_opts opts;
- settings(bundle b, const QString& axis_name, Axis idx);
+ settings(bundle const& b, const QString& axis_name, Axis idx);
~settings() override;
};
-} // ns spline_detail
-
struct OTR_SPLINE_EXPORT base_spline_
{
- virtual inline ~base_spline_();
+ base_spline_() = default;
+ 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;
- warn_result_unused virtual bool get_last_value(QPointF& point) = 0;
- virtual void set_tracking_active(bool value) = 0;
+ [[nodiscard]] virtual bool get_last_value(QPointF& point) = 0;
+ virtual void set_tracking_active(bool value) const = 0;
virtual double max_input() const = 0;
virtual double max_output() const = 0;
- using points_t = QList<QPointF>;
-
- virtual points_t get_points() const = 0;
+ virtual const points_t& get_points() const = 0;
virtual int get_point_count() 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;
+ base_spline_(const base_spline_&) = default;
+ base_spline_& operator=(const base_spline_&) = default;
+};
+
+struct OTR_SPLINE_EXPORT spline_settings_mixin
+{
+ virtual std::shared_ptr<base_settings> get_settings() = 0;
+ virtual std::shared_ptr<const base_settings> get_settings() const = 0;
+
+ spline_settings_mixin(const spline_settings_mixin&) = default;
+ spline_settings_mixin& operator=(const spline_settings_mixin&) = default;
+
+ spline_settings_mixin() = default;
+ virtual ~spline_settings_mixin();
};
struct OTR_SPLINE_EXPORT spline_modify_mixin
@@ -79,42 +87,52 @@ struct OTR_SPLINE_EXPORT spline_modify_mixin
virtual void remove_point(int i) = 0;
virtual void clear() = 0;
- virtual inline ~spline_modify_mixin();
+ spline_modify_mixin(const spline_modify_mixin&) = default;
+ spline_modify_mixin& operator=(const spline_modify_mixin&) = default;
+
+ spline_modify_mixin() = default;
+ virtual ~spline_modify_mixin();
};
-class OTR_SPLINE_EXPORT base_spline : public base_spline_, public spline_modify_mixin
+struct OTR_SPLINE_EXPORT base_spline : base_spline_, spline_modify_mixin, spline_settings_mixin
{
-public:
- using base_settings = spline_detail::base_settings;
+ base_spline(const base_spline&) = default;
+ base_spline& operator=(const base_spline&) = default;
+
+ base_spline() = default;
+ ~base_spline() override;
};
class OTR_SPLINE_EXPORT spline : public base_spline
{
+ using f = double;
+
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);
- static bool sort_fn(const QPointF& one, const QPointF& two);
+ void update_interp_data() const;
+ double get_value_internal(int x) const;
+ static bool sort_fn(QPointF one, 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);
- std::shared_ptr<spline_detail::settings> s;
- QMetaObject::Connection connection, conn_maxx, conn_maxy;
-
- static constexpr inline std::size_t value_count = 4096;
+ void disconnect_signals();
+ void invalidate_settings_();
- std::vector<float> data = std::vector<float>(value_count, float(-16));
+ mutex mtx { mutex::Recursive };
+ std::shared_ptr<settings> s;
+ QMetaObject::Connection conn_points, conn_maxx, conn_maxy;
- mutex _mutex { mutex::recursive };
- QPointF last_input_value;
std::shared_ptr<QObject> ctx { std::make_shared<QObject>() };
- Axis axis = NonAxis;
+ mutable QPointF last_input_value{-1, -1};
+ mutable std::vector<f> data = std::vector<f>(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;
- bool activep = false;
- bool validp = false;
+ static constexpr unsigned value_count = 8192;
+ static constexpr f magic_fill_value = -(1 << 24) + 1;
+ static constexpr double c_interp = 5;
public:
void invalidate_settings();
@@ -128,14 +146,13 @@ public:
spline();
spline(const QString& name, const QString& axis_name, Axis axis);
- ~spline();
+ ~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;
- warn_result_unused bool get_last_value(QPointF& point) 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;
void add_point(double x, double y) override;
@@ -143,19 +160,18 @@ public:
void remove_point(int i) override;
void clear() override;
- points_t get_points() const override;
+ const points_t& get_points() const override;
- void set_tracking_active(bool value) override;
+ void set_tracking_active(bool value) const override;
bundle get_bundle();
- void ensure_valid(QList<QPointF>& the_points);
+ 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_() {}
-inline spline_modify_mixin::~spline_modify_mixin() {}
+} // ns spline_detail
+
+using spline = spline_detail::spline;