diff options
author | Stanislaw Halik <sthalik@misaki.pl> | 2017-03-28 10:49:19 +0200 |
---|---|---|
committer | Stanislaw Halik <sthalik@misaki.pl> | 2017-03-28 11:01:31 +0200 |
commit | 783739a2f3599e00869f907ed1747491b35486b1 (patch) | |
tree | 1b1e5179c30e9feb711ce1e678f5236d97fe1789 /spline/spline-widget.cpp | |
parent | 8c2ed1d36e1a9d7c0eeabea40f8229d1aa6774d1 (diff) |
rename spline-widget -> spline
Adjust usages.
Diffstat (limited to 'spline/spline-widget.cpp')
-rw-r--r-- | spline/spline-widget.cpp | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/spline/spline-widget.cpp b/spline/spline-widget.cpp new file mode 100644 index 00000000..c7dd5e5a --- /dev/null +++ b/spline/spline-widget.cpp @@ -0,0 +1,652 @@ +/* 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 "compat/util.hpp" +#include "spline-widget.hpp" +#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 <QDebug> + +#include <cmath> +#include <algorithm> + +spline_widget::spline_widget(QWidget *parent) : + QWidget(parent), + _config(nullptr), + snap_x(0), + snap_y(0), + _x_step(10), + _y_step(10), + moving_control_point_idx(-1), + _draw_function(true), + _preview_only(false) +{ + setMouseTracking(true); + setFocusPolicy(Qt::ClickFocus); + setCursor(Qt::ArrowCursor); +} + +spline_widget::~spline_widget() +{ + if (connection) + QObject::disconnect(connection); +} + +void spline_widget::setConfig(spline* spl) +{ + if (spl != _config) + { + if (connection) + { + QObject::disconnect(connection); + connection = QMetaObject::Connection(); + } + + if (spl) + { + mem<spline::settings> s = spl->get_settings(); + connection = connect(s.get(), &spline::settings::recomputed, + this, [this]() { reload_spline(); }, + Qt::QueuedConnection); + } + + _config = spl; + _background = QPixmap(); + update_range(); + } +} + +QColor spline_widget::colorBezier() const +{ + return spline_color; +} + +void spline_widget::setColorBezier(QColor color) +{ + spline_color = color; + update(); +} + +void spline_widget::force_redraw() +{ + _background = QPixmap(); + update(); +} + +void spline_widget::set_preview_only(bool val) +{ + _preview_only = val; +} + +bool spline_widget::is_preview_only() const +{ + return _preview_only; +} + +void spline_widget::drawBackground() +{ + _background = QPixmap(width(), height()); + + QPainter painter(&_background); + + painter.fillRect(rect(), palette().color(QPalette::Light)); + + QColor bg_color(112, 154, 209); + if (!isEnabled() && !_preview_only) + 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); + + 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(); + + // horizontal grid + for (int i = 0; i <= maxy; i += ystep) + { + const int y = int(pixel_bounds.height() - i * c.y() + pixel_bounds.y()); + drawLine(painter, + QPoint(pixel_bounds.x(), y), + QPoint(pixel_bounds.x() + pixel_bounds.width(), y), + pen); + painter.drawText(QRectF(10, + y - metrics.height()/2., + pixel_bounds.left(), + metrics.height()), + QString::number(i)); + } + + // vertical grid + for (int i = 0; i <= maxx; i += xstep) + { + const int x = iround(pixel_bounds.x() + i * c.x()); + drawLine(painter, + QPoint(x, pixel_bounds.y()), + QPoint(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 spline_widget::drawFunction() +{ + _function = _background; + QPainter painter(&_function); + painter.setRenderHint(QPainter::Antialiasing, true); + + 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++) + { + const QPointF tmp_ = point_to_pixel(points[i]); + const QPoint tmp(iround(tmp_.x()), iround(tmp_.y())); + drawLine(painter, prev, tmp, pen); + prev = tmp; + } + } + + const QColor color = progn + ( + if (!isEnabled() && !_preview_only) + { + QColor color(spline_color); + const int avg = int(float(color.red() + color.green() + color.blue())/3); + return QColor(int(float(color.red() + avg) * .5f), + int(float(color.green() + avg) * .5f), + int(float(color.blue() + avg) * .5f), + 96); + } + else + { + QColor color(spline_color); + color.setAlphaF(color.alphaF() * .9); + return color; + } + ); + + painter.setPen(QPen(color, 1.75, Qt::SolidLine, Qt::FlatCap)); + +//#define DEBUG_SPLINE +#ifndef DEBUG_SPLINE + static constexpr double step_ = 3./3; + + const double maxx = _config->max_input(); + const double step = step_ / c.x(); + + QPainterPath path; + + path.moveTo(point_to_pixel(QPointF(0, 0))); + + 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 QPointF b(point_to_pixel_(QPointF(k + step*1, qreal(next_1)))), + c(point_to_pixel_(QPointF(k + step*2, qreal(next_2)))), + d(point_to_pixel_(QPointF(k + step*3, qreal(next_3)))); + + path.cubicTo(b, c, d); + } + + painter.drawPath(path); +#else + static 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) + { + const qreal val = qreal(_config->get_value_no_save(i)); + const QPointF cur = point_to_pixel(QPointF(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))); + } +#endif + + const int alpha = !isEnabled() ? 64 : 120; + if (!_preview_only) + { + 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)); + } + } +} + +void spline_widget::paintEvent(QPaintEvent *e) +{ + if (!_config) + return; + + QPainter p(this); + + if (_background.isNull()) + { + _draw_function = true; + drawBackground(); + } + + if (_draw_function) + { + _draw_function = false; + drawFunction(); + } + + p.drawPixmap(e->rect(), _function); + + // 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()) + drawPoint(p, point_to_pixel(last), QColor(255, 0, 0, 120)); +} + +void spline_widget::drawPoint(QPainter& painter, const QPointF& pos, const QColor& colBG, const QColor& border) +{ + 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.restore(); +} + +void spline_widget::drawLine(QPainter& painter, const QPoint& start, const QPoint& end, const QPen& pen) +{ + painter.save(); + painter.setPen(pen); + painter.setBrush(Qt::NoBrush); + painter.drawLine(start, end); + painter.restore(); +} + +void spline_widget::mousePressEvent(QMouseEvent *e) +{ + if (!_config || !isEnabled() || !is_in_bounds(e->pos())) + { + clearFocus(); + return; + } + + const int point_pixel_closeness_limit = get_closeness_limit(); + + points_t points = _config->get_points(); + if (e->button() == Qt::LeftButton) + { + bool bTouchingPoint = false; + moving_control_point_idx = -1; + for (int i = 0; i < points.size(); i++) + { + if (point_within_pixel(points[i], e->pos())) + { + bTouchingPoint = true; + moving_control_point_idx = int(i); + break; + } + } + if (!bTouchingPoint) + { + bool too_close = false; + const QPoint pos = e->pos(); + + for (int i = 0; i < points.size(); i++) + { + const QPointF pt = point_to_pixel(points[i]); + const qreal x = std::fabs(pt.x() - pos.x()); + if (point_pixel_closeness_limit >= x) + { + too_close = true; + break; + } + } + + if (!too_close) + { + _config->add_point(pixel_coord_to_point(e->pos())); + show_tooltip(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->remove_point(found_pt); + } + moving_control_point_idx = -1; + } + } + _draw_function = true; + update(); +} + +void spline_widget::mouseMoveEvent(QMouseEvent *e) +{ + if (_preview_only && _config) + { + show_tooltip(e->pos()); + clearFocus(); + return; + } + + if (!_config || !isEnabled() || !isActiveWindow() || (moving_control_point_idx != -1 && !hasFocus())) + { + clearFocus(); + return; + } + + const int i = moving_control_point_idx; + const points_t points = _config->get_points(); + const int sz = points.size(); + + if (i >= 0 && i < sz) + { + const int point_pixel_closeness_limit = get_closeness_limit(); + bool overlap = false; + + const QPoint pix = progn( + // takes snap into account + const QPointF pix_ = point_to_pixel(pixel_coord_to_point(e->pos())); + return QPoint(int(pix_.x()), int(pix_.y())); + ); + + QPointF new_pt = pixel_coord_to_point(e->pos()); + + if (i + 1 < points.size()) + { + const QPointF other_pix = point_to_pixel(points[i+1]); + overlap |= other_pix.x() - pix.x() < point_pixel_closeness_limit; + } + if (i != 0) + { + const QPointF other_pix = point_to_pixel(points[i-1]); + overlap |= pix.x() - other_pix.x() < point_pixel_closeness_limit; + } + + if (overlap) + new_pt.setX(points[i].x()); + + _config->move_point(i, new_pt); + _draw_function = true; + + setCursor(Qt::ClosedHandCursor); + show_tooltip(pix, new_pt); + update(); + } + else if (sz) + { + int i; + bool is_on_point = is_on_pt(e->pos(), &i); + + if (is_on_point) + { + setCursor(Qt::CrossCursor); + show_tooltip(e->pos(), points[i]); + } + else + { + setCursor(Qt::ArrowCursor); + if (is_in_bounds(e->pos())) + show_tooltip(e->pos()); + else + QToolTip::hideText(); + } + } +} + +void spline_widget::mouseReleaseEvent(QMouseEvent *e) +{ + if (!_config || !isEnabled() || !isActiveWindow() || !hasFocus()) + { + clearFocus(); + return; + } + + if (e->button() == Qt::LeftButton) + { + { + if (is_on_pt(e->pos(), nullptr)) + setCursor(Qt::CrossCursor); + else + setCursor(Qt::ArrowCursor); + } + moving_control_point_idx = -1; + _draw_function = true; + + if (is_in_bounds(e->pos())) + show_tooltip(e->pos()); + else + QToolTip::hideText(); + + update(); + } +} + +void spline_widget::reload_spline() +{ + // don't recompute here as the value's about to be recomputed in the callee + update_range(); +} + +int spline_widget::get_closeness_limit() +{ + return std::max(iround(snap_x * c.x()), 1); +} + +void spline_widget::show_tooltip(const QPoint& pos, const QPointF& value_) +{ + const QPointF value = QPoint(0, 0) == value_ ? pixel_coord_to_point(pos) : value_; + + double x = value.x(), y = value.y(); + + 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) + x = x_; + if (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); + + const QPoint pix(int(pos.x()) + add_x, int(pos.y()) + add_y); + + QToolTip::showText(mapToGlobal(pix), + QStringLiteral("value: %1x%2").arg(x).arg(y), + this, + rect(), + 0); +} + +bool spline_widget::is_in_bounds(const QPoint& pos) const +{ + static constexpr int grace = point_size * 3; + static constexpr int bottom_grace = int(point_size * 1.5); + return (pos.x() + grace > pixel_bounds.left() && + pos.x() - grace < pixel_bounds.right() && + pos.y() + grace > pixel_bounds.top() && + pos.y() - bottom_grace < pixel_bounds.bottom()); +} + +void spline_widget::update_range() +{ + 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(); + + update(); +} + +bool spline_widget::point_within_pixel(const QPointF& pt, const QPoint &pixel) +{ + const QPointF tmp = pixel - point_to_pixel(pt); + return QPointF::dotProduct(tmp, tmp) < point_size * point_size; +} + +void spline_widget::focusOutEvent(QFocusEvent* e) +{ + if (moving_control_point_idx != -1) + QToolTip::hideText(); + moving_control_point_idx = -1; + _draw_function = true; + lower(); + setCursor(Qt::ArrowCursor); + e->accept(); +} + +QPointF spline_widget::pixel_coord_to_point(const QPoint& point) +{ + qreal x = (point.x() - pixel_bounds.x()) / c.x(); + qreal y = (pixel_bounds.height() - point.y() + pixel_bounds.y()) / c.y(); + + static constexpr int c = 1000; + + if (snap_x > 0) + { + x += snap_x * .5; + x -= std::fmod(x, snap_x); + // truncate after few decimal places to reduce rounding errors. + // round upward to nearest. + 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); +} + +QPointF spline_widget::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()); +} + +QPoint spline_widget::point_to_pixel(const QPointF& point) +{ + QPointF pt(point_to_pixel_(point)); + + return QPoint(iround(pt.x()), iround(pt.y())); +} + +void spline_widget::resizeEvent(QResizeEvent *) +{ + update_range(); +} + +bool spline_widget::is_on_pt(const QPoint& pos, int* pt) +{ + if (!_config) + { + if (pt) + *pt = -1; + return false; + } + + const points_t points = _config->get_points(); + + for (int i = 0; i < points.size(); i++) + { + if (point_within_pixel(points[i], pos)) + { + if (pt) + *pt = i; + return true; + } + } + + if (pt) + *pt = -1; + return false; +} |