summaryrefslogtreecommitdiffhomepage
path: root/spline-widget
diff options
context:
space:
mode:
authorStanislaw Halik <sthalik@misaki.pl>2015-10-30 07:37:41 +0100
committerStanislaw Halik <sthalik@misaki.pl>2015-10-30 08:39:32 +0100
commitaa066bdd4622d4f6824fee864f6be6806813f04d (patch)
tree3df328b8b364cba2373a85827191b259bd78d546 /spline-widget
parentd6a54431d178632a2bf466c9904f74abd143afe6 (diff)
move to subdirectory-based build system
Closes #224
Diffstat (limited to 'spline-widget')
-rw-r--r--spline-widget/CMakeLists.txt6
-rw-r--r--spline-widget/broken/qfunctionconfiguratorplugin.cpp111
-rw-r--r--spline-widget/broken/qfunctionconfiguratorplugin.h36
-rw-r--r--spline-widget/functionconfig.cpp249
-rw-r--r--spline-widget/functionconfig.h75
-rw-r--r--spline-widget/qfunctionconfigurator.cpp440
-rw-r--r--spline-widget/qfunctionconfigurator.h75
7 files changed, 992 insertions, 0 deletions
diff --git a/spline-widget/CMakeLists.txt b/spline-widget/CMakeLists.txt
new file mode 100644
index 00000000..522491ea
--- /dev/null
+++ b/spline-widget/CMakeLists.txt
@@ -0,0 +1,6 @@
+opentrack_boilerplate(opentrack-spline-widget NO-LIBRARY)
+opentrack_qt(opentrack-spline-widget)
+add_library(opentrack-spline-widget STATIC ${opentrack-spline-widget-all})
+opentrack_compat(opentrack-spline-widget)
+target_include_directories(opentrack-spline-widget PUBLIC qfunctionconfigurator/)
+target_link_libraries(opentrack-spline-widget ${MY_QT_LIBS})
diff --git a/spline-widget/broken/qfunctionconfiguratorplugin.cpp b/spline-widget/broken/qfunctionconfiguratorplugin.cpp
new file mode 100644
index 00000000..1a9da10a
--- /dev/null
+++ b/spline-widget/broken/qfunctionconfiguratorplugin.cpp
@@ -0,0 +1,111 @@
+/* Copyright (c) 2011-2012 Stanislaw Halik <sthalik@misaki.pl>
+ * Adapted to FaceTrackNoIR by Wim Vriend.
+ * 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 "qfunctionconfigurator.h"
+
+#include <QtCore/QtPlugin>
+#include "qfunctionconfiguratorplugin.h"
+
+
+QFunctionConfiguratorPlugin::QFunctionConfiguratorPlugin(QObject *parent)
+ : QObject(parent)
+{
+ initialized = false;
+}
+
+void QFunctionConfiguratorPlugin::initialize(QDesignerFormEditorInterface *)
+{
+ if (initialized)
+ return;
+
+ initialized = true;
+}
+
+bool QFunctionConfiguratorPlugin::isInitialized() const
+{
+ return initialized;
+}
+
+QWidget *QFunctionConfiguratorPlugin::createWidget(QWidget *parent)
+{
+ return new QFunctionConfigurator(parent);
+}
+
+QString QFunctionConfiguratorPlugin::name() const
+{
+ return "QFunctionConfigurator";
+}
+
+QString QFunctionConfiguratorPlugin::group() const
+{
+ return "My Plugins";
+}
+
+QIcon QFunctionConfiguratorPlugin::icon() const
+{
+ return QIcon();
+}
+
+QString QFunctionConfiguratorPlugin::toolTip() const
+{
+ return QString();
+}
+
+QString QFunctionConfiguratorPlugin::whatsThis() const
+{
+ return QString();
+}
+
+bool QFunctionConfiguratorPlugin::isContainer() const
+{
+ return false;
+}
+
+QString QFunctionConfiguratorPlugin::domXml() const
+{
+ return "<widget class=\"QFunctionConfigurator\" name=\"qFunctionA\">\n"
+ " <property name=\"geometry\">\n"
+ " <rect>\n"
+ " <x>0</x>\n"
+ " <y>0</y>\n"
+ " <width>161</width>\n"
+ " <height>220</height>\n"
+ " </rect>\n"
+ " </property>\n"
+ " <property name=\"colorBezier\">\n"
+ " <color>\n"
+ " <red>255</red>\n"
+ " <green>170</green>\n"
+ " <blue>0</blue>\n"
+ " </color>\n"
+ " </property>\n"
+ " <property name=\"stringInputEGU\" stdset=\"0\">\n"
+ " <string>Input Yaw (degr.)</string>\n"
+ " </property>\n"
+ " <property name=\"stringOutputEGU\" stdset=\"0\">\n"
+ " <string>Output Yaw (degr.)</string>\n"
+ " </property>\n"
+ " <property name=\"maxInputEGU\" stdset=\"0\">\n"
+ " <number>50</number>\n"
+ " </property>\n"
+ " <property name=\"maxOutputEGU\" stdset=\"0\">\n"
+ " <number>180</number>\n"
+ " </property>\n"
+ " <property name=\"pixPerEGU_Input\" stdset=\"0\">\n"
+ " <number>2</number>\n"
+ " </property>\n"
+ " <property name=\"pixPerEGU_Output\" stdset=\"0\">\n"
+ " <number>1</number>\n"
+ " </property>\n"
+ "</widget>\n";
+}
+
+QString QFunctionConfiguratorPlugin::includeFile() const
+{
+ return "qfunctionconfigurator.h";
+}
+
+Q_EXPORT_PLUGIN2(qfunctionconfigurator, QFunctionConfiguratorPlugin)
diff --git a/spline-widget/broken/qfunctionconfiguratorplugin.h b/spline-widget/broken/qfunctionconfiguratorplugin.h
new file mode 100644
index 00000000..ca68e0e2
--- /dev/null
+++ b/spline-widget/broken/qfunctionconfiguratorplugin.h
@@ -0,0 +1,36 @@
+/* Copyright (c) 2011-2012 Stanislaw Halik <sthalik@misaki.pl>
+ * Adapted to FaceTrackNoIR by Wim Vriend.
+ * 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.
+ */
+#ifndef QFUNCTIONCONFIGURATORPLUGIN_H
+#define QFUNCTIONCONFIGURATORPLUGIN_H
+
+#include <QDesignerCustomWidgetInterface>
+
+class QFunctionConfiguratorPlugin : public QObject, public QDesignerCustomWidgetInterface
+{
+ Q_OBJECT
+ Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetInterface" FILE "analogclock.json")
+ Q_INTERFACES(QDesignerCustomWidgetInterface)
+
+public:
+ QFunctionConfiguratorPlugin(QObject *parent = 0);
+
+ bool isContainer() const;
+ bool isInitialized() const;
+ QIcon icon() const;
+ QString domXml() const;
+ QString group() const;
+ QString includeFile() const;
+ QString name() const;
+ QString toolTip() const;
+ QString whatsThis() const;
+ QWidget *createWidget(QWidget *parent);
+ void initialize(QDesignerFormEditorInterface *core);
+private:
+ bool initialized;
+};
+
+#endif // QFUNCTIONCONFIGURATORPLUGIN_H
diff --git a/spline-widget/functionconfig.cpp b/spline-widget/functionconfig.cpp
new file mode 100644
index 00000000..27f3bf40
--- /dev/null
+++ b/spline-widget/functionconfig.cpp
@@ -0,0 +1,249 @@
+/* Copyright (c) 2012-2015, 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 <QMutexLocker>
+#include <QCoreApplication>
+#include <QPointF>
+#include <QList>
+#include "functionconfig.h"
+#include <QtAlgorithms>
+#include <QtAlgorithms>
+#include <QSettings>
+#include <QPixmap>
+#include <algorithm>
+#include <cmath>
+
+void Map::setTrackingActive(bool blnActive)
+{
+ activep = blnActive;
+}
+
+Map::Map() :
+ _mutex(QMutex::Recursive),
+ activep(false),
+ max_x(0),
+ max_y(0)
+{
+}
+
+float Map::getValue(float x) {
+ QMutexLocker foo(&_mutex);
+ float q = x * precision();
+ int xi = (int)q;
+ float yi = getValueInternal(xi);
+ float yiplus1 = getValueInternal(xi+1);
+ float f = (q-xi);
+ float ret = yiplus1 * f + yi * (1.0f - f); // at least do a linear interpolation.
+ last_input_value.setX(std::fabs(x));
+ last_input_value.setY(std::fabs(ret));
+ return ret;
+}
+
+bool Map::getLastPoint(QPointF& point ) {
+ QMutexLocker foo(&_mutex);
+ point = last_input_value;
+ return activep;
+}
+
+float Map::getValueInternal(int x) {
+ float sign = x < 0 ? -1 : 1;
+ x = abs(x);
+ float ret;
+ int sz = cur.data.size();
+ if (sz == 0)
+ ret = 0;
+ else
+ ret = cur.data[std::min<unsigned>(x, sz-1)];
+ return ret * sign;
+}
+
+static QPointF ensureInBounds(QList<QPointF> points, int i) {
+ int siz = points.size();
+ if (siz == 0 || i < 0)
+ return QPointF(0, 0);
+ if (siz > i)
+ return points[i];
+ return points[siz - 1];
+}
+
+static bool sortFn(const QPointF& one, const QPointF& two) {
+ return one.x() < two.x();
+}
+
+void Map::reload() {
+ if (cur.input.size())
+ {
+ qStableSort(cur.input.begin(), cur.input.end(), sortFn);
+
+ QList<QPointF> input = cur.input;
+ auto& data = cur.data;
+
+ data = std::vector<float>(value_count);
+ const int mult = precision();
+ const int mult_ = mult * 30;
+
+ const int sz = data.size();
+
+ for (int i = 0; i < sz; i++)
+ data[i] = -1;
+
+ if (input.size() == 1 && input[0].x() > 1e-2)
+ {
+ for (int k = 0; k < input[0].x() * mult; k++) {
+ if (k < sz)
+ data[k] = input[0].y() * k / (input[0].x() * mult);
+ }
+ }
+ else if (input[0].x() > 1e-2)
+ input.prepend(QPointF(0, 0));
+
+ for (int i = 0; i < sz; i++) {
+ const QPointF p0 = ensureInBounds(input, i - 1);
+ const QPointF p1 = ensureInBounds(input, i);
+ const QPointF p2 = ensureInBounds(input, i + 1);
+ const QPointF p3 = ensureInBounds(input, i + 2);
+
+ const float p0_x = p0.x(), p1_x = p1.x(), p2_x = p2.x(), p3_x = p3.x();
+ const float p0_y = p0.y(), p1_y = p1.y(), p2_y = p2.y(), p3_y = p3.y();
+
+ // multiplier helps fill in all the x's needed
+ const int end = std::min<int>(sz, p2.x() * mult_);
+ const int start = p1.x() * mult;
+
+ for (int j = start; j < end; j++) {
+ const float t = (j - start) / (float) (end - start);
+ const float t2 = t*t;
+ const float t3 = t*t*t;
+
+ const int x = .5 * ((2. * p1_x) +
+ (-p0_x + p2_x) * t +
+ (2. * p0_x - 5. * p1_x + 4. * p2_x - p3_x) * t2 +
+ (-p0_x + 3. * p1_x - 3. * p2_x + p3_x) * t3)
+ * mult;
+
+ const float y = .5 * ((2. * p1_y) +
+ (-p0_y + p2_y) * t +
+ (2. * p0_y - 5. * p1_y + 4. * p2_y - p3_y) * t2 +
+ (-p0_y + 3. * p1_y - 3. * p2_y + p3_y) * t3);
+
+ if (x >= 0 && x < sz)
+ data[x] = y;
+ }
+ }
+
+ float last = 0;
+ for (int i = 0; i < sz; i++)
+ {
+ if (data[i] < 0)
+ data[i] = last;
+ last = data[i];
+ }
+ }
+ else
+ cur.data.clear();
+}
+
+void Map::removePoint(int i) {
+ QMutexLocker foo(&_mutex);
+ if (i >= 0 && i < cur.input.size())
+ {
+ cur.input.removeAt(i);
+ reload();
+ }
+}
+
+void Map::addPoint(QPointF pt) {
+ QMutexLocker foo(&_mutex);
+ cur.input.append(pt);
+ reload();
+ qStableSort(cur.input.begin(), cur.input.end(), sortFn);
+}
+
+void Map::movePoint(int idx, QPointF pt) {
+ QMutexLocker foo(&_mutex);
+ if (idx >= 0 && idx < cur.input.size())
+ {
+ cur.input[idx] = pt;
+ reload();
+ // we don't allow points to be reordered, so no sort here
+ }
+}
+
+const QList<QPointF> Map::getPoints() {
+ QMutexLocker foo(&_mutex);
+ return cur.input;
+}
+
+void Map::invalidate_unsaved_settings()
+{
+ QMutexLocker foo(&_mutex);
+ cur = saved;
+ reload();
+}
+
+void Map::loadSettings(QSettings& settings, const QString& title) {
+ QMutexLocker foo(&_mutex);
+ QPointF newPoint;
+ QList<QPointF> points;
+ settings.beginGroup(QString("Curves-%1").arg(title));
+
+ int max = settings.value("point-count", 0).toInt();
+
+ for (int i = 0; i < max; i++) {
+ newPoint = QPointF(settings.value(QString("point-%1-x").arg(i), 0).toDouble(),
+ settings.value(QString("point-%1-y").arg(i), 0).toDouble());
+ if (newPoint.x() > max_x) {
+ newPoint.setX(max_x);
+ }
+ if (newPoint.y() > max_y) {
+ newPoint.setY(max_y);
+ }
+ points.append(newPoint);
+ }
+
+ settings.endGroup();
+
+ if (max == 0)
+ points.append(QPointF(maxInput(), maxOutput()));
+
+ cur.input = points;
+ reload();
+ saved = cur;
+}
+
+void Map::saveSettings(QSettings& settings, const QString& title) {
+ QMutexLocker foo(&_mutex);
+ settings.beginGroup(QString("Curves-%1").arg(title));
+ int max = cur.input.size();
+ settings.setValue("point-count", max);
+
+ for (int i = 0; i < max; i++) {
+ settings.setValue(QString("point-%1-x").arg(i), cur.input[i].x());
+ settings.setValue(QString("point-%1-y").arg(i), cur.input[i].y());
+ }
+
+ for (int i = max; true; i++)
+ {
+ QString x = QString("point-%1-x").arg(i);
+ if (!settings.contains(x))
+ break;
+ settings.remove(x);
+ settings.remove(QString("point-%1-y").arg(i));
+ }
+
+ saved = cur;
+
+ settings.endGroup();
+}
+
+
+int Map::precision() const {
+ if (cur.input.size())
+ return value_count / std::max<float>(1.f, (cur.input[cur.input.size() - 1].x()));
+ return 1;
+}
diff --git a/spline-widget/functionconfig.h b/spline-widget/functionconfig.h
new file mode 100644
index 00000000..6d76d0de
--- /dev/null
+++ b/spline-widget/functionconfig.h
@@ -0,0 +1,75 @@
+/* Copyright (c) 2012-2015, 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.
+ */
+
+#pragma once
+
+#include <QList>
+#include <QPointF>
+#include <QString>
+#include <QSettings>
+#include <QMutex>
+#include <vector>
+#include <limits>
+#include "opentrack-compat/qcopyable-mutex.hpp"
+
+class Map {
+private:
+ static constexpr int value_count = 10000;
+
+ struct State {
+ QList<QPointF> input;
+ std::vector<float> data;
+ };
+
+ int precision() const;
+ void reload();
+ float getValueInternal(int x);
+
+ MyMutex _mutex;
+ QPointF last_input_value;
+ volatile bool activep;
+ double max_x;
+ double max_y;
+
+ State cur, saved;
+public:
+ double maxInput() const { return max_x; }
+ double maxOutput() const { return max_y; }
+ Map();
+ Map(double maxx, double maxy)
+ {
+ setMaxInput(maxx);
+ setMaxOutput(maxy);
+ reload();
+ }
+
+ float getValue(float x);
+ bool getLastPoint(QPointF& point);
+ void removePoint(int i);
+ void removeAllPoints() {
+ QMutexLocker foo(&_mutex);
+ cur.input.clear();
+ reload();
+ }
+
+ void addPoint(QPointF pt);
+ void movePoint(int idx, QPointF pt);
+ const QList<QPointF> getPoints();
+ void setMaxInput(double MaxInput) {
+ max_x = MaxInput;
+ }
+ void setMaxOutput(double MaxOutput) {
+ max_y = MaxOutput;
+ }
+
+ void saveSettings(QSettings& settings, const QString& title);
+ void loadSettings(QSettings& settings, const QString& title);
+ void invalidate_unsaved_settings();
+
+ void setTrackingActive(bool blnActive);
+};
diff --git a/spline-widget/qfunctionconfigurator.cpp b/spline-widget/qfunctionconfigurator.cpp
new file mode 100644
index 00000000..7ab1e360
--- /dev/null
+++ b/spline-widget/qfunctionconfigurator.cpp
@@ -0,0 +1,440 @@
+/* Copyright (c) 2012-2015 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 "opentrack/options.hpp"
+using namespace options;
+#include "spline-widget/qfunctionconfigurator.h"
+#include <QPainter>
+#include <QPaintEvent>
+#include <QPen>
+#include <QPixmap>
+#include <cmath>
+#include <algorithm>
+
+static const int pointSize = 5;
+
+QFunctionConfigurator::QFunctionConfigurator(QWidget *parent) :
+ QWidget(parent),
+ _config(nullptr),
+ moving_control_point_idx(-1),
+ _draw_function(true)
+{
+ update_range();
+ setMouseTracking(true);
+}
+
+void QFunctionConfigurator::setConfig(Map* config, const QString& name) {
+ mem<QSettings> iniFile = group::ini_file();
+ if (name != "")
+ config->loadSettings(*iniFile, name);
+ _config = config;
+ _draw_function = true;
+ update_range();
+ update();
+}
+
+void QFunctionConfigurator::drawBackground()
+{
+ if (!_config)
+ return;
+ _background = QPixmap(width(), height());
+
+ QPainter painter(&_background);
+ painter.fillRect(rect(), QColor::fromRgb(204, 204, 204));
+
+ QColor bg_color(112, 154, 209);
+ if (!isEnabled())
+ bg_color = QColor(176,176,180);
+ painter.fillRect(pixel_bounds, bg_color);
+
+ QFont font;
+ font.setPointSize(8);
+ painter.setFont(font);
+ QFontMetrics metrics(font);
+
+ QColor color__(176, 190, 209, 127);
+
+ if (!isEnabled())
+ color__ = QColor(70, 90, 100, 96);
+
+ QPen pen(color__, 1, Qt::SolidLine);
+
+ const int xstep = 10, ystep = 10;
+ const double maxx = _config->maxInput();
+ const double maxy = _config->maxOutput();
+
+ // horizontal grid
+ for (int i = 0; i < maxy; i += xstep)
+ {
+ double y = pixel_bounds.height() - i * c.y() + pixel_bounds.y();
+ drawLine(&painter,
+ 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));
+ }
+
+ {
+ const double i = maxy;
+ double y = pixel_bounds.height() - i * c.y() + pixel_bounds.y();
+ drawLine(&painter,
+ QPointF(pixel_bounds.x(), y),
+ QPointF(pixel_bounds.x() + pixel_bounds.width(), y),
+ pen);
+ painter.drawText(QRectF(10,
+ y - metrics.height()/2,
+ pixel_bounds.x() - 10,
+ metrics.height()),
+ QString::number(i));
+ }
+
+ // vertical grid
+ for (int i = 0; i < maxx; i += ystep)
+ {
+ double x = pixel_bounds.x() + i * c.x();
+ drawLine(&painter,
+ 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()),
+ text);
+ }
+ {
+ const double i = maxx;
+ double x = pixel_bounds.x() + i * c.x();
+ drawLine(&painter,
+ 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()),
+ text);
+ }
+}
+
+void QFunctionConfigurator::drawFunction()
+{
+ if (!_config)
+ return;
+
+ _function = QPixmap(_background);
+ QPainter painter(&_function);
+ painter.setRenderHint(QPainter::Antialiasing, true);
+
+ QList<QPointF> points = _config->getPoints();
+
+ const int alpha = !isEnabled() ? 64 : 120;
+ for (int i = 0; i < points.size(); i++) {
+ drawPoint(&painter,
+ point_to_pixel(points[i]),
+ QColor(200, 200, 210, alpha),
+ isEnabled() ? QColor(50, 100, 120, 200) : QColor(200, 200, 200, 96));
+ }
+
+ QColor color = spline_color;
+
+ if (!isEnabled())
+ {
+ const int avg = 176;
+ auto color_ = color;
+ color = QColor(color_.red() * .5 + avg * .5,
+ color_.green() * .5 + avg * .5,
+ color_.blue() * .5 + avg * .5,
+ 96);
+ }
+
+ QPen pen(color, 1.2, Qt::SolidLine);
+
+ const double max = _config->maxInput();
+ const double step = std::max(1e-2, max / 1000.);
+
+ QPointF prev = point_to_pixel(QPointF(0, 0));
+ for (double i = 0; i < max; i += step) {
+ double val = _config->getValue(i);
+ QPointF cur = point_to_pixel(QPointF(i, val));
+ drawLine(&painter, prev, cur, pen);
+ prev = cur;
+ }
+}
+
+void QFunctionConfigurator::paintEvent(QPaintEvent *e)
+{
+ QPainter p(this);
+
+ if (_background.isNull())
+ {
+ _draw_function = true;
+ drawBackground();
+ }
+
+ if (_draw_function) {
+ _draw_function = false;
+ drawFunction();
+ }
+
+ p.drawPixmap(e->rect(), _function);
+
+ if (_config) {
+ QPen pen(Qt::white, 1, Qt::SolidLine);
+ QList<QPointF> points = _config->getPoints();
+ if (points.size() && moving_control_point_idx >= 0 && moving_control_point_idx < points.size()) {
+ if (points[0].x() > 1e-2)
+ points.prepend(QPointF(0, 0));
+ QPointF prev = point_to_pixel(points[0]);
+ for (int i = 1; i < points.size(); i++) {
+ auto tmp = point_to_pixel(points[i]);
+ drawLine(&p, prev, tmp, pen);
+ prev = tmp;
+ }
+ }
+
+ // 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->getLastPoint(last) && isEnabled()) {
+ QPointF pixel_pos = point_to_pixel(last);
+ drawPoint(&p, pixel_pos, QColor(255, 0, 0, 120));
+ }
+ }
+}
+
+void QFunctionConfigurator::drawPoint(QPainter *painter, const QPointF &pos, QColor colBG, QColor border)
+{
+ painter->save();
+ painter->setPen(border);
+ painter->setBrush( colBG );
+ painter->drawEllipse(QRectF(pos.x() - pointSize,
+ pos.y() - pointSize,
+ pointSize*2, pointSize*2));
+ painter->restore();
+}
+
+void QFunctionConfigurator::drawLine(QPainter *painter, const QPointF &start, const QPointF &end, QPen &pen)
+{
+ painter->save();
+ painter->setPen(pen);
+ painter->setBrush(Qt::NoBrush);
+ painter->drawLine(start, end);
+ painter->restore();
+}
+
+void QFunctionConfigurator::mousePressEvent(QMouseEvent *e)
+{
+ if (!_config || !isEnabled())
+ return;
+ QList<QPointF> points = _config->getPoints();
+ if (e->button() == Qt::LeftButton) {
+ bool bTouchingPoint = false;
+ moving_control_point_idx = -1;
+ if (_config) {
+ for (int i = 0; i < points.size(); i++) {
+ if (point_within_pixel(points[i], e->pos())) {
+ bTouchingPoint = true;
+ moving_control_point_idx = i;
+ break;
+ }
+ }
+ if (!bTouchingPoint) {
+ bool too_close = false;
+ const auto pos = e->pos();
+
+ for (int i = 0; i < points.size(); i++)
+ {
+ const QPointF pt = point_to_pixel(points[i]);
+ const auto x = pt.x() - pos.x();
+ if (point_closeness_limit * point_closeness_limit >= x * x)
+ {
+ too_close = true;
+ break;
+ }
+ }
+
+ if (!too_close)
+ _config->addPoint(pixel_coord_to_point(e->pos()));
+ }
+ }
+ }
+
+ if (e->button() == Qt::RightButton) {
+ if (_config) {
+ int found_pt = -1;
+ for (int i = 0; i < points.size(); i++) {
+ if (point_within_pixel(points[i], e->pos())) {
+ found_pt = i;
+ break;
+ }
+ }
+
+ if (found_pt != -1) {
+ _config->removePoint(found_pt);
+ }
+ moving_control_point_idx = -1;
+ }
+ }
+ _draw_function = true;
+ update();
+}
+
+void QFunctionConfigurator::mouseMoveEvent(QMouseEvent *e)
+{
+ if (!_config || !isEnabled())
+ return;
+
+ QList<QPointF> points = _config->getPoints();
+
+ if (moving_control_point_idx != -1 && moving_control_point_idx < points.size()) {
+ setCursor(Qt::ClosedHandCursor);
+
+ bool overlap = false;
+
+ QPoint pix = e->pos();
+ QPointF new_pt = pixel_coord_to_point(pix);
+
+ for (int i = 0; i < 2; i++)
+ {
+ bool bad = false;
+ if (moving_control_point_idx + 1 < points.size())
+ {
+ auto other = points[moving_control_point_idx+1];
+ auto other_pix = point_to_pixel(other);
+ bad = pix.x() + point_closeness_limit > other_pix.x();
+ if (i == 0 && bad)
+ {
+ pix.setX(other_pix.x() - point_closeness_limit - 1);
+ new_pt = pixel_coord_to_point(pix);
+ }
+ else
+ overlap |= bad;
+ }
+ if (moving_control_point_idx != 0)
+ {
+ auto other = points[moving_control_point_idx-1];
+ auto other_pix = point_to_pixel(other);
+ bad = pix.x() - point_closeness_limit < other_pix.x();
+ if (i == 0 && bad)
+ {
+ pix.setX(other_pix.x() + point_closeness_limit + 1);
+ new_pt = pixel_coord_to_point(pix);
+ }
+ else
+ overlap |= bad;
+ }
+ if (!bad)
+ break;
+ }
+
+ if (!overlap)
+ {
+ points[moving_control_point_idx] = new_pt;
+ _config->movePoint(moving_control_point_idx, new_pt);
+ _draw_function = true;
+ update();
+ }
+ }
+ else {
+ bool is_on_point = false;
+ for (int i = 0; i < points.size(); i++) {
+ const QPoint pos = e->pos();
+ if (point_within_pixel(points[i], pos)) {
+ is_on_point = true;
+ break;
+ }
+ }
+
+ if (is_on_point) {
+ setCursor(Qt::OpenHandCursor);
+ }
+ else {
+ setCursor(Qt::ArrowCursor);
+ }
+ }
+}
+
+void QFunctionConfigurator::mouseReleaseEvent(QMouseEvent *e)
+{
+ if (!_config || !isEnabled())
+ return;
+
+ if (e->button() == Qt::LeftButton) {
+ mouseMoveEvent(e);
+ setCursor(Qt::ArrowCursor);
+ moving_control_point_idx = -1;
+
+ _draw_function = true;
+ update();
+ }
+}
+
+void QFunctionConfigurator::update_range()
+{
+ if (!_config)
+ return;
+
+ const double w = width(), h = height();
+ const double mwl = 40, mhl = 20;
+ const double mwr = 15, mhr = 35;
+
+ pixel_bounds = QRectF(mwl, mhl, (w - mwl - mwr), (h - mhl - mhr));
+ c = QPointF(pixel_bounds.width() / _config->maxInput(), pixel_bounds.height() / _config->maxOutput());
+ _draw_function = true;
+
+ _background = QPixmap();
+ _function = QPixmap();
+
+ update();
+}
+
+bool QFunctionConfigurator::point_within_pixel(const QPointF &pt, const QPointF &pixel)
+{
+ QPointF pixel2 = point_to_pixel(pt);
+ return pixel2.x() >= pixel.x() - pointSize && pixel2.x() < pixel.x() + pointSize &&
+ pixel2.y() >= pixel.y() - pointSize && pixel2.y() < pixel.y() + pointSize;
+}
+
+QPointF QFunctionConfigurator::pixel_coord_to_point(const QPointF& point)
+{
+ if (!_config)
+ return QPointF(-1, -1);
+
+ double x = (point.x() - pixel_bounds.x()) / c.x();
+ double y = (pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y();
+
+ if (x < 0)
+ x = 0;
+ if (x > _config->maxInput())
+ x = _config->maxInput();
+
+ if (y < 0)
+ y = 0;
+ if (y > _config->maxOutput())
+ y = _config->maxOutput();
+
+ return QPointF(x, y);
+}
+
+QPointF QFunctionConfigurator::point_to_pixel(const QPointF& point)
+{
+ return QPointF(pixel_bounds.x() + point.x() * c.x(),
+ pixel_bounds.y() + pixel_bounds.height() - point.y() * c.y());
+}
+
+void QFunctionConfigurator::resizeEvent(QResizeEvent *)
+{
+ update_range();
+}
diff --git a/spline-widget/qfunctionconfigurator.h b/spline-widget/qfunctionconfigurator.h
new file mode 100644
index 00000000..baea9e34
--- /dev/null
+++ b/spline-widget/qfunctionconfigurator.h
@@ -0,0 +1,75 @@
+/* Copyright (c) 2012-2015 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.
+ */
+
+// Adapted to FaceTrackNoIR by Wim Vriend.
+
+#pragma once
+
+#include <QWidget>
+#include <QtGui>
+#include <QPointF>
+#include "spline-widget/functionconfig.h"
+#include "opentrack/plugin-api.hpp"
+
+class QFunctionConfigurator : public QWidget
+{
+ Q_OBJECT
+ Q_PROPERTY(QColor colorBezier READ colorBezier WRITE setColorBezier)
+public:
+ QFunctionConfigurator(QWidget *parent = 0);
+
+ Map* config();
+ void setConfig(Map* config, const QString &name);
+
+ QColor colorBezier() const
+ {
+ return spline_color;
+ }
+ void setColorBezier(QColor color)
+ {
+ spline_color = color;
+ update();
+ }
+ void force_redraw()
+ {
+ _background = QPixmap();
+ update();
+ }
+protected slots:
+ void paintEvent(QPaintEvent *e) override;
+ void mousePressEvent(QMouseEvent *e) override;
+ void mouseMoveEvent(QMouseEvent *e) override;
+ void mouseReleaseEvent(QMouseEvent *e) override;
+private:
+ void drawBackground();
+ void drawFunction();
+ void drawPoint(QPainter *painter, const QPointF &pt, QColor colBG, QColor border = QColor(50, 100, 120, 200));
+ void drawLine(QPainter *painter, const QPointF &start, const QPointF &end, QPen& pen);
+ bool point_within_pixel(const QPointF& pt, const QPointF& pixel);
+protected:
+ void resizeEvent(QResizeEvent *) override;
+private:
+ void update_range();
+ static constexpr int point_closeness_limit = 12;
+
+ QPointF pixel_coord_to_point (const QPointF& point);
+ QPointF point_to_pixel (const QPointF& point);
+
+ Map* _config;
+
+ // bounds of the rectangle user can interact with
+ QRectF pixel_bounds;
+
+ int moving_control_point_idx;
+ QPointF c;
+
+ QColor spline_color;
+
+ QPixmap _background;
+ QPixmap _function;
+ bool _draw_function;
+};