diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2015-10-30 07:37:41 +0100 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2015-10-30 08:39:32 +0100 |
commit | aa066bdd4622d4f6824fee864f6be6806813f04d (patch) | |
tree | 3df328b8b364cba2373a85827191b259bd78d546 /spline-widget | |
parent | d6a54431d178632a2bf466c9904f74abd143afe6 (diff) |
move to subdirectory-based build system
Closes #224
Diffstat (limited to 'spline-widget')
-rw-r--r-- | spline-widget/CMakeLists.txt | 6 | ||||
-rw-r--r-- | spline-widget/broken/qfunctionconfiguratorplugin.cpp | 111 | ||||
-rw-r--r-- | spline-widget/broken/qfunctionconfiguratorplugin.h | 36 | ||||
-rw-r--r-- | spline-widget/functionconfig.cpp | 249 | ||||
-rw-r--r-- | spline-widget/functionconfig.h | 75 | ||||
-rw-r--r-- | spline-widget/qfunctionconfigurator.cpp | 440 | ||||
-rw-r--r-- | spline-widget/qfunctionconfigurator.h | 75 |
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; +}; |