From 16580841a63f0f7830bd31218311787780570297 Mon Sep 17 00:00:00 2001 From: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon, 28 Mar 2022 00:01:48 -0700 Subject: [PATCH] Squashed commit of the following: commit 8619bb044081da81b4eef238d138bd051e678a4c Merge: 1a767e0c2 1c30c4d36 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun Mar 27 23:59:10 2022 -0700 Merge branch 'master' into gizmo-rework commit 1a767e0c2279cac3baf96a9028c1b78ec0491e00 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun Mar 27 17:23:07 2022 -0700 implement custom html parser/exporter commit bbd9361d345ce864d198f4340b3d59f526e642e5 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed Mar 9 22:27:17 2022 -0800 textgizmo: further improved behavior commit 27634d5d377f03cab009bca418765d73764893f2 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed Mar 9 22:26:19 2022 -0800 textgizmo: improved double click behavior commit 0897ebfa36e676461c7a47305a3c1bf66e7a9fd1 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed Mar 9 10:20:57 2022 -0800 text: added more features and improvements commit 9d6446a25d977ea0c66e5f5c69239aed0e92fe83 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed Mar 9 10:14:59 2022 -0800 text: don't create color input commit d1d3b9d46e74d8197ba3485a8e965068eb245166 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed Mar 9 09:10:23 2022 -0800 text: convert to reference space commit 3ba1565129a0bdd74b860b1869fec5ef30025abb Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon Mar 7 17:57:26 2022 -0800 finally got scaling to work holy shit I never thought it'd be this easy commit 081368cfac9b49a171db6c1d576e134903dffac0 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon Mar 7 11:25:58 2022 -0800 improved text behavior commit f3eda52eee29b7cd354d7d90b3ae1a819e1b1cf2 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon Mar 7 11:08:31 2022 -0800 implemented font styles (weights and italics) commit 0898b613ec9b473ac93996536d00541e0a2a3f74 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Mon Mar 7 01:26:13 2022 -0800 implemented beginnings of text gizmo commit bffb8ede2d425e7391d16bc03e613d4d90367add Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sun Mar 6 17:55:03 2022 -0800 conformed transform node to new gizmos commit 1a1f86fa9bec9ea6b67f77032748c04b6b4c95fd Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Sat Mar 5 11:16:34 2022 -0800 update polygon node commit 2388dd69164e8c57e0a1d221c7680a0d39173c2c Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Fri Mar 4 15:31:39 2022 -0800 implement glsl hack for corner pin commit e96df6cb326fb278cc6555890ff0ac1d37ebf4bf Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu Mar 3 17:06:14 2022 -0800 updated corner pin commit c70aa0e72d4e83045c84259294608c1380e8929b Merge: 5afed6383 780342881 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu Mar 3 17:04:40 2022 -0800 Merge branch 'master' into gizmo-rework commit 5afed6383952d7d936b5eeda2556339c854610af Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu Mar 3 16:55:57 2022 -0800 use float points for more precise math commit 159ade0d2812d556dee57f99e3f733ef1e838d01 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu Mar 3 16:55:40 2022 -0800 ported shape node commit 28f9500d7bc6cd2661ee9ea591db7b3f8d583196 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Thu Mar 3 15:50:19 2022 -0800 implemented enough dragging code to reimplement crop Requires far less boiler-plate code per node commit 5aeaa59227ad2f1dc3c7f499c636de843a262a88 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed Mar 2 19:45:04 2022 -0800 update just merged corner pin for new gizmos commit 8a2d041650d1b873c6d87664564ebcdf92a3760f Merge: c916e8e08 f5b4729a1 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed Mar 2 19:38:36 2022 -0800 Merge branch 'master' into gizmo-rework commit c916e8e08016c1d0b72d85087db76e4137587d52 Author: itsmattkc <34096995+itsmattkc@users.noreply.github.com> Date: Wed Mar 2 19:35:09 2022 -0800 gizmos: rewrote drawing into standard routines Work towards a far more maintainable gizmo system --- CMakeLists.txt | 2 +- app/common/CMakeLists.txt | 3 + app/common/html.cpp | 469 +++++++++++++++++ app/common/html.h | 60 +++ app/common/qtutils.cpp | 7 + app/common/qtutils.h | 2 + app/common/util.h | 30 ++ app/node/CMakeLists.txt | 1 + .../cornerpin/cornerpindistortnode.cpp | 91 +--- .../distort/cornerpin/cornerpindistortnode.h | 25 +- app/node/distort/crop/cropdistortnode.cpp | 184 ++----- app/node/distort/crop/cropdistortnode.h | 35 +- .../transform/transformdistortnode.cpp | 496 ++++++++---------- .../distort/transform/transformdistortnode.h | 50 +- app/node/factory.cpp | 18 +- app/node/factory.h | 5 +- app/node/generator/polygon/polygon.cpp | 183 +++---- app/node/generator/polygon/polygon.h | 27 +- app/node/generator/shape/shapenodebase.cpp | 295 +++++------ app/node/generator/shape/shapenodebase.h | 40 +- app/node/generator/text/CMakeLists.txt | 10 +- .../text/{textlegacy.cpp => textv1.cpp} | 30 +- .../generator/text/{textlegacy.h => textv1.h} | 14 +- .../generator/text/{text.cpp => textv2.cpp} | 30 +- app/node/generator/text/{text.h => textv2.h} | 14 +- app/node/generator/text/textv3.cpp | 140 +++++ app/node/generator/text/textv3.h | 60 +++ app/node/gizmo/CMakeLists.txt | 36 ++ app/node/gizmo/draggable.cpp | 52 ++ app/node/gizmo/draggable.h | 85 +++ app/node/gizmo/gizmo.cpp | 30 ++ app/node/gizmo/gizmo.h | 51 ++ app/node/gizmo/line.cpp | 38 ++ app/node/gizmo/line.h | 48 ++ app/node/gizmo/path.cpp | 39 ++ app/node/gizmo/path.h | 48 ++ app/node/gizmo/point.cpp | 99 ++++ app/node/gizmo/point.h | 72 +++ app/node/gizmo/polygon.cpp | 38 ++ app/node/gizmo/polygon.h | 48 ++ app/node/gizmo/screen.cpp | 31 ++ app/node/gizmo/screen.h | 38 ++ app/node/gizmo/text.cpp | 43 ++ app/node/gizmo/text.h | 56 ++ app/node/globals.h | 2 + app/node/inputdragger.h | 10 + app/node/node.cpp | 103 ++-- app/node/node.h | 53 +- app/node/value.h | 2 + app/render/job/generatejob.h | 5 + app/render/renderprocessor.cpp | 9 + app/ui/icons/icons.cpp | 15 + app/ui/icons/icons.h | 9 + .../nodeparamviewwidgetbridge.cpp | 35 +- app/widget/slider/base/sliderbase.cpp | 1 + app/widget/timelinewidget/tool/add.cpp | 4 +- app/widget/viewer/CMakeLists.txt | 2 + app/widget/viewer/viewerdisplay.cpp | 175 +++++- app/widget/viewer/viewerdisplay.h | 13 +- app/widget/viewer/viewertexteditor.cpp | 483 +++++++++++++++++ app/widget/viewer/viewertexteditor.h | 178 +++++++ 61 files changed, 3257 insertions(+), 1015 deletions(-) create mode 100644 app/common/html.cpp create mode 100644 app/common/html.h create mode 100644 app/common/util.h rename app/node/generator/text/{textlegacy.cpp => textv1.cpp} (82%) rename app/node/generator/text/{textlegacy.h => textv1.h} (85%) rename app/node/generator/text/{text.cpp => textv2.cpp} (86%) rename app/node/generator/text/{text.h => textv2.h} (86%) create mode 100644 app/node/generator/text/textv3.cpp create mode 100644 app/node/generator/text/textv3.h create mode 100644 app/node/gizmo/CMakeLists.txt create mode 100644 app/node/gizmo/draggable.cpp create mode 100644 app/node/gizmo/draggable.h create mode 100644 app/node/gizmo/gizmo.cpp create mode 100644 app/node/gizmo/gizmo.h create mode 100644 app/node/gizmo/line.cpp create mode 100644 app/node/gizmo/line.h create mode 100644 app/node/gizmo/path.cpp create mode 100644 app/node/gizmo/path.h create mode 100644 app/node/gizmo/point.cpp create mode 100644 app/node/gizmo/point.h create mode 100644 app/node/gizmo/polygon.cpp create mode 100644 app/node/gizmo/polygon.h create mode 100644 app/node/gizmo/screen.cpp create mode 100644 app/node/gizmo/screen.h create mode 100644 app/node/gizmo/text.cpp create mode 100644 app/node/gizmo/text.h create mode 100644 app/widget/viewer/viewertexteditor.cpp create mode 100644 app/widget/viewer/viewertexteditor.h diff --git a/CMakeLists.txt b/CMakeLists.txt index cd0c6d89a..8df0764d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,7 +22,7 @@ option(BUILD_DOXYGEN "Build Doxygen documentation" OFF) option(BUILD_TESTS "Build unit tests" ON) option(USE_WERROR "Error on compile warning" ON) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) diff --git a/app/common/CMakeLists.txt b/app/common/CMakeLists.txt index 2938ad229..cc881d473 100644 --- a/app/common/CMakeLists.txt +++ b/app/common/CMakeLists.txt @@ -37,6 +37,8 @@ set(OLIVE_SOURCES common/flipmodifiers.cpp common/flipmodifiers.h common/functiontimer.h + common/html.cpp + common/html.h common/jobtime.cpp common/jobtime.h common/lerp.h @@ -61,6 +63,7 @@ set(OLIVE_SOURCES common/timerange.cpp common/timerange.h common/tohex.h + common/util.h common/xmlutils.cpp common/xmlutils.h PARENT_SCOPE diff --git a/app/common/html.cpp b/app/common/html.cpp new file mode 100644 index 000000000..1b58418fa --- /dev/null +++ b/app/common/html.cpp @@ -0,0 +1,469 @@ +#include "html.h" + +#include +#include + +#include "xmlutils.h" + +namespace olive { + +const QVector Html::kBlockTags = { + QStringLiteral("p"), + QStringLiteral("div") +}; + +inline bool StrEquals(const QString &a, const QStringRef &b) +{ + return !a.compare(b, Qt::CaseInsensitive); +} + +inline bool StrEquals(const QString &a, const QString &b) +{ + return !a.compare(b, Qt::CaseInsensitive); +} + +inline bool StrEquals(const QStringRef &a, const QString &b) +{ + return !a.compare(b, Qt::CaseInsensitive); +} + +inline bool StrEquals(const QStringRef &a, const QStringRef &b) +{ + return !a.compare(b, Qt::CaseInsensitive); +} + +QString Html::DocToHtml(const QTextDocument *doc) +{ + QString html; + QXmlStreamWriter writer(&html); + + //writer.setAutoFormatting(true); + + for (auto it=doc->begin(); it!=doc->end(); it=it.next()) { + WriteBlock(&writer, it); + } + + return html; +} + +struct HtmlNode { + QString tag; + QTextCharFormat format; +}; + +QTextCharFormat MergeHtmlFormats(const QVector &stack) +{ + QTextCharFormat f; + + for (int i=0; iclear(); + bool inside_block = true; + + // Create cursor, which appears to be Qt's official way of inserting blocks and fragments + QTextCursor c(doc); + + QString wrapped = QStringLiteral("").append(html).append(""); + QXmlStreamReader reader(wrapped); + + QVector fmt_stack; + + QTextCharFormat default_fmt; + default_fmt.setFontWeight(QFont::Normal); + fmt_stack.append({QStringLiteral("html"), default_fmt}); + + QTextCharFormat current_fmt; + + while (!reader.atEnd()) { + reader.readNext(); + + if (reader.tokenType() == QXmlStreamReader::StartElement) { + + QString tag = reader.name().toString().toLower(); + + fmt_stack.append({tag, ReadCharFormat(reader.attributes())}); + current_fmt = MergeHtmlFormats(fmt_stack); + + if (kBlockTags.contains(tag)) { + QTextBlockFormat block_fmt = ReadBlockFormat(reader.attributes()); + if (inside_block) { + c.setBlockFormat(block_fmt); + c.setBlockCharFormat(current_fmt); + } else { + c.insertBlock(block_fmt, current_fmt); + inside_block = true; + } + } + + } else if (reader.tokenType() == QXmlStreamReader::Characters) { + + QString characters = reader.text().toString(); + c.insertText(characters, current_fmt); + + } else if (reader.tokenType() == QXmlStreamReader::EndElement) { + + QString tag = reader.name().toString().toLower(); + + for (int i=fmt_stack.size()-1; i>=0; i--) { + if (fmt_stack.at(i).tag == tag) { + fmt_stack.removeAt(i); + current_fmt = MergeHtmlFormats(fmt_stack); + + if (kBlockTags.contains(tag)) { + inside_block = false; + } + break; + } + } + + } + } + + if (reader.error()) { + qCritical() << "Failed to parse HTML:" << reader.errorString(); + } +} + +void Html::WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block) +{ + writer->writeStartElement(QStringLiteral("p")); + + const QTextBlockFormat &fmt = block.blockFormat(); + + // Write block alignment + if (!(fmt.alignment() & Qt::AlignLeft)) { + if (fmt.alignment() & Qt::AlignRight) { + writer->writeAttribute(QStringLiteral("align"), QStringLiteral("right")); + } else if (fmt.alignment() & Qt::AlignCenter) { + writer->writeAttribute(QStringLiteral("align"), QStringLiteral("center")); + } else if (fmt.alignment() & Qt::AlignJustify) { + writer->writeAttribute(QStringLiteral("align"), QStringLiteral("justify")); + } + } + + // RTL support + if (block.textDirection() == Qt::RightToLeft) { + writer->writeAttribute(QStringLiteral("dir"), QStringLiteral("rtl")); + } + + // Write CSS attributes + QString style; + + if (fmt.lineHeightType() != QTextBlockFormat::SingleHeight) { + WriteCSSProperty(&style, QStringLiteral("line-height"), QStringLiteral("%1%").arg(fmt.lineHeight())); + } + + //WriteCharFormat(&style, block.charFormat()); + + if (!style.isEmpty()) { + writer->writeAttribute(QStringLiteral("style"), style); + } + + auto it = block.begin(); + + if (it == block.end()) { + // FIXME: Might not be necessary with our custom HTML implementation + QString s; + s.append(QChar::Nbsp); + writer->writeCharacters(s); + } else { + for (; it!=block.end(); it++) { + WriteFragment(writer, it.fragment()); + } + } + + writer->writeEndElement(); // p +} + +void Html::WriteFragment(QXmlStreamWriter *writer, const QTextFragment &fragment) +{ + const QTextCharFormat &fmt = fragment.charFormat(); + + writer->writeStartElement(QStringLiteral("span")); + + // Write CSS attributes + QString style; + + WriteCharFormat(&style, fmt); + + if (!style.isEmpty()) { + writer->writeAttribute(QStringLiteral("style"), style); + } + + QStringList lines = fragment.text().split(QChar::LineSeparator); + bool first_line = true; + foreach (const QString &l, lines) { + if (first_line) { + first_line = false; + } else { + writer->writeEmptyElement(QStringLiteral("br")); + } + writer->writeCharacters(l); + } + + writer->writeEndElement(); // span +} + +void Html::WriteCSSProperty(QString *style, const QString &key, const QStringList &values) +{ + QString value; + foreach (QString v, values) { + if (v.contains(' ')) { + v = QStringLiteral("'%1'").arg(v); + } + + AppendStringAutoSpace(&value, v); + } + + AppendStringAutoSpace(style, QStringLiteral("%1: %2;").arg(key, value)); +} + +void Html::WriteCharFormat(QString *style, const QTextCharFormat &fmt) +{ + if (!fmt.fontFamily().isEmpty()) { + WriteCSSProperty(style, QStringLiteral("font-family"), fmt.fontFamily()); + } + + if (fmt.hasProperty(QTextFormat::FontPointSize)) { + WriteCSSProperty(style, QStringLiteral("font-size"), QStringLiteral("%1pt").arg(QString::number(fmt.fontPointSize()))); + } + + if (fmt.hasProperty(QTextFormat::FontWeight)) { + WriteCSSProperty(style, QStringLiteral("font-weight"), QString::number(fmt.fontWeight() * 8)); + } + + if (fmt.hasProperty(QTextFormat::FontItalic)) { + WriteCSSProperty(style, QStringLiteral("font-style"), fmt.fontItalic() ? QStringLiteral("italic") : QStringLiteral("normal")); + } + + if (fmt.hasProperty(QTextFormat::FontStyleName)) { + WriteCSSProperty(style, QStringLiteral("-ove-font-style"), fmt.fontStyleName().toString()); + } + + QStringList deco; + + if (fmt.fontUnderline()) { + deco.append(QStringLiteral("underline")); + } + + if (fmt.fontStrikeOut()) { + deco.append(QStringLiteral("line-through")); + } + + if (fmt.fontOverline()) { + deco.append(QStringLiteral("overline")); + } + + if (!deco.isEmpty()) { + WriteCSSProperty(style, QStringLiteral("text-decoration"), deco); + } + + if (fmt.foreground().style() != Qt::NoBrush) { + const QColor &color = fmt.foreground().color(); + QString cs; + + if (color.alpha() == 255) { + cs = color.name(); + } else if (color.alpha()) { + cs = QStringLiteral("rgba(%1, %2, %3, %4)").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue()), QString::number(color.alphaF())); + } + + WriteCSSProperty(style, QStringLiteral("color"), cs); + } + + if (fmt.fontCapitalization() != QFont::MixedCase) { + if (fmt.fontCapitalization() == QFont::SmallCaps) { + WriteCSSProperty(style, QStringLiteral("font-variant"), QStringLiteral("small-caps")); + // TODO: Add others + } + } + + if (fmt.fontLetterSpacing() != 0.0) { + WriteCSSProperty(style, QStringLiteral("letter-spacing"), QStringLiteral("%1%").arg(QString::number(fmt.fontLetterSpacing()))); + } + + if (fmt.fontStretch() != 0) { + WriteCSSProperty(style, QStringLiteral("font-stretch"), QStringLiteral("%1%").arg(QString::number(fmt.fontStretch()))); + } +} + +QTextCharFormat Html::ReadCharFormat(const QXmlStreamAttributes &attributes) +{ + QTextCharFormat fmt; + + foreach (const QXmlStreamAttribute &attr, attributes) { + if (StrEquals(attr.name(), QStringLiteral("style"))) { + auto css = GetCSSFromStyle(attr.value().toString()); + + for (auto it=css.begin(); it!=css.end(); it++) { + const QString &first_val = it.value().first(); + + if (it.key() == QStringLiteral("font-family")) { + fmt.setFontFamily(first_val); + } else if (it.key() == QStringLiteral("font-size")) { + if (first_val.endsWith(QStringLiteral("pt"), Qt::CaseInsensitive)) { + fmt.setFontPointSize(first_val.chopped(2).toDouble()); + } + } else if (it.key() == QStringLiteral("font-weight")) { + fmt.setFontWeight(first_val.toInt()/8); + } else if (it.key() == QStringLiteral("font-style")) { + fmt.setFontItalic(StrEquals(first_val, QStringLiteral("italic"))); + } else if (it.key() == QStringLiteral("text-decoration")) { + foreach (const QString &v, it.value()) { + if (StrEquals(v, QStringLiteral("underline"))) { + fmt.setFontUnderline(true); + } else if (StrEquals(v, QStringLiteral("line-through"))) { + fmt.setFontStrikeOut(true); + } else if (StrEquals(v, QStringLiteral("overline"))) { + fmt.setFontOverline(true); + } + } + } else if (it.key() == QStringLiteral("color")) { + if (first_val.startsWith(QStringLiteral("rgba"), Qt::CaseInsensitive)) { + QString vals_only = first_val; + vals_only.remove(QStringLiteral("rgba")); + vals_only.remove(QStringLiteral("(")); + vals_only.remove(QStringLiteral(")")); + QStringList rgba = vals_only.split(','); + if (rgba.size() == 4) { + QColor c; + c.setRedF(rgba.at(0).toDouble()); + c.setGreenF(rgba.at(1).toDouble()); + c.setBlueF(rgba.at(2).toDouble()); + c.setAlphaF(rgba.at(3).toDouble()); + fmt.setForeground(c); + } + } else { + fmt.setForeground(QColor(first_val)); + } + } else if (it.key() == QStringLiteral("font-variant")) { + if (StrEquals(first_val, QStringLiteral("small-caps"))) { + fmt.setFontCapitalization(QFont::SmallCaps); + } + } else if (it.key() == QStringLiteral("letter-spacing")) { + if (first_val.contains(QChar('%'))) { + fmt.setFontLetterSpacing(first_val.chopped(1).toDouble()); + } + } else if (it.key() == QStringLiteral("font-stretch")) { + if (first_val.contains(QChar('%'))) { + fmt.setFontStretch(first_val.chopped(1).toInt()); + } + } else if (it.key() == QStringLiteral("-ove-font-style")) { + fmt.setFontStyleName(first_val); + } + } + } + } + return fmt; +} + +QTextBlockFormat Html::ReadBlockFormat(const QXmlStreamAttributes &attributes) +{ + QTextBlockFormat block_fmt; + + foreach (const QXmlStreamAttribute &attr, attributes) { + if (StrEquals(attr.name(), QStringLiteral("align"))) { + if (StrEquals(attr.value(), QStringLiteral("right"))) { + block_fmt.setAlignment(Qt::AlignRight); + } else if (StrEquals(attr.value(), QStringLiteral("center"))) { + block_fmt.setAlignment(Qt::AlignCenter); + } else if (StrEquals(attr.value(), QStringLiteral("justify"))) { + block_fmt.setAlignment(Qt::AlignJustify); + } + } else if (StrEquals(attr.name(), QStringLiteral("dir"))) { + if (StrEquals(attr.value(), QStringLiteral("rtl"))) { + block_fmt.setLayoutDirection(Qt::RightToLeft); + } + } else if (StrEquals(attr.name(), QStringLiteral("style"))) { + auto css = GetCSSFromStyle(attr.value().toString()); + + for (auto it=css.begin(); it!=css.end(); it++) { + if (it.key() == QStringLiteral("line-height")) { + const QString &first_val = it.value().constFirst(); + if (first_val.contains(QChar('%'))) { + block_fmt.setLineHeight(first_val.chopped(1).toDouble(), QTextBlockFormat::ProportionalHeight); + } + } + } + } + } + + return block_fmt; +} + +void Html::AppendStringAutoSpace(QString *s, const QString &append) +{ + if (!s->isEmpty()) { + s->append(QChar(' ')); + } + + s->append(append); +} + +QMap Html::GetCSSFromStyle(const QString &s) +{ + QMap map; + + QStringList list = s.split(QChar(';')); + + foreach (const QString &a, list) { + QStringList kv = a.split(QChar(':')); + + if (kv.size() != 2) { + continue; + } + + // I'm sure there's regex that could do this, but I couldn't figure it out. It needs to split + // by space EXCEPT within quotes OR double-quotes, and said quotes should be EXCLUDED from each + // match. Also commas should be filtered out. + QStringList values; + const QString &val = kv.at(1); + QChar in_quote = 0; + QString current_str; + for (int i=0; i +#include +#include +#include + +namespace olive { + +/** + * @brief Functions for converting HTML to QTextDocument and vice versa + * + * Qt does contain its own functions for this, however they have some limitations. Some things that + * we want to support (e.g. kerning/spacing and font stretch) are not implemented in Qt's + * QTextHtmlExporter and QTextHtmlParser. Additionally, since these functions are not part of Qt's + * public API, and make many references to other parts of Qt that are not part of the public API, + * there is no way to subclass or extend their functionality without forking Qt as a whole. + * + * Therefore, it became necessary to write a custom class for the conversion so that we can + * ensure support for the features we need. + * + * If someone wishes to extend this class for more feature support, feel free to open a pull + * request. But this is NOT intended to be an exhaustive HTML implementation, and is primarily + * designed to store rich text in a standard format for the purpose of text formatting for video. + */ +class Html { +public: + static QString DocToHtml(const QTextDocument *doc); + + static void HtmlToDoc(QTextDocument *doc, const QString &html); + +private: + static void WriteBlock(QXmlStreamWriter *writer, const QTextBlock &block); + + static void WriteFragment(QXmlStreamWriter *writer, const QTextFragment &fragment); + + static void WriteCSSProperty(QString *style, const QString &key, const QStringList &value); + static void WriteCSSProperty(QString *style, const QString &key, const QString &value) + { + WriteCSSProperty(style, key, QStringList({value})); + } + + static void WriteCharFormat(QString *style, const QTextCharFormat &fmt); + + static QTextCharFormat ReadCharFormat(const QXmlStreamAttributes &attributes); + + static QTextBlockFormat ReadBlockFormat(const QXmlStreamAttributes &attributes); + + static void AppendStringAutoSpace(QString *s, const QString &append); + + static QMap GetCSSFromStyle(const QString &s); + + static const QVector kBlockTags; + +}; + +} + +#endif // HTML_H diff --git a/app/common/qtutils.cpp b/app/common/qtutils.cpp index 757ca2617..f4f363880 100644 --- a/app/common/qtutils.cpp +++ b/app/common/qtutils.cpp @@ -38,6 +38,13 @@ QFrame *QtUtils::CreateHorizontalLine() return horizontal_line; } +QFrame *QtUtils::CreateVerticalLine() +{ + QFrame *l = CreateHorizontalLine(); + l->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); + return l; +} + int QtUtils::MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &message, QMessageBox::StandardButtons buttons) { QMessageBox b(parent); diff --git a/app/common/qtutils.h b/app/common/qtutils.h index 2247f89d8..45321c76b 100644 --- a/app/common/qtutils.h +++ b/app/common/qtutils.h @@ -54,6 +54,8 @@ public: static QFrame* CreateHorizontalLine(); + static QFrame* CreateVerticalLine(); + static int MessageBox(QWidget *parent, QMessageBox::Icon icon, const QString& title, const QString& message, QMessageBox::StandardButtons buttons = QMessageBox::Ok); static QDateTime GetCreationDate(const QFileInfo &info); diff --git a/app/common/util.h b/app/common/util.h new file mode 100644 index 000000000..bc1642f81 --- /dev/null +++ b/app/common/util.h @@ -0,0 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef UTIL_H +#define UTIL_H + +template +inline T mid(T a, T b) +{ + return (a + b) * 0.5; +} + +#endif // UTIL_H diff --git a/app/node/CMakeLists.txt b/app/node/CMakeLists.txt index c97d250ca..aa1a090d4 100644 --- a/app/node/CMakeLists.txt +++ b/app/node/CMakeLists.txt @@ -21,6 +21,7 @@ add_subdirectory(distort) add_subdirectory(effect) add_subdirectory(filter) add_subdirectory(generator) +add_subdirectory(gizmo) add_subdirectory(group) add_subdirectory(input) add_subdirectory(math) diff --git a/app/node/distort/cornerpin/cornerpindistortnode.cpp b/app/node/distort/cornerpin/cornerpindistortnode.cpp index 4197f9649..e4429ede7 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.cpp +++ b/app/node/distort/cornerpin/cornerpindistortnode.cpp @@ -41,6 +41,13 @@ CornerPinDistortNode::CornerPinDistortNode() AddInput(kTopRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); AddInput(kBottomRightInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); AddInput(kBottomLeftInput, NodeValue::kVec2, QVector2D(0.0, 0.0)); + + // Initiate gizmos + gizmo_whole_rect_ = AddDraggableGizmo(); + gizmo_resize_handle_[0] = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 0), NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 1)}); + gizmo_resize_handle_[1] = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 1)}); + gizmo_resize_handle_[2] = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 1)}); + gizmo_resize_handle_[3] = AddDraggableGizmo({NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 1)}); } void CornerPinDistortNode::Retranslate() @@ -127,14 +134,20 @@ QPointF CornerPinDistortNode::ValueToPixel(int value, const NodeValueRow& row, c } } -void CornerPinDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) +void CornerPinDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) +{ + DraggableGizmo *gizmo = static_cast(sender()); + + if (gizmo != gizmo_whole_rect_) { + gizmo->GetDraggers()[0].Drag(gizmo->GetDraggers()[0].GetStartValue().toDouble() + x); + gizmo->GetDraggers()[1].Drag(gizmo->GetDraggers()[1].GetStartValue().toDouble() + y); + } +} + +void CornerPinDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { const QVector2D &resolution = globals.resolution(); - const double handle_radius = GetGizmoHandleRadius(p->transform()); - - p->setPen(QPen(Qt::white, 0)); - QPointF top_left = ValueToPixel(0, row, resolution); QPointF top_right = ValueToPixel(1, row, resolution); QPointF bottom_right = ValueToPixel(2, row, resolution); @@ -147,71 +160,13 @@ void CornerPinDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals SetInputProperty(kBottomLeftInput, QStringLiteral("offset"), QVector2D(0.0, resolution.y())); // Draw bounding box - p->drawLine(QLineF(top_left, top_right)); - p->drawLine(QLineF(top_right, bottom_right)); - p->drawLine(QLineF(bottom_right, bottom_left)); - p->drawLine(QLineF(bottom_left, top_left)); + gizmo_whole_rect_->SetPolygon(QPolygonF({top_left, top_right, bottom_right, bottom_left, top_left})); // Create handles - gizmo_resize_handle_[0] = CreateGizmoHandleRect(top_left, handle_radius); - gizmo_resize_handle_[1] = CreateGizmoHandleRect(top_right, handle_radius); - gizmo_resize_handle_[2] = CreateGizmoHandleRect(bottom_right, handle_radius); - gizmo_resize_handle_[3] = CreateGizmoHandleRect(bottom_left, handle_radius); - - // Draw handles - DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoCornerCount); -} - -bool CornerPinDistortNode::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) -{ - bool gizmo_active[kGizmoCornerCount] = {false}; - - for (int i = 0; i < kGizmoCornerCount; i++) { - gizmo_active[i] = gizmo_resize_handle_[i].contains(p); - - if (gizmo_active[i]) { - gizmo_drag_start_ = p; - gizmo_res_ = globals.resolution(); - gizmo_drag_ = i; - return true; - } - } - - return false; -} - -void CornerPinDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) -{ - if (gizmo_dragger_.isEmpty()) { - gizmo_dragger_.resize(2); - if (gizmo_drag_ == 0) { - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 0), time); - gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 1), time); - } - if (gizmo_drag_ == 1) { - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 0), time); - gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 1), time); - } - if (gizmo_drag_ == 2) { - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 0), time); - gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 1), time); - } - if (gizmo_drag_ == 3) { - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 0), time); - gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kBottomLeftInput), 1), time); - } - } - - QPointF diff = p - gizmo_drag_start_; - gizmo_dragger_[0].Drag(gizmo_dragger_[0].GetStartValue().toDouble() + diff.x()); - gizmo_dragger_[1].Drag(gizmo_dragger_[1].GetStartValue().toDouble() + diff.y()); -} - -void CornerPinDistortNode::GizmoRelease(MultiUndoCommand *command) { - for (NodeInputDragger &i : gizmo_dragger_) { - i.End(command); - } - gizmo_dragger_.clear(); + gizmo_resize_handle_[0]->SetPoint(top_left); + gizmo_resize_handle_[1]->SetPoint(top_right); + gizmo_resize_handle_[2]->SetPoint(bottom_right); + gizmo_resize_handle_[3]->SetPoint(bottom_left); } } diff --git a/app/node/distort/cornerpin/cornerpindistortnode.h b/app/node/distort/cornerpin/cornerpindistortnode.h index 6261d7e1a..86d80952f 100644 --- a/app/node/distort/cornerpin/cornerpindistortnode.h +++ b/app/node/distort/cornerpin/cornerpindistortnode.h @@ -23,6 +23,8 @@ #include +#include "node/gizmo/point.h" +#include "node/gizmo/polygon.h" #include "node/inputdragger.h" #include "node/node.h" @@ -66,16 +68,7 @@ public: virtual ShaderCode GetShaderCode(const QString &shader_id) const override; - virtual bool HasGizmos() const override - { - return true; - } - - virtual void DrawGizmos(const NodeValueRow& row, const NodeGlobals &globals, QPainter *p) override; - - virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override; - virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease(MultiUndoCommand *command) override; + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; /** * @brief Convenience function - converts the 2D slider values from being @@ -90,16 +83,14 @@ public: static const QString kBottomRightInput; static const QString kBottomLeftInput; +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + private: // Gizmo variables static const int kGizmoCornerCount = 4; - QRectF gizmo_resize_handle_[kGizmoCornerCount]; - QRectF gizmo_whole_rect_; - - int gizmo_drag_; - QVector gizmo_dragger_; - QPointF gizmo_drag_start_; - QVector2D gizmo_res_; + PointGizmo *gizmo_resize_handle_[kGizmoCornerCount]; + PolygonGizmo *gizmo_whole_rect_; }; diff --git a/app/node/distort/crop/cropdistortnode.cpp b/app/node/distort/crop/cropdistortnode.cpp index 3307502e3..b204c7eda 100644 --- a/app/node/distort/crop/cropdistortnode.cpp +++ b/app/node/distort/crop/cropdistortnode.cpp @@ -20,7 +20,7 @@ #include "cropdistortnode.h" -#include "common/lerp.h" +#include "common/util.h" #include "core.h" #include "widget/slider/floatslider.h" @@ -44,6 +44,18 @@ CropDistortNode::CropDistortNode() AddInput(kFeatherInput, NodeValue::kFloat, 0.0); SetInputProperty(kFeatherInput, QStringLiteral("min"), 0.0); + + // Initiate gizmos + poly_gizmo_ = AddDraggableGizmo({kLeftInput, kTopInput, kRightInput, kBottomInput}); + + point_gizmo_[kGizmoScaleTopLeft] = AddDraggableGizmo({kLeftInput, kTopInput}); + point_gizmo_[kGizmoScaleTopCenter] = AddDraggableGizmo({kTopInput}); + point_gizmo_[kGizmoScaleTopRight] = AddDraggableGizmo({kRightInput, kTopInput}); + point_gizmo_[kGizmoScaleBottomLeft] = AddDraggableGizmo({kLeftInput, kBottomInput}); + point_gizmo_[kGizmoScaleBottomCenter] = AddDraggableGizmo({kBottomInput}); + point_gizmo_[kGizmoScaleBottomRight] = AddDraggableGizmo({kRightInput, kBottomInput}); + point_gizmo_[kGizmoScaleCenterLeft] = AddDraggableGizmo({kLeftInput}); + point_gizmo_[kGizmoScaleCenterRight] = AddDraggableGizmo({kRightInput}); } void CropDistortNode::Retranslate() @@ -81,164 +93,50 @@ ShaderCode CropDistortNode::GetShaderCode(const QString &shader_id) const return ShaderCode(FileFunctions::ReadFileAsString(QStringLiteral(":/shaders/crop.frag"))); } -void CropDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) +void CropDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { const QVector2D &resolution = globals.resolution(); - const double handle_radius = GetGizmoHandleRadius(p->transform()); - - p->setPen(QPen(Qt::white, 0)); - double left_pt = resolution.x() * row[kLeftInput].data().toDouble(); double top_pt = resolution.y() * row[kTopInput].data().toDouble(); double right_pt = resolution.x() * (1.0 - row[kRightInput].data().toDouble()); double bottom_pt = resolution.y() * (1.0 - row[kBottomInput].data().toDouble()); - double center_x_pt = lerp(left_pt, right_pt, 0.5); - double center_y_pt = lerp(top_pt, bottom_pt, 0.5); + double center_x_pt = mid(left_pt, right_pt); + double center_y_pt = mid(top_pt, bottom_pt); - gizmo_whole_rect_ = QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt); - p->drawRect(gizmo_whole_rect_); + point_gizmo_[kGizmoScaleTopLeft]->SetPoint(QPointF(left_pt, top_pt)); + point_gizmo_[kGizmoScaleTopCenter]->SetPoint(QPointF(center_x_pt, top_pt)); + point_gizmo_[kGizmoScaleTopRight]->SetPoint(QPointF(right_pt, top_pt)); + point_gizmo_[kGizmoScaleBottomLeft]->SetPoint(QPointF(left_pt, bottom_pt)); + point_gizmo_[kGizmoScaleBottomCenter]->SetPoint(QPointF(center_x_pt, bottom_pt)); + point_gizmo_[kGizmoScaleBottomRight]->SetPoint(QPointF(right_pt, bottom_pt)); + point_gizmo_[kGizmoScaleCenterLeft]->SetPoint(QPointF(left_pt, center_y_pt)); + point_gizmo_[kGizmoScaleCenterRight]->SetPoint(QPointF(right_pt, center_y_pt)); - gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(QPointF(left_pt, top_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, top_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(QPointF(right_pt, top_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleBottomLeft] = CreateGizmoHandleRect(QPointF(left_pt, bottom_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleBottomCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, bottom_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleBottomRight] = CreateGizmoHandleRect(QPointF(right_pt, bottom_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(QPointF(left_pt, center_y_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(QPointF(right_pt, center_y_pt), handle_radius); - - DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoScaleCount); + poly_gizmo_->SetPolygon(QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt)); } -bool CropDistortNode::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) +void CropDistortNode::GizmoDragMove(double x_diff, double y_diff, const Qt::KeyboardModifiers &modifiers) { - bool found_handle = false; + DraggableGizmo *gizmo = static_cast(sender()); - bool gizmo_active[kGizmoScaleCount] = {false}; + QVector2D res = gizmo->GetGlobals().resolution(); + x_diff /= res.x(); + y_diff /= res.y(); - for (int i=0; iGetDraggers().size(); j++) { + NodeInputDragger& i = gizmo->GetDraggers()[j]; + double s = i.GetStartValue().toDouble(); + if (i.GetInput().input().input() == kLeftInput) { + i.Drag(s + x_diff); + } else if (i.GetInput().input().input() == kTopInput) { + i.Drag(s + y_diff); + } else if (i.GetInput().input().input() == kRightInput) { + i.Drag(s - x_diff); + } else if (i.GetInput().input().input() == kBottomInput) { + i.Drag(s - y_diff); } } - - bool in_rect = found_handle ? false : gizmo_whole_rect_.contains(p); - - gizmo_drag_ = kGizmoNone; - - // Drag handle - if (gizmo_active[kGizmoScaleTopLeft] - || gizmo_active[kGizmoScaleCenterLeft] - || gizmo_active[kGizmoScaleBottomLeft] - || in_rect) { - gizmo_drag_ |= kGizmoLeft; - - gizmo_start_.append(row[kLeftInput].data()); - } - - if (gizmo_active[kGizmoScaleTopLeft] - || gizmo_active[kGizmoScaleTopCenter] - || gizmo_active[kGizmoScaleTopRight] - || in_rect) { - gizmo_drag_ |= kGizmoTop; - - gizmo_start_.append(row[kTopInput].data()); - } - - if (gizmo_active[kGizmoScaleTopRight] - || gizmo_active[kGizmoScaleCenterRight] - || gizmo_active[kGizmoScaleBottomRight] - || in_rect) { - gizmo_drag_ |= kGizmoRight; - - gizmo_start_.append(row[kRightInput].data()); - } - - if (gizmo_active[kGizmoScaleBottomLeft] - || gizmo_active[kGizmoScaleBottomCenter] - || gizmo_active[kGizmoScaleBottomRight] - || in_rect) { - gizmo_drag_ |= kGizmoBottom; - - gizmo_start_.append(row[kBottomInput].data()); - } - - if (gizmo_drag_ > kGizmoNone) { - gizmo_res_ = globals.resolution(); - gizmo_drag_start_ = p; - - return true; - } - - return false; -} - -void CropDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) -{ - if (gizmo_dragger_.isEmpty()) { - gizmo_dragger_.resize(gizmo_start_.size()); - - int counter = 0; - - if (gizmo_drag_ & kGizmoLeft) { - gizmo_dragger_[counter].Start(NodeInput(this, kLeftInput), time); - counter++; - } - - if (gizmo_drag_ & kGizmoTop) { - gizmo_dragger_[counter].Start(NodeInput(this, kTopInput), time); - counter++; - } - - if (gizmo_drag_ & kGizmoRight) { - gizmo_dragger_[counter].Start(NodeInput(this, kRightInput), time); - counter++; - } - - if (gizmo_drag_ & kGizmoBottom) { - gizmo_dragger_[counter].Start(NodeInput(this, kBottomInput), time); - counter++; - } - } - - int counter = 0; - - double x_diff = (p.x() - gizmo_drag_start_.x()) / gizmo_res_.x(); - double y_diff = (p.y() - gizmo_drag_start_.y()) / gizmo_res_.y(); - - if (gizmo_drag_ & kGizmoLeft) { - gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() + x_diff); - counter++; - } - - if (gizmo_drag_ & kGizmoTop) { - gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() + y_diff); - counter++; - } - - if (gizmo_drag_ & kGizmoRight) { - gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() - x_diff); - counter++; - } - - if (gizmo_drag_ & kGizmoBottom) { - gizmo_dragger_[counter].Drag(gizmo_start_[counter].toDouble() - y_diff); - counter++; - } -} - -void CropDistortNode::GizmoRelease(MultiUndoCommand *command) -{ - for (NodeInputDragger& i : gizmo_dragger_) { - i.End(command); - } - gizmo_dragger_.clear(); - - gizmo_start_.clear(); } void CropDistortNode::CreateCropSideInput(const QString &id) diff --git a/app/node/distort/crop/cropdistortnode.h b/app/node/distort/crop/cropdistortnode.h index 24f2c0b50..d2916a24d 100644 --- a/app/node/distort/crop/cropdistortnode.h +++ b/app/node/distort/crop/cropdistortnode.h @@ -23,6 +23,8 @@ #include +#include "node/gizmo/point.h" +#include "node/gizmo/polygon.h" #include "node/inputdragger.h" #include "node/node.h" @@ -67,16 +69,7 @@ public: virtual ShaderCode GetShaderCode(const QString &shader_id) const override; - virtual bool HasGizmos() const override - { - return true; - } - - virtual void DrawGizmos(const NodeValueRow& row, const NodeGlobals &globals, QPainter *p) override; - - virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override; - virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease(MultiUndoCommand *command) override; + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; static const QString kTextureInput; static const QString kLeftInput; @@ -85,27 +78,15 @@ public: static const QString kBottomInput; static const QString kFeatherInput; +protected slots: + virtual void GizmoDragMove(double delta_x, double delta_y, const Qt::KeyboardModifiers &modifiers) override; + private: void CreateCropSideInput(const QString& id); // Gizmo variables - QRectF gizmo_resize_handle_[kGizmoScaleCount]; - QRectF gizmo_whole_rect_; - - enum GizmoDragDirection { - kGizmoNone = 0x0, - kGizmoLeft = 0x1, - kGizmoTop = 0x2, - kGizmoRight = 0x4, - kGizmoBottom = 0x8, - kGizmoRectangle = 0xFF - }; - - int gizmo_drag_; - QVector gizmo_dragger_; - QVector gizmo_start_; - QPointF gizmo_drag_start_; - QVector2D gizmo_res_; + PointGizmo *point_gizmo_[kGizmoScaleCount]; + PolygonGizmo *poly_gizmo_; }; diff --git a/app/node/distort/transform/transformdistortnode.cpp b/app/node/distort/transform/transformdistortnode.cpp index 260445b66..59ec09bc2 100644 --- a/app/node/distort/transform/transformdistortnode.cpp +++ b/app/node/distort/transform/transformdistortnode.cpp @@ -41,6 +41,29 @@ TransformDistortNode::TransformDistortNode() AddInput(kInterpolationInput, NodeValue::kCombo, 2); PrependInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable)); + + // Initiate gizmos + rotation_gizmo_ = AddDraggableGizmo(); + rotation_gizmo_->AddInput(NodeInput(this, kRotationInput)); + rotation_gizmo_->SetDragValueBehavior(ScreenGizmo::kAbsolute); + + poly_gizmo_ = AddDraggableGizmo(); + poly_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0)); + poly_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1)); + + anchor_gizmo_ = AddDraggableGizmo(); + anchor_gizmo_->SetShape(PointGizmo::kAnchorPoint); + anchor_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kAnchorInput), 0)); + anchor_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kAnchorInput), 1)); + anchor_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0)); + anchor_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1)); + + for (int i=0; i(); + point_gizmo_[i]->AddInput(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 0)); + point_gizmo_[i]->AddInput(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 1)); + point_gizmo_[i]->SetDragValueBehavior(PointGizmo::kAbsolute); + } } void TransformDistortNode::Retranslate() @@ -101,218 +124,6 @@ ShaderCode TransformDistortNode::GetShaderCode(const QString &shader_id) const return ShaderCode(); } -bool TransformDistortNode::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) -{ - TexturePtr tex = row[kTextureInput].data().value(); - if (!tex) { - return false; - } - - // Store cursor position - gizmo_drag_pos_ = p; - - // Check scaling - bool gizmo_scale_active[kGizmoScaleCount] = {false}; - bool scaling = false; - - for (int i=0; iparams(); - QVector2D texture_sz(texture_params.square_pixel_width(), texture_params.height()); - gizmo_scale_anchor_ = row[kAnchorInput].data().value() + texture_sz/2; - - if (gizmo_scale_active[kGizmoScaleTopRight] - || gizmo_scale_active[kGizmoScaleBottomRight] - || gizmo_scale_active[kGizmoScaleCenterRight]) { - // Right handles, flip X axis - gizmo_scale_anchor_.setX(texture_sz.x() - gizmo_scale_anchor_.x()); - } - - if (gizmo_scale_active[kGizmoScaleBottomLeft] - || gizmo_scale_active[kGizmoScaleBottomRight] - || gizmo_scale_active[kGizmoScaleBottomCenter]) { - // Bottom handles, flip Y axis - gizmo_scale_anchor_.setY(texture_sz.y() - gizmo_scale_anchor_.y()); - } - - // Store current matrix - gizmo_matrix_ = GenerateMatrix(row, false, true, true, true); - - return true; - - } else if (gizmo_anchor_pt_.contains(p)) { - - // Dragging the anchor point specifically - gizmo_start_ = {row[kAnchorInput].data(), - row[kPositionInput].data()}; - gizmo_drag_ = kAnchorInput; - - // Store current matrix - gizmo_matrix_ = GenerateMatrix(row, false, true, true, false); - - return true; - - } else if (gizmo_rect_.containsPoint(p, Qt::OddEvenFill)) { - - // Dragging the main rectangle - gizmo_start_ = {row[kPositionInput].data()}; - gizmo_drag_ = kPositionInput; - - return true; - - } else { - - // Dragging rotation - gizmo_start_ = {row[kRotationInput].data()}; - gizmo_drag_ = kRotationInput; - gizmo_start_angle_ = qAtan2(gizmo_drag_pos_.y() - gizmo_anchor_pt_.center().y(), - gizmo_drag_pos_.x() - gizmo_anchor_pt_.center().x()); - - return true; - - } - - return false; -} - -void TransformDistortNode::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) -{ - QPointF movement = (p - gizmo_drag_pos_); - QVector2D vec_movement(movement); - - if (gizmo_drag_ == kAnchorInput) { - - // Dragging the anchor point around - if (gizmo_dragger_.isEmpty()) { - gizmo_dragger_.resize(4); - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kAnchorInput), 0), time); - gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kAnchorInput), 1), time); - gizmo_dragger_[2].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), time); - gizmo_dragger_[3].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), time); - } - - QVector2D inverted_movement(gizmo_matrix_.toTransform().inverted().map(movement)); - QVector2D anchor_cont = gizmo_start_[0].value() + inverted_movement; - QVector2D position_cont = gizmo_start_[1].value() + vec_movement; - - gizmo_dragger_[0].Drag(anchor_cont.x()); - gizmo_dragger_[1].Drag(anchor_cont.y()); - gizmo_dragger_[2].Drag(position_cont.x()); - gizmo_dragger_[3].Drag(position_cont.y()); - - } else if (gizmo_drag_ == kPositionInput) { - - // Dragging the main rectangle around - if (gizmo_dragger_.isEmpty()) { - gizmo_dragger_.resize(2); - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), time); - gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), time); - } - - QVector2D position_cont = gizmo_start_[0].value() + vec_movement; - - gizmo_dragger_[0].Drag(position_cont.x()); - gizmo_dragger_[1].Drag(position_cont.y()); - - } else if (gizmo_drag_ == kScaleInput) { - - // Dragging a resize handle - if (gizmo_dragger_.isEmpty()) { - if (gizmo_scale_uniform_ || gizmo_scale_axes_ == kGizmoScaleXOnly) { - gizmo_dragger_.resize(1); - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 0), time); - } else if (gizmo_scale_axes_ == kGizmoScaleYOnly) { - gizmo_dragger_.resize(1); - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 1), time); - } else { - gizmo_dragger_.resize(2); - gizmo_dragger_[0].Start(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 0), time); - gizmo_dragger_[1].Start(NodeKeyframeTrackReference(NodeInput(this, kScaleInput), 1), time); - } - } - - QPointF mouse_relative = gizmo_matrix_.toTransform().inverted().map(QPointF(p - gizmo_anchor_pt_.center())); - - double x_scaled_movement = qAbs(mouse_relative.x() / gizmo_scale_anchor_.x()); - double y_scaled_movement = qAbs(mouse_relative.y() / gizmo_scale_anchor_.y()); - - switch (gizmo_scale_axes_) { - case kGizmoScaleXOnly: - gizmo_dragger_[0].Drag(x_scaled_movement); - break; - case kGizmoScaleYOnly: - gizmo_dragger_[0].Drag(y_scaled_movement); - break; - case kGizmoScaleBoth: - if (gizmo_scale_uniform_) { - double distance = std::hypot(mouse_relative.x(), mouse_relative.y()); - double texture_diag = std::hypot(gizmo_scale_anchor_.x(), gizmo_scale_anchor_.y()); - - gizmo_dragger_[0].Drag(qAbs(distance / texture_diag)); - } else { - gizmo_dragger_[0].Drag(x_scaled_movement); - gizmo_dragger_[1].Drag(y_scaled_movement); - } - break; - } - - } else if (gizmo_drag_ == kRotationInput) { - - // Dragging outside the rectangle to rotate - if (gizmo_dragger_.isEmpty()) { - gizmo_dragger_.resize(1); - gizmo_dragger_[0].Start(NodeInput(this, kRotationInput), time); - } - - double current_angle = qAtan2(p.y() - gizmo_anchor_pt_.center().y(), - p.x() - gizmo_anchor_pt_.center().x()); - - double rotation_difference = (current_angle - gizmo_start_angle_) * 57.2958; - - double rotation_cont = gizmo_start_[0].toDouble() + rotation_difference; - - gizmo_dragger_[0].Drag(rotation_cont); - - } -} - -void TransformDistortNode::GizmoRelease(MultiUndoCommand *command) -{ - for (NodeInputDragger& i : gizmo_dragger_) { - i.End(command); - } - gizmo_dragger_.clear(); - - gizmo_start_.clear(); - - gizmo_drag_ = nullptr; -} - void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &globals, const VideoParams &video_params) const { // If not connected to output, this will produce nothing @@ -342,6 +153,168 @@ void TransformDistortNode::Hash(QCryptographicHash &hash, const NodeGlobals &glo Node::Hash(out, GetValueHintForInput(kTextureInput), hash, globals, video_params); } +void TransformDistortNode::GizmoDragStart(const NodeValueRow &row, double x, double y, const rational &time) +{ + DraggableGizmo *gizmo = static_cast(sender()); + + if (gizmo == anchor_gizmo_) { + + gizmo_inverted_transform_ = GenerateMatrix(row, false, true, true, false).toTransform().inverted(); + + } else if (IsAScaleGizmo(gizmo)) { + + // Dragging scale handle + TexturePtr tex = row[kTextureInput].data().value(); + if (!tex) { + return; + } + + gizmo_scale_uniform_ = row[kUniformScaleInput].data().toBool(); + gizmo_anchor_pt_ = (row[kAnchorInput].data().value() + gizmo->GetGlobals().resolution()/2).toPointF(); + + if (gizmo == point_gizmo_[kGizmoScaleTopLeft] || gizmo == point_gizmo_[kGizmoScaleTopRight] + || gizmo == point_gizmo_[kGizmoScaleBottomLeft] || gizmo == point_gizmo_[kGizmoScaleBottomRight]) { + gizmo_scale_axes_ = kGizmoScaleBoth; + } else if (gizmo == point_gizmo_[kGizmoScaleCenterLeft] || gizmo == point_gizmo_[kGizmoScaleCenterRight]) { + gizmo_scale_axes_ = kGizmoScaleXOnly; + } else { + gizmo_scale_axes_ = kGizmoScaleYOnly; + } + + // Store texture size + VideoParams texture_params = tex->params(); + QVector2D texture_sz(texture_params.square_pixel_width(), texture_params.height()); + gizmo_scale_anchor_ = row[kAnchorInput].data().value() + texture_sz/2; + + if (gizmo == point_gizmo_[kGizmoScaleTopRight] + || gizmo == point_gizmo_[kGizmoScaleBottomRight] + || gizmo == point_gizmo_[kGizmoScaleCenterRight]) { + // Right handles, flip X axis + gizmo_scale_anchor_.setX(texture_sz.x() - gizmo_scale_anchor_.x()); + } + + if (gizmo == point_gizmo_[kGizmoScaleBottomLeft] + || gizmo == point_gizmo_[kGizmoScaleBottomRight] + || gizmo == point_gizmo_[kGizmoScaleBottomCenter]) { + // Bottom handles, flip Y axis + gizmo_scale_anchor_.setY(texture_sz.y() - gizmo_scale_anchor_.y()); + } + + // Store current matrix + gizmo_inverted_transform_ = GenerateMatrix(row, false, true, true, true).toTransform().inverted(); + + } else if (gizmo == rotation_gizmo_) { + + gizmo_anchor_pt_ = (row[kAnchorInput].data().value() + gizmo->GetGlobals().resolution()/2).toPointF(); + gizmo_start_angle_ = qAtan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x()); + gizmo_last_angle_ = gizmo_start_angle_; + gizmo_last_alt_angle_ = qAtan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y()); + gizmo_rotate_wrap_ = 0; + gizmo_rotate_last_dir_ = kDirectionNone; + + } +} + +void TransformDistortNode::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) +{ + DraggableGizmo *gizmo = static_cast(sender()); + + if (gizmo == poly_gizmo_) { + + NodeInputDragger &x_drag = gizmo->GetDraggers()[0]; + NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; + + x_drag.Drag(x_drag.GetStartValue().toDouble() + x); + y_drag.Drag(y_drag.GetStartValue().toDouble() + y); + + } else if (gizmo == anchor_gizmo_) { + + NodeInputDragger &x_anchor_drag = gizmo->GetDraggers()[0]; + NodeInputDragger &y_anchor_drag = gizmo->GetDraggers()[1]; + NodeInputDragger &x_pos_drag = gizmo->GetDraggers()[2]; + NodeInputDragger &y_pos_drag = gizmo->GetDraggers()[3]; + + QPointF inverted_movement(gizmo_inverted_transform_.map(QPointF(x, y))); + + x_anchor_drag.Drag(x_anchor_drag.GetStartValue().toDouble() + inverted_movement.x()); + y_anchor_drag.Drag(y_anchor_drag.GetStartValue().toDouble() + inverted_movement.y()); + x_pos_drag.Drag(x_pos_drag.GetStartValue().toDouble() + x); + y_pos_drag.Drag(y_pos_drag.GetStartValue().toDouble() + y); + + } else if (gizmo == rotation_gizmo_) { + + double raw_angle = qAtan2(y - gizmo_anchor_pt_.y(), x - gizmo_anchor_pt_.x()); + double alt_angle = qAtan2(x - gizmo_anchor_pt_.x(), y - gizmo_anchor_pt_.y()); + + double current_angle = raw_angle; + + // Detect rotation wrap around + RotationDirection this_dir = GetDirectionFromAngles(gizmo_last_angle_, raw_angle); + RotationDirection alt_dir = GetDirectionFromAngles(gizmo_last_alt_angle_, alt_angle); + + if (gizmo_rotate_last_dir_ != kDirectionNone && this_dir != gizmo_rotate_last_dir_) { + if (alt_dir == gizmo_rotate_last_alt_dir_) { + if ((raw_angle - gizmo_last_angle_) < 0) { + gizmo_rotate_wrap_++; + } else { + gizmo_rotate_wrap_--; + } + + this_dir = gizmo_rotate_last_dir_; + alt_dir = gizmo_rotate_last_alt_dir_; + } + } + + gizmo_rotate_last_dir_ = this_dir; + gizmo_rotate_last_alt_dir_ = alt_dir; + gizmo_last_angle_ = raw_angle; + gizmo_last_alt_angle_ = alt_angle; + + current_angle += M_PI*2*gizmo_rotate_wrap_; + + // Convert radians to degrees + double rotation_difference = (current_angle - gizmo_start_angle_) * 57.2958; + + NodeInputDragger &d = gizmo->GetDraggers()[0]; + d.Drag(d.GetStartValue().toDouble() + rotation_difference); + + } else if (IsAScaleGizmo(gizmo)) { + + QPointF mouse_relative = gizmo_inverted_transform_.map(QPointF(x, y) - gizmo_anchor_pt_); + + double x_scaled_movement = qAbs(mouse_relative.x() / gizmo_scale_anchor_.x()); + double y_scaled_movement = qAbs(mouse_relative.y() / gizmo_scale_anchor_.y()); + + NodeInputDragger &x_drag = gizmo->GetDraggers()[0]; + NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; + + switch (gizmo_scale_axes_) { + case kGizmoScaleXOnly: + x_drag.Drag(x_scaled_movement); + break; + case kGizmoScaleYOnly: + if (gizmo_scale_uniform_) { + x_drag.Drag(y_scaled_movement); + } else { + y_drag.Drag(y_scaled_movement); + } + break; + case kGizmoScaleBoth: + if (gizmo_scale_uniform_) { + double distance = std::hypot(mouse_relative.x(), mouse_relative.y()); + double texture_diag = std::hypot(gizmo_scale_anchor_.x(), gizmo_scale_anchor_.y()); + + x_drag.Drag(qAbs(distance / texture_diag)); + } else { + x_drag.Drag(x_scaled_movement); + y_drag.Drag(y_scaled_movement); + } + break; + } + + } +} + QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat, const QVector2D &sequence_res, const QVector2D &texture_res, AutoScaleType autoscale_type) { // First, create an identity matrix @@ -387,33 +360,13 @@ QMatrix4x4 TransformDistortNode::AdjustMatrixByResolutions(const QMatrix4x4 &mat return adjusted_matrix; } -QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF &half_res, const QMatrix4x4 &mat) -{ - return mat.map(QPointF(x, y)) + half_res; -} - -QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& generated_matrix, const NodeValueRow& value, const NodeGlobals &globals, const VideoParams& texture_params) const -{ - const QVector2D &sequence_res = globals.resolution(); - QVector2D texture_res(texture_params.square_pixel_width(), texture_params.height()); - AutoScaleType autoscale = static_cast(value[kAutoscaleInput].data().toInt()); - - return AdjustMatrixByResolutions(generated_matrix, - sequence_res, - texture_res, - autoscale); -} - -void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) +void TransformDistortNode::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { TexturePtr tex = row[kTextureInput].data().value(); if (!tex) { return; } - // 0 pen width is always 1px wide despite any transform - p->setPen(QPen(Qt::white, 0)); - // Get the sequence resolution const QVector2D &sequence_res = globals.resolution(); QVector2D sequence_half_res = sequence_res * 0.5; @@ -441,14 +394,9 @@ void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals QPointF(-1, 1), QPointF(-1, -1)}; QTransform rectangle_transform = rectangle_matrix.toTransform(); - gizmo_rect_ = rectangle_transform.map(points); - gizmo_rect_.translate(sequence_half_res_pt); - - // Draw rectangle - p->drawPolyline(gizmo_rect_); - - // Get handle size (relative to screen space rather than buffer space) - const double resize_handle_rad = GetGizmoHandleRadius(p->transform()); + QPolygonF r = rectangle_transform.map(points); + r.translate(sequence_half_res_pt); + poly_gizmo_->SetPolygon(r); // Draw anchor point QMatrix4x4 anchor_matrix; @@ -457,32 +405,17 @@ void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals sequence_res, tex_sz, autoscale); - QPointF anchor_pt = anchor_matrix.toTransform().map(QPointF(0, 0)) + sequence_half_res_pt; - const double anchor_pt_radius = resize_handle_rad * 2; - - gizmo_anchor_pt_ = QRectF(anchor_pt.x() - anchor_pt_radius, - anchor_pt.y() - anchor_pt_radius, - anchor_pt_radius*2, - anchor_pt_radius*2); - - p->drawEllipse(anchor_pt, anchor_pt_radius, anchor_pt_radius); - - p->drawLines({QLineF(anchor_pt.x() - anchor_pt_radius, anchor_pt.y(), - anchor_pt.x() + anchor_pt_radius, anchor_pt.y()), - QLineF(anchor_pt.x(), anchor_pt.y() - anchor_pt_radius, - anchor_pt.x(), anchor_pt.y() + anchor_pt_radius)}); + anchor_gizmo_->SetPoint(anchor_matrix.toTransform().map(QPointF(0, 0)) + sequence_half_res_pt); // Draw scale handles - gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad); - gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(CreateScalePoint( 0, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad); - gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(CreateScalePoint( 1, -1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad); - gizmo_resize_handle_[kGizmoScaleBottomLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad); - gizmo_resize_handle_[kGizmoScaleBottomCenter] = CreateGizmoHandleRect(CreateScalePoint( 0, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad); - gizmo_resize_handle_[kGizmoScaleBottomRight] = CreateGizmoHandleRect(CreateScalePoint( 1, 1, sequence_half_res_pt, rectangle_matrix), resize_handle_rad); - gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(CreateScalePoint(-1, 0, sequence_half_res_pt, rectangle_matrix), resize_handle_rad); - gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(CreateScalePoint( 1, 0, sequence_half_res_pt, rectangle_matrix), resize_handle_rad); - - DrawAndExpandGizmoHandles(p, resize_handle_rad, gizmo_resize_handle_, kGizmoScaleCount); + point_gizmo_[kGizmoScaleTopLeft]->SetPoint(CreateScalePoint(-1, -1, sequence_half_res_pt, rectangle_matrix)); + point_gizmo_[kGizmoScaleTopCenter]->SetPoint(CreateScalePoint( 0, -1, sequence_half_res_pt, rectangle_matrix)); + point_gizmo_[kGizmoScaleTopRight]->SetPoint(CreateScalePoint( 1, -1, sequence_half_res_pt, rectangle_matrix)); + point_gizmo_[kGizmoScaleBottomLeft]->SetPoint(CreateScalePoint(-1, 1, sequence_half_res_pt, rectangle_matrix)); + point_gizmo_[kGizmoScaleBottomCenter]->SetPoint(CreateScalePoint( 0, 1, sequence_half_res_pt, rectangle_matrix)); + point_gizmo_[kGizmoScaleBottomRight]->SetPoint(CreateScalePoint( 1, 1, sequence_half_res_pt, rectangle_matrix)); + point_gizmo_[kGizmoScaleCenterLeft]->SetPoint(CreateScalePoint(-1, 0, sequence_half_res_pt, rectangle_matrix)); + point_gizmo_[kGizmoScaleCenterRight]->SetPoint(CreateScalePoint( 1, 0, sequence_half_res_pt, rectangle_matrix)); // Use offsets to make the appearance of values that start in the top left, even though we // really anchor around the center @@ -490,4 +423,37 @@ void TransformDistortNode::DrawGizmos(const NodeValueRow &row, const NodeGlobals SetInputProperty(kAnchorInput, QStringLiteral("offset"), tex_sz * 0.5); } +QPointF TransformDistortNode::CreateScalePoint(double x, double y, const QPointF &half_res, const QMatrix4x4 &mat) +{ + return mat.map(QPointF(x, y)) + half_res; +} + +QMatrix4x4 TransformDistortNode::GenerateAutoScaledMatrix(const QMatrix4x4& generated_matrix, const NodeValueRow& value, const NodeGlobals &globals, const VideoParams& texture_params) const +{ + const QVector2D &sequence_res = globals.resolution(); + QVector2D texture_res(texture_params.square_pixel_width(), texture_params.height()); + AutoScaleType autoscale = static_cast(value[kAutoscaleInput].data().toInt()); + + return AdjustMatrixByResolutions(generated_matrix, + sequence_res, + texture_res, + autoscale); +} + +bool TransformDistortNode::IsAScaleGizmo(NodeGizmo *g) const +{ + for (int i=0; i last) ? kDirectionPositive : kDirectionNegative; +} + } diff --git a/app/node/distort/transform/transformdistortnode.h b/app/node/distort/transform/transformdistortnode.h index 6bfe5e2f3..a2ca55919 100644 --- a/app/node/distort/transform/transformdistortnode.h +++ b/app/node/distort/transform/transformdistortnode.h @@ -22,6 +22,9 @@ #define TRANSFORMDISTORTNODE_H #include "node/generator/matrix/matrix.h" +#include "node/gizmo/point.h" +#include "node/gizmo/polygon.h" +#include "node/gizmo/screen.h" namespace olive { @@ -69,17 +72,6 @@ public: virtual ShaderCode GetShaderCode(const QString& shader_id) const override; - virtual bool HasGizmos() const override - { - return true; - } - - virtual void DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) override; - - virtual bool GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) override; - virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease(MultiUndoCommand *command) override; - enum AutoScaleType { kAutoScaleNone, kAutoScaleFit, @@ -92,6 +84,8 @@ public: const QVector2D& texture_res, AutoScaleType autoscale_type = kAutoScaleNone); + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + static const QString kTextureInput; static const QString kAutoscaleInput; static const QString kInterpolationInput; @@ -99,18 +93,36 @@ public: protected: virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const override; +protected slots: + virtual void GizmoDragStart(const olive::NodeValueRow &row, double x, double y, const olive::rational &time) override; + + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + private: static QPointF CreateScalePoint(double x, double y, const QPointF& half_res, const QMatrix4x4& mat); QMatrix4x4 GenerateAutoScaledMatrix(const QMatrix4x4 &generated_matrix, const NodeValueRow &db, const NodeGlobals &globals, const VideoParams &texture_params) const; + bool IsAScaleGizmo(NodeGizmo *g) const; + // Gizmo variables - QString gizmo_drag_; - QVector gizmo_start_; - QVector gizmo_dragger_; - QPointF gizmo_drag_pos_; double gizmo_start_angle_; + QTransform gizmo_inverted_transform_; + QPointF gizmo_anchor_pt_; bool gizmo_scale_uniform_; + double gizmo_last_angle_; + double gizmo_last_alt_angle_; + int gizmo_rotate_wrap_; + + enum RotationDirection { + kDirectionNone, + kDirectionPositive, // Clockwise + kDirectionNegative // Counter-clockwise + }; + + static RotationDirection GetDirectionFromAngles(double last, double current); + RotationDirection gizmo_rotate_last_dir_; + RotationDirection gizmo_rotate_last_alt_dir_; enum GizmoScaleType { kGizmoScaleXOnly, @@ -119,13 +131,13 @@ private: }; GizmoScaleType gizmo_scale_axes_; - QMatrix4x4 gizmo_matrix_; QVector2D gizmo_scale_anchor_; // Gizmo on screen object storage - QPolygonF gizmo_rect_; - QRectF gizmo_anchor_pt_; - QRectF gizmo_resize_handle_[kGizmoScaleCount]; + PointGizmo *point_gizmo_[kGizmoScaleCount]; + PointGizmo *anchor_gizmo_; + PolygonGizmo *poly_gizmo_; + ScreenGizmo *rotation_gizmo_; }; diff --git a/app/node/factory.cpp b/app/node/factory.cpp index ed28bc535..990438eac 100644 --- a/app/node/factory.cpp +++ b/app/node/factory.cpp @@ -39,8 +39,9 @@ #include "generator/polygon/polygon.h" #include "generator/shape/shapenode.h" #include "generator/solid/solid.h" -#include "generator/text/text.h" -#include "generator/text/textlegacy.h" +#include "generator/text/textv1.h" +#include "generator/text/textv2.h" +#include "generator/text/textv3.h" #include "filter/blur/blur.h" #include "filter/mosaic/mosaicfilternode.h" #include "filter/stroke/stroke.h" @@ -72,7 +73,8 @@ void NodeFactory::Initialize() library_.append(created_node); } - hidden_.append(kTextGeneratorLegacy); + hidden_.append(kTextGeneratorV1); + hidden_.append(kTextGeneratorV2); hidden_.append(kGroupNode); } @@ -229,10 +231,12 @@ Node *NodeFactory::CreateFromFactoryIndex(const NodeFactory::InternalID &id) return new MergeNode(); case kStrokeFilter: return new StrokeFilterNode(); - case kTextGeneratorLegacy: - return new TextGeneratorLegacy(); - case kTextGenerator: - return new TextGenerator(); + case kTextGeneratorV1: + return new TextGeneratorV1(); + case kTextGeneratorV2: + return new TextGeneratorV2(); + case kTextGeneratorV3: + return new TextGeneratorV3(); case kCrossDissolveTransition: return new CrossDissolveTransition(); case kDipToColorTransition: diff --git a/app/node/factory.h b/app/node/factory.h index 77fe474eb..5d76b6b47 100644 --- a/app/node/factory.h +++ b/app/node/factory.h @@ -48,8 +48,9 @@ public: kSolidGenerator, kMerge, kStrokeFilter, - kTextGeneratorLegacy, - kTextGenerator, + kTextGeneratorV1, + kTextGeneratorV2, + kTextGeneratorV3, kCrossDissolveTransition, kDipToColorTransition, kMosaicFilter, diff --git a/app/node/generator/polygon/polygon.cpp b/app/node/generator/polygon/polygon.cpp index 451430403..7f99bd835 100644 --- a/app/node/generator/polygon/polygon.cpp +++ b/app/node/generator/polygon/polygon.cpp @@ -54,6 +54,9 @@ PolygonGenerator::PolygonGenerator() SetSplitStandardValueOnTrack(kPointsInput, 1, kBottomY, 3); SetSplitStandardValueOnTrack(kPointsInput, 0, -kMiddleX, 4); SetSplitStandardValueOnTrack(kPointsInput, 1, -kMiddleY, 4); + + // Initiate gizmos + poly_gizmo_ = new PathGizmo(this); } Node *PolygonGenerator::copy() const @@ -149,139 +152,93 @@ void PolygonGenerator::GenerateFrame(FramePtr frame, const GenerateJob &job) con } } -bool PolygonGenerator::HasGizmos() const +template +void PolygonGenerator::ValidateGizmoVectorSize(QVector &vec, int new_sz) { - return true; -} + int old_sz = vec.size(); -void PolygonGenerator::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) -{ - const double handle_radius = GetGizmoHandleRadius(p->transform()); - const double bezier_radius = handle_radius/2; - - p->setPen(Qt::white); - p->setBrush(Qt::white); - p->translate(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2); - - QVector points = row[kPointsInput].data().value< QVector >(); - - gizmo_position_handles_.resize(points.size()); - gizmo_bezier_handles_.resize(points.size() * 2); - - p->setPen(QPen(Qt::white, 0)); - p->setBrush(Qt::NoBrush); - - if (!points.isEmpty()) { - QVector lines(points.size() * 2); - - for (int i=0; i(); - - QPointF main = pt.ToPointF(); - QPointF cp1 = main + pt.ControlPoint1ToPointF(); - QPointF cp2 = main + pt.ControlPoint2ToPointF(); - - gizmo_position_handles_[i] = CreateGizmoHandleRect(main, handle_radius); - - gizmo_bezier_handles_[i*2] = CreateGizmoHandleRect(cp1, bezier_radius); - lines[i*2] = QLineF(main, cp1); - - gizmo_bezier_handles_[i*2+1] = CreateGizmoHandleRect(cp2, bezier_radius); - lines[i*2+1] = QLineF(main, cp2); - } - - p->drawLines(lines); - } - - gizmo_polygon_path_ = GeneratePath(points); - p->drawPath(gizmo_polygon_path_); - - DrawAndExpandGizmoHandles(p, handle_radius, gizmo_position_handles_.data(), gizmo_position_handles_.size()); - DrawAndExpandGizmoHandles(p, handle_radius, gizmo_bezier_handles_.data(), gizmo_bezier_handles_.size()); -} - -bool PolygonGenerator::GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) -{ - QPointF adjusted = p - (globals.resolution_by_par() / 2).toPointF(); - - // First, look for main points - - for (int i=0; i new_sz) { + for (int i=new_sz; i) { + vec[i] = AddDraggableGizmo(); + } else { + vec[i] = new T(this); } } } } - - gizmo_drag_start_ = p; - - return !gizmo_x_active_.isEmpty() || !gizmo_y_active_.isEmpty(); } -void PolygonGenerator::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) +void PolygonGenerator::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { - if (gizmo_x_draggers_.isEmpty() && gizmo_y_draggers_.isEmpty()) { - gizmo_x_draggers_.resize(gizmo_x_active_.size()); - gizmo_y_draggers_.resize(gizmo_y_active_.size()); - for (int i=0; i points = row[kPointsInput].data().value< QVector >(); + + int current_pos_sz = gizmo_position_handles_.size(); + + ValidateGizmoVectorSize(gizmo_position_handles_, points.size()); + ValidateGizmoVectorSize(gizmo_bezier_handles_, points.size() * 2); + ValidateGizmoVectorSize(gizmo_bezier_lines_, points.size() * 2); + + for (int i=current_pos_sz; iAddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 0)); + gizmo_position_handles_.at(i)->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 1)); + + PointGizmo *bez_gizmo1 = gizmo_bezier_handles_.at(i*2+0); + bez_gizmo1->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 2)); + bez_gizmo1->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 3)); + bez_gizmo1->SetShape(PointGizmo::kCircle); + bez_gizmo1->SetSmaller(true); + + PointGizmo *bez_gizmo2 = gizmo_bezier_handles_.at(i*2+1); + bez_gizmo2->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 4)); + bez_gizmo2->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 5)); + bez_gizmo2->SetShape(PointGizmo::kCircle); + bez_gizmo2->SetSmaller(true); + } + + if (!points.isEmpty()) { + for (int i=0; i(); + + QPointF main = pt.ToPointF() + half_res; + QPointF cp1 = main + pt.ControlPoint1ToPointF(); + QPointF cp2 = main + pt.ControlPoint2ToPointF(); + + gizmo_position_handles_[i]->SetPoint(main); + + gizmo_bezier_handles_[i*2]->SetPoint(cp1); + gizmo_bezier_lines_[i*2]->SetLine(QLineF(main, cp1)); + gizmo_bezier_handles_[i*2+1]->SetPoint(cp2); + gizmo_bezier_lines_[i*2+1]->SetLine(QLineF(main, cp2)); } } - QPointF diff = p - gizmo_drag_start_; - - for (NodeInputDragger &dragger : gizmo_x_draggers_) { - dragger.Drag(dragger.GetStartValue().toDouble() + diff.x()); - } - - for (NodeInputDragger &dragger : gizmo_y_draggers_) { - dragger.Drag(dragger.GetStartValue().toDouble() + diff.y()); - } + poly_gizmo_->SetPath(GeneratePath(points).translated(half_res)); } -void PolygonGenerator::GizmoRelease(MultiUndoCommand *command) +void PolygonGenerator::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) { - for (NodeInputDragger &dragger : gizmo_x_draggers_) { - dragger.End(command); - } - gizmo_x_draggers_.clear(); + DraggableGizmo *gizmo = static_cast(sender()); - for (NodeInputDragger &dragger : gizmo_y_draggers_) { - dragger.End(command); + if (gizmo == poly_gizmo_) { + // FIXME: Drag all points + } else { + NodeInputDragger &x_drag = gizmo->GetDraggers()[0]; + NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; + x_drag.Drag(x_drag.GetStartValue().toDouble() + x); + y_drag.Drag(y_drag.GetStartValue().toDouble() + y); } - gizmo_y_draggers_.clear(); - - gizmo_x_active_.clear(); - gizmo_y_active_.clear(); } void PolygonGenerator::AddPointToPath(QPainterPath *path, const Bezier &before, const Bezier &after) diff --git a/app/node/generator/polygon/polygon.h b/app/node/generator/polygon/polygon.h index e7da30009..ff85fc8be 100644 --- a/app/node/generator/polygon/polygon.h +++ b/app/node/generator/polygon/polygon.h @@ -24,6 +24,9 @@ #include #include "common/bezier.h" +#include "node/gizmo/line.h" +#include "node/gizmo/path.h" +#include "node/gizmo/point.h" #include "node/node.h" #include "node/inputdragger.h" @@ -50,30 +53,26 @@ public: virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override; - virtual bool HasGizmos() const override; - virtual void DrawGizmos(const NodeValueRow& row, const NodeGlobals &globals, QPainter *p) override; - - virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override; - virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease(MultiUndoCommand *command) override; + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; static const QString kPointsInput; static const QString kColorInput; +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; + private: static void AddPointToPath(QPainterPath *path, const Bezier &before, const Bezier &after); static QPainterPath GeneratePath(const QVector &points); - QPainterPath gizmo_polygon_path_; - QVector gizmo_position_handles_; - QVector gizmo_bezier_handles_; + template + void ValidateGizmoVectorSize(QVector &vec, int new_sz); - QVector gizmo_x_active_; - QVector gizmo_y_active_; - QVector gizmo_x_draggers_; - QVector gizmo_y_draggers_; - QPointF gizmo_drag_start_; + PathGizmo *poly_gizmo_; + QVector gizmo_position_handles_; + QVector gizmo_bezier_handles_; + QVector gizmo_bezier_lines_; }; diff --git a/app/node/generator/shape/shapenodebase.cpp b/app/node/generator/shape/shapenodebase.cpp index 033ecca78..15a262f29 100644 --- a/app/node/generator/shape/shapenodebase.cpp +++ b/app/node/generator/shape/shapenodebase.cpp @@ -23,7 +23,7 @@ #include #include -#include "common/lerp.h" +#include "common/util.h" #include "core.h" namespace olive { @@ -34,12 +34,30 @@ QString ShapeNodeBase::kPositionInput = QStringLiteral("pos_in"); QString ShapeNodeBase::kSizeInput = QStringLiteral("size_in"); QString ShapeNodeBase::kColorInput = QStringLiteral("color_in"); -ShapeNodeBase::ShapeNodeBase() +ShapeNodeBase::ShapeNodeBase(bool create_color_input) { AddInput(kPositionInput, NodeValue::kVec2, QVector2D(0, 0)); AddInput(kSizeInput, NodeValue::kVec2, QVector2D(100, 100)); SetInputProperty(kSizeInput, QStringLiteral("min"), QVector2D(0, 0)); - AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0, 0.0, 0.0, 1.0))); + + if (create_color_input) { + AddInput(kColorInput, NodeValue::kColor, QVariant::fromValue(Color(1.0, 0.0, 0.0, 1.0))); + } + + // Initiate gizmos + QVector pos_n_sz = { + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), + NodeKeyframeTrackReference(NodeInput(this, kSizeInput), 0), + NodeKeyframeTrackReference(NodeInput(this, kSizeInput), 1) + }; + poly_gizmo_ = AddDraggableGizmo({ + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), + NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), + }); + for (int i=0; i(pos_n_sz, PointGizmo::kAbsolute); + } } void ShapeNodeBase::Retranslate() @@ -48,20 +66,19 @@ void ShapeNodeBase::Retranslate() SetInputName(kPositionInput, tr("Position")); SetInputName(kSizeInput, tr("Size")); - SetInputName(kColorInput, tr("Color")); + + if (HasInputWithID(kColorInput)) { + SetInputName(kColorInput, tr("Color")); + } } -void ShapeNodeBase::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) +void ShapeNodeBase::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) { // Use offsets to make the appearance of values that start in the top left, even though we // really anchor around the center QVector2D center_pt = globals.resolution() * 0.5; SetInputProperty(kPositionInput, QStringLiteral("offset"), center_pt); - const double handle_radius = GetGizmoHandleRadius(p->transform()); - - p->setPen(QPen(Qt::white, 0)); - QVector2D pos = row[kPositionInput].data().value(); QVector2D sz = row[kSizeInput].data().value(); QVector2D half_sz = sz * 0.5; @@ -70,151 +87,73 @@ void ShapeNodeBase::DrawGizmos(const NodeValueRow &row, const NodeGlobals &globa double top_pt = pos.y() + center_pt.y() - half_sz.y(); double right_pt = left_pt + sz.x(); double bottom_pt = top_pt + sz.y(); - double center_x_pt = lerp(left_pt, right_pt, 0.5); - double center_y_pt = lerp(top_pt, bottom_pt, 0.5); + double center_x_pt = mid(left_pt, right_pt); + double center_y_pt = mid(top_pt, bottom_pt); - gizmo_whole_rect_ = QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt); - p->drawRect(gizmo_whole_rect_); + point_gizmo_[kGizmoScaleTopLeft]->SetPoint(QPointF(left_pt, top_pt)); + point_gizmo_[kGizmoScaleTopCenter]->SetPoint(QPointF(center_x_pt, top_pt)); + point_gizmo_[kGizmoScaleTopRight]->SetPoint(QPointF(right_pt, top_pt)); + point_gizmo_[kGizmoScaleBottomLeft]->SetPoint(QPointF(left_pt, bottom_pt)); + point_gizmo_[kGizmoScaleBottomCenter]->SetPoint(QPointF(center_x_pt, bottom_pt)); + point_gizmo_[kGizmoScaleBottomRight]->SetPoint(QPointF(right_pt, bottom_pt)); + point_gizmo_[kGizmoScaleCenterLeft]->SetPoint(QPointF(left_pt, center_y_pt)); + point_gizmo_[kGizmoScaleCenterRight]->SetPoint(QPointF(right_pt, center_y_pt)); - gizmo_resize_handle_[kGizmoScaleTopLeft] = CreateGizmoHandleRect(QPointF(left_pt, top_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleTopCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, top_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleTopRight] = CreateGizmoHandleRect(QPointF(right_pt, top_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleBottomLeft] = CreateGizmoHandleRect(QPointF(left_pt, bottom_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleBottomCenter] = CreateGizmoHandleRect(QPointF(center_x_pt, bottom_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleBottomRight] = CreateGizmoHandleRect(QPointF(right_pt, bottom_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleCenterLeft] = CreateGizmoHandleRect(QPointF(left_pt, center_y_pt), handle_radius); - gizmo_resize_handle_[kGizmoScaleCenterRight] = CreateGizmoHandleRect(QPointF(right_pt, center_y_pt), handle_radius); - - DrawAndExpandGizmoHandles(p, handle_radius, gizmo_resize_handle_, kGizmoScaleCount); + poly_gizmo_->SetPolygon(QRectF(left_pt, top_pt, right_pt - left_pt, bottom_pt - top_pt)); } -bool ShapeNodeBase::GizmoPress(const NodeValueRow &row, const NodeGlobals &globals, const QPointF &p) +void ShapeNodeBase::GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) { - gizmo_drag_ = -1; + DraggableGizmo *gizmo = static_cast(sender()); - // See if any of our resize handles have the pointer - for (int i=0; iGetDraggers()[0]; + NodeInputDragger &y_drag = gizmo->GetDraggers()[1]; - // See if the rect has the pointer - if (gizmo_drag_ == -1 && gizmo_whole_rect_.contains(p)) { - gizmo_drag_ = kGizmoWholeRect; - } - - if (gizmo_drag_ >= 0) { - gizmo_pos_start_ = row[kPositionInput].data().value(); - gizmo_sz_start_ = row[kSizeInput].data().value(); - gizmo_drag_start_ = p; - gizmo_half_res_ = globals.resolution()/2; - - // Get aspect ratio (avoiding potential zero) - if (qFuzzyIsNull(gizmo_sz_start_.y())) { - gizmo_aspect_ratio_ = 0; - } else { - gizmo_aspect_ratio_ = gizmo_sz_start_.x() / gizmo_sz_start_.y(); - } - return true; - } - - return false; -} - -enum { - kGDPosX, - kGDPosY, - kGDSzX, - kGDSzY, - kGDCount -}; - -QVector2D ShapeNodeBase::GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, int drag, QVector2D *pt = nullptr) -{ - QVector2D anchor = pos; - QVector2D half_sz = size/2; - - if (drag == kGizmoScaleTopLeft || drag == kGizmoScaleCenterLeft || drag == kGizmoScaleBottomLeft) { - anchor.setX(anchor.x() + half_sz.x()); - if (pt && pt->x() > anchor.x()) { - pt->setX(anchor.x()); - } - } - - if (drag == kGizmoScaleTopRight || drag == kGizmoScaleCenterRight || drag == kGizmoScaleBottomRight) { - anchor.setX(anchor.x() - half_sz.x()); - if (pt && pt->x() < anchor.x()) { - pt->setX(anchor.x()); - } - } - - if (drag == kGizmoScaleTopLeft || drag == kGizmoScaleTopCenter || drag == kGizmoScaleTopRight) { - anchor.setY(anchor.y() + half_sz.y()); - if (pt && pt->y() > anchor.y()) { - pt->setY(anchor.y()); - } - } - - if (drag == kGizmoScaleBottomLeft || drag == kGizmoScaleBottomCenter || drag == kGizmoScaleBottomRight) { - anchor.setY(anchor.y() - half_sz.y()); - if (pt && pt->y() < anchor.y()) { - pt->setY(anchor.y()); - } - } - - return anchor; -} - -void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) -{ - if (gizmo_dragger_.isEmpty()) { - gizmo_dragger_.resize(kGDCount); - gizmo_dragger_[kGDPosX].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0), time); - gizmo_dragger_[kGDPosY].Start(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1), time); - gizmo_dragger_[kGDSzX].Start(NodeKeyframeTrackReference(NodeInput(this, kSizeInput), 0), time); - gizmo_dragger_[kGDSzY].Start(NodeKeyframeTrackReference(NodeInput(this, kSizeInput), 1), time); - } - - bool from_center = modifiers & Qt::AltModifier; - bool keep_ratio = modifiers & Qt::ShiftModifier; - - QVector2D adjusted_pt(p.x(), p.y()); - QVector2D diff(adjusted_pt.x() - gizmo_drag_start_.x(), adjusted_pt.y() - gizmo_drag_start_.y()); - - if (gizmo_drag_ == kGizmoWholeRect) { - // Simply move position by difference - gizmo_dragger_[kGDPosX].Drag(gizmo_pos_start_.x() + diff.x()); - gizmo_dragger_[kGDPosY].Drag(gizmo_pos_start_.y() + diff.y()); + if (gizmo == poly_gizmo_) { + x_drag.Drag(x_drag.GetStartValue().toDouble() + x); + y_drag.Drag(y_drag.GetStartValue().toDouble() + y); } else { + bool from_center = modifiers & Qt::AltModifier; + bool keep_ratio = modifiers & Qt::ShiftModifier; + NodeInputDragger &w_drag = gizmo->GetDraggers()[2]; + NodeInputDragger &h_drag = gizmo->GetDraggers()[3]; + + QVector2D gizmo_sz_start(w_drag.GetStartValue().toDouble(), h_drag.GetStartValue().toDouble()); + QVector2D gizmo_pos_start(x_drag.GetStartValue().toDouble(), y_drag.GetStartValue().toDouble()); + QVector2D gizmo_half_res = gizmo->GetGlobals().resolution()/2; + QVector2D adjusted_pt(x, y); QVector2D new_size; QVector2D new_pos; QVector2D anchor; static const int kXYCount = 2; bool negative[kXYCount] = {false}; + double original_ratio; + if (keep_ratio) { + original_ratio = w_drag.GetStartValue().toDouble() / h_drag.GetStartValue().toDouble(); + } + // Calculate new size if (from_center) { // Calculate new size by using distance from center and doubling it - new_size = (adjusted_pt - gizmo_half_res_ - gizmo_pos_start_) * 2; + new_size = (adjusted_pt - gizmo_half_res - gizmo_pos_start) * 2; - if (gizmo_drag_ == kGizmoScaleTopCenter || gizmo_drag_ == kGizmoScaleTopLeft || gizmo_drag_ == kGizmoScaleTopRight) { + if (IsGizmoTop(gizmo)) { new_size.setY(-new_size.y()); } - if (gizmo_drag_ == kGizmoScaleTopLeft || gizmo_drag_ == kGizmoScaleCenterLeft || gizmo_drag_ == kGizmoScaleBottomLeft) { + if (IsGizmoLeft(gizmo)) { new_size.setX(-new_size.x()); } } else { // Calculate new size by using distance from "anchor" - i.e. the opposite point of the shape // from the gizmo being dragged - adjusted_pt -= gizmo_half_res_; + adjusted_pt -= gizmo_half_res; - anchor = GenerateGizmoAnchor(gizmo_pos_start_, gizmo_sz_start_, gizmo_drag_, &adjusted_pt) + gizmo_half_res_; + anchor = GenerateGizmoAnchor(gizmo_pos_start, gizmo_sz_start, gizmo, &adjusted_pt) + gizmo_half_res; - adjusted_pt += gizmo_half_res_; + adjusted_pt += gizmo_half_res; // Calculate size and position new_size = adjusted_pt - anchor; @@ -229,32 +168,31 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt:: } // Restrict sizes by constraints - if (gizmo_drag_ == kGizmoScaleTopCenter || gizmo_drag_ == kGizmoScaleBottomCenter) { + if (IsGizmoVerticalCenter(gizmo)) { if (keep_ratio) { // Calculate width from new height - new_size.setX(new_size.y() * gizmo_aspect_ratio_); + new_size.setX(new_size.y() * original_ratio); } else { // Constrain to original width - new_size.setX(gizmo_sz_start_.x()); + new_size.setX(gizmo_sz_start.x()); } } - if (gizmo_drag_ == kGizmoScaleCenterLeft || gizmo_drag_ == kGizmoScaleCenterRight) { + if (IsGizmoHorizontalCenter(gizmo)) { if (keep_ratio) { // Calculate height from new width - new_size.setY(new_size.x() / gizmo_aspect_ratio_); + new_size.setY(new_size.x() / original_ratio); } else { // Constrain to original height - new_size.setY(gizmo_sz_start_.y()); + new_size.setY(gizmo_sz_start.y()); } } - if (gizmo_drag_ == kGizmoScaleTopLeft || gizmo_drag_ == kGizmoScaleTopRight - || gizmo_drag_ == kGizmoScaleBottomRight || gizmo_drag_ == kGizmoScaleBottomLeft) { + if (IsGizmoCorner(gizmo)) { if (keep_ratio) { float hypot = std::hypot(new_size.x(), new_size.y()); - float original_angle = std::atan2(gizmo_sz_start_.x(), gizmo_sz_start_.y()); + float original_angle = std::atan2(gizmo_sz_start.x(), gizmo_sz_start.y()); // Calculate new size based on original angle and hypotenuse new_size.setX(std::sin(original_angle) * hypot); @@ -264,7 +202,7 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt:: // Calculate position if (from_center) { - new_pos = gizmo_pos_start_; + new_pos = gizmo_pos_start; } else { QVector2D using_size = new_size; @@ -276,31 +214,94 @@ void ShapeNodeBase::GizmoMove(const QPointF &p, const rational &time, const Qt:: } // I'm pretty sure there's an algorithmic way of doing this, but I'm tired and this works - if (gizmo_drag_ == kGizmoScaleCenterLeft || gizmo_drag_ == kGizmoScaleCenterRight) { + if (IsGizmoHorizontalCenter(gizmo)) { using_size.setY(0); } - if (gizmo_drag_ == kGizmoScaleTopCenter || gizmo_drag_ == kGizmoScaleBottomCenter) { + if (IsGizmoVerticalCenter(gizmo)) { using_size.setX(0); } - new_pos = GenerateGizmoAnchor(gizmo_pos_start_, gizmo_sz_start_, gizmo_drag_) + using_size / 2; + new_pos = GenerateGizmoAnchor(gizmo_pos_start, gizmo_sz_start, gizmo) + using_size / 2; } - gizmo_dragger_[kGDPosX].Drag(new_pos.x()); - gizmo_dragger_[kGDPosY].Drag(new_pos.y()); - gizmo_dragger_[kGDSzX].Drag(new_size.x()); - gizmo_dragger_[kGDSzY].Drag(new_size.y()); - + x_drag.Drag(new_pos.x()); + y_drag.Drag(new_pos.y()); + w_drag.Drag(new_size.x()); + h_drag.Drag(new_size.y()); } } -void ShapeNodeBase::GizmoRelease(MultiUndoCommand *command) +QVector2D ShapeNodeBase::GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, NodeGizmo *gizmo, QVector2D *pt) const { - for (NodeInputDragger& i : gizmo_dragger_) { - i.End(command); + QVector2D anchor = pos; + QVector2D half_sz = size/2; + + if (IsGizmoLeft(gizmo)) { + anchor.setX(anchor.x() + half_sz.x()); + if (pt && pt->x() > anchor.x()) { + pt->setX(anchor.x()); + } } - gizmo_dragger_.clear(); + + if (IsGizmoRight(gizmo)) { + anchor.setX(anchor.x() - half_sz.x()); + if (pt && pt->x() < anchor.x()) { + pt->setX(anchor.x()); + } + } + + if (IsGizmoTop(gizmo)) { + anchor.setY(anchor.y() + half_sz.y()); + if (pt && pt->y() > anchor.y()) { + pt->setY(anchor.y()); + } + } + + if (IsGizmoBottom(gizmo)) { + anchor.setY(anchor.y() - half_sz.y()); + if (pt && pt->y() < anchor.y()) { + pt->setY(anchor.y()); + } + } + + return anchor; +} + +bool ShapeNodeBase::IsGizmoTop(NodeGizmo *g) const +{ + return g == point_gizmo_[kGizmoScaleTopCenter] || g == point_gizmo_[kGizmoScaleTopLeft] || g == point_gizmo_[kGizmoScaleTopRight]; +} + +bool ShapeNodeBase::IsGizmoBottom(NodeGizmo *g) const +{ + return g == point_gizmo_[kGizmoScaleBottomCenter] || g == point_gizmo_[kGizmoScaleBottomLeft] || g == point_gizmo_[kGizmoScaleBottomRight]; +} + +bool ShapeNodeBase::IsGizmoLeft(NodeGizmo *g) const +{ + return g == point_gizmo_[kGizmoScaleTopLeft] || g == point_gizmo_[kGizmoScaleCenterLeft] || g == point_gizmo_[kGizmoScaleBottomLeft]; +} + +bool ShapeNodeBase::IsGizmoRight(NodeGizmo *g) const +{ + return g == point_gizmo_[kGizmoScaleTopRight] || g == point_gizmo_[kGizmoScaleCenterRight] || g == point_gizmo_[kGizmoScaleBottomRight]; +} + +bool ShapeNodeBase::IsGizmoHorizontalCenter(NodeGizmo *g) const +{ + return g == point_gizmo_[kGizmoScaleCenterLeft] || g == point_gizmo_[kGizmoScaleCenterRight]; +} + +bool ShapeNodeBase::IsGizmoVerticalCenter(NodeGizmo *g) const +{ + return g == point_gizmo_[kGizmoScaleTopCenter] || g == point_gizmo_[kGizmoScaleBottomCenter]; +} + +bool ShapeNodeBase::IsGizmoCorner(NodeGizmo *g) const +{ + return g == point_gizmo_[kGizmoScaleTopLeft] || g == point_gizmo_[kGizmoScaleTopRight] + || g == point_gizmo_[kGizmoScaleBottomRight] || g == point_gizmo_[kGizmoScaleBottomLeft]; } } diff --git a/app/node/generator/shape/shapenodebase.h b/app/node/generator/shape/shapenodebase.h index 7337d5b6c..d7529eff0 100644 --- a/app/node/generator/shape/shapenodebase.h +++ b/app/node/generator/shape/shapenodebase.h @@ -21,6 +21,8 @@ #ifndef SHAPENODEBASE_H #define SHAPENODEBASE_H +#include "node/gizmo/point.h" +#include "node/gizmo/polygon.h" #include "node/inputdragger.h" #include "node/node.h" @@ -30,42 +32,42 @@ class ShapeNodeBase : public Node { Q_OBJECT public: - ShapeNodeBase(); + ShapeNodeBase(bool create_color_input = true); NODE_DEFAULT_DESTRUCTOR(ShapeNodeBase) virtual void Retranslate() override; + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + static QString kPositionInput; static QString kSizeInput; static QString kColorInput; - virtual bool HasGizmos() const override +protected: + PolygonGizmo *poly_gizmo() const { - return true; + return poly_gizmo_; } - virtual void DrawGizmos(const NodeValueRow &row, const NodeGlobals &globals, QPainter *p) override; - - virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF &p) override; - virtual void GizmoMove(const QPointF &p, const rational &time, const Qt::KeyboardModifiers &modifiers) override; - virtual void GizmoRelease(MultiUndoCommand *command) override; +protected slots: + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers) override; private: - static QVector2D GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, int drag, QVector2D *pt); + QVector2D GenerateGizmoAnchor(const QVector2D &pos, const QVector2D &size, NodeGizmo *gizmo, QVector2D *pt = nullptr) const; + + bool IsGizmoTop(NodeGizmo *g) const; + bool IsGizmoBottom(NodeGizmo *g) const; + bool IsGizmoLeft(NodeGizmo *g) const; + bool IsGizmoRight(NodeGizmo *g) const; + bool IsGizmoHorizontalCenter(NodeGizmo *g) const; + bool IsGizmoVerticalCenter(NodeGizmo *g) const; + bool IsGizmoCorner(NodeGizmo *g) const; // Gizmo variables static const int kGizmoWholeRect = kGizmoScaleCount; - QRectF gizmo_resize_handle_[kGizmoScaleCount]; - QRectF gizmo_whole_rect_; - - int gizmo_drag_; - QVector gizmo_dragger_; - QVector2D gizmo_pos_start_; - QVector2D gizmo_sz_start_; - QPointF gizmo_drag_start_; - float gizmo_aspect_ratio_; - QVector2D gizmo_half_res_; + PointGizmo *point_gizmo_[kGizmoScaleCount]; + PolygonGizmo *poly_gizmo_; }; diff --git a/app/node/generator/text/CMakeLists.txt b/app/node/generator/text/CMakeLists.txt index 889177361..81c69f4e7 100644 --- a/app/node/generator/text/CMakeLists.txt +++ b/app/node/generator/text/CMakeLists.txt @@ -16,9 +16,11 @@ set(OLIVE_SOURCES ${OLIVE_SOURCES} - node/generator/text/text.cpp - node/generator/text/text.h - node/generator/text/textlegacy.cpp - node/generator/text/textlegacy.h + node/generator/text/textv1.cpp + node/generator/text/textv1.h + node/generator/text/textv2.cpp + node/generator/text/textv2.h + node/generator/text/textv3.cpp + node/generator/text/textv3.h PARENT_SCOPE ) diff --git a/app/node/generator/text/textlegacy.cpp b/app/node/generator/text/textv1.cpp similarity index 82% rename from app/node/generator/text/textlegacy.cpp rename to app/node/generator/text/textv1.cpp index edb871a02..d6c4c8588 100644 --- a/app/node/generator/text/textlegacy.cpp +++ b/app/node/generator/text/textv1.cpp @@ -18,7 +18,7 @@ ***/ -#include "textlegacy.h" +#include "textv1.h" #include #include @@ -31,14 +31,14 @@ enum TextVerticalAlign { kVerticalAlignBottom, }; -const QString TextGeneratorLegacy::kTextInput = QStringLiteral("text_in"); -const QString TextGeneratorLegacy::kHtmlInput = QStringLiteral("html_in"); -const QString TextGeneratorLegacy::kColorInput = QStringLiteral("color_in"); -const QString TextGeneratorLegacy::kVAlignInput = QStringLiteral("valign_in"); -const QString TextGeneratorLegacy::kFontInput = QStringLiteral("font_in"); -const QString TextGeneratorLegacy::kFontSizeInput = QStringLiteral("font_size_in"); +const QString TextGeneratorV1::kTextInput = QStringLiteral("text_in"); +const QString TextGeneratorV1::kHtmlInput = QStringLiteral("html_in"); +const QString TextGeneratorV1::kColorInput = QStringLiteral("color_in"); +const QString TextGeneratorV1::kVAlignInput = QStringLiteral("valign_in"); +const QString TextGeneratorV1::kFontInput = QStringLiteral("font_in"); +const QString TextGeneratorV1::kFontSizeInput = QStringLiteral("font_size_in"); -TextGeneratorLegacy::TextGeneratorLegacy() +TextGeneratorV1::TextGeneratorV1() { AddInput(kTextInput, NodeValue::kText, tr("Sample Text")); @@ -53,27 +53,27 @@ TextGeneratorLegacy::TextGeneratorLegacy() AddInput(kFontSizeInput, NodeValue::kFloat, 72.0f); } -QString TextGeneratorLegacy::Name() const +QString TextGeneratorV1::Name() const { return tr("Text (Legacy)"); } -QString TextGeneratorLegacy::id() const +QString TextGeneratorV1::id() const { return QStringLiteral("org.olivevideoeditor.Olive.textgenerator"); } -QVector TextGeneratorLegacy::Category() const +QVector TextGeneratorV1::Category() const { return {kCategoryGenerator}; } -QString TextGeneratorLegacy::Description() const +QString TextGeneratorV1::Description() const { return tr("Generate rich text."); } -void TextGeneratorLegacy::Retranslate() +void TextGeneratorV1::Retranslate() { SetInputName(kTextInput, tr("Text")); SetInputName(kHtmlInput, tr("Enable HTML")); @@ -84,7 +84,7 @@ void TextGeneratorLegacy::Retranslate() SetComboBoxStrings(kVAlignInput, {tr("Top"), tr("Center"), tr("Bottom")}); } -void TextGeneratorLegacy::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +void TextGeneratorV1::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { GenerateJob job; job.InsertValue(value); @@ -95,7 +95,7 @@ void TextGeneratorLegacy::Value(const NodeValueRow &value, const NodeGlobals &gl } } -void TextGeneratorLegacy::GenerateFrame(FramePtr frame, const GenerateJob& job) const +void TextGeneratorV1::GenerateFrame(FramePtr frame, const GenerateJob& job) const { // This could probably be more optimized, but for now we use Qt to draw to a QImage. // QImages only support integer pixels and we use float pixels, so what we do here is draw onto diff --git a/app/node/generator/text/textlegacy.h b/app/node/generator/text/textv1.h similarity index 85% rename from app/node/generator/text/textlegacy.h rename to app/node/generator/text/textv1.h index 296d838e3..868e22449 100644 --- a/app/node/generator/text/textlegacy.h +++ b/app/node/generator/text/textv1.h @@ -18,21 +18,21 @@ ***/ -#ifndef TEXTGENERATORLEGACY_H -#define TEXTGENERATORLEGACY_H +#ifndef TEXTGENERATORV1_H +#define TEXTGENERATORV1_H #include "node/node.h" namespace olive { -class TextGeneratorLegacy : public Node +class TextGeneratorV1 : public Node { Q_OBJECT public: - TextGeneratorLegacy(); + TextGeneratorV1(); - NODE_DEFAULT_DESTRUCTOR(TextGeneratorLegacy) - NODE_COPY_FUNCTION(TextGeneratorLegacy) + NODE_DEFAULT_DESTRUCTOR(TextGeneratorV1) + NODE_COPY_FUNCTION(TextGeneratorV1) virtual QString Name() const override; virtual QString id() const override; @@ -56,4 +56,4 @@ public: } -#endif // TEXTGENERATORLEGACY_H +#endif // TEXTGENERATORV1_H diff --git a/app/node/generator/text/text.cpp b/app/node/generator/text/textv2.cpp similarity index 86% rename from app/node/generator/text/text.cpp rename to app/node/generator/text/textv2.cpp index b2eb1e0a2..b976a58ff 100644 --- a/app/node/generator/text/text.cpp +++ b/app/node/generator/text/textv2.cpp @@ -18,7 +18,7 @@ ***/ -#include "text.h" +#include "textv2.h" #include #include @@ -37,13 +37,13 @@ enum TextVerticalAlign { kVerticalAlignBottom, }; -const QString TextGenerator::kTextInput = QStringLiteral("text_in"); -const QString TextGenerator::kHtmlInput = QStringLiteral("html_in"); -const QString TextGenerator::kVAlignInput = QStringLiteral("valign_in"); -const QString TextGenerator::kFontInput = QStringLiteral("font_in"); -const QString TextGenerator::kFontSizeInput = QStringLiteral("font_size_in"); +const QString TextGeneratorV2::kTextInput = QStringLiteral("text_in"); +const QString TextGeneratorV2::kHtmlInput = QStringLiteral("html_in"); +const QString TextGeneratorV2::kVAlignInput = QStringLiteral("valign_in"); +const QString TextGeneratorV2::kFontInput = QStringLiteral("font_in"); +const QString TextGeneratorV2::kFontSizeInput = QStringLiteral("font_size_in"); -TextGenerator::TextGenerator() +TextGeneratorV2::TextGeneratorV2() { AddInput(kTextInput, NodeValue::kText, tr("Sample Text")); @@ -59,27 +59,27 @@ TextGenerator::TextGenerator() SetStandardValue(kSizeInput, QVector2D(400, 300)); } -QString TextGenerator::Name() const +QString TextGeneratorV2::Name() const { - return tr("Text"); + return tr("Text (Legacy)"); } -QString TextGenerator::id() const +QString TextGeneratorV2::id() const { return QStringLiteral("org.olivevideoeditor.Olive.text2"); } -QVector TextGenerator::Category() const +QVector TextGeneratorV2::Category() const { return {kCategoryGenerator}; } -QString TextGenerator::Description() const +QString TextGeneratorV2::Description() const { return tr("Generate rich text."); } -void TextGenerator::Retranslate() +void TextGeneratorV2::Retranslate() { super::Retranslate(); @@ -91,7 +91,7 @@ void TextGenerator::Retranslate() SetComboBoxStrings(kVAlignInput, {tr("Top"), tr("Center"), tr("Bottom")}); } -void TextGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +void TextGeneratorV2::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const { GenerateJob job; job.InsertValue(value); @@ -103,7 +103,7 @@ void TextGenerator::Value(const NodeValueRow &value, const NodeGlobals &globals, } } -void TextGenerator::GenerateFrame(FramePtr frame, const GenerateJob& job) const +void TextGeneratorV2::GenerateFrame(FramePtr frame, const GenerateJob& job) const { // This could probably be more optimized, but for now we use Qt to draw to a QImage. // QImages only support integer pixels and we use float pixels, so what we do here is draw onto diff --git a/app/node/generator/text/text.h b/app/node/generator/text/textv2.h similarity index 86% rename from app/node/generator/text/text.h rename to app/node/generator/text/textv2.h index d2be75cde..d9fb17386 100644 --- a/app/node/generator/text/text.h +++ b/app/node/generator/text/textv2.h @@ -18,21 +18,21 @@ ***/ -#ifndef TEXTGENERATOR_H -#define TEXTGENERATOR_H +#ifndef TEXTGENERATORV2_H +#define TEXTGENERATORV2_H #include "node/generator/shape/shapenodebase.h" namespace olive { -class TextGenerator : public ShapeNodeBase +class TextGeneratorV2 : public ShapeNodeBase { Q_OBJECT public: - TextGenerator(); + TextGeneratorV2(); - NODE_DEFAULT_DESTRUCTOR(TextGenerator) - NODE_COPY_FUNCTION(TextGenerator) + NODE_DEFAULT_DESTRUCTOR(TextGeneratorV2) + NODE_COPY_FUNCTION(TextGeneratorV2) virtual QString Name() const override; virtual QString id() const override; @@ -55,4 +55,4 @@ public: } -#endif // TEXTGENERATOR_H +#endif // TEXTGENERATORV2_H diff --git a/app/node/generator/text/textv3.cpp b/app/node/generator/text/textv3.cpp new file mode 100644 index 000000000..d5e791855 --- /dev/null +++ b/app/node/generator/text/textv3.cpp @@ -0,0 +1,140 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "textv3.h" + +#include +#include +#include + +#include "common/functiontimer.h" +#include "common/html.h" +#include "node/project/project.h" + +namespace olive { + +#define super ShapeNodeBase + +enum TextVerticalAlign { + kVerticalAlignTop, + kVerticalAlignCenter, + kVerticalAlignBottom, +}; + +const QString TextGeneratorV3::kTextInput = QStringLiteral("text_in"); + +TextGeneratorV3::TextGeneratorV3() : + ShapeNodeBase(false) +{ + AddInput(kTextInput, NodeValue::kText, QStringLiteral("

%1

").arg(tr("Sample Text"))); + + SetStandardValue(kSizeInput, QVector2D(400, 300)); + + text_gizmo_ = new TextGizmo(this); + text_gizmo_->SetInput(NodeInput(this, kTextInput)); +} + +QString TextGeneratorV3::Name() const +{ + return tr("Text"); +} + +QString TextGeneratorV3::id() const +{ + return QStringLiteral("org.olivevideoeditor.Olive.text3"); +} + +QVector TextGeneratorV3::Category() const +{ + return {kCategoryGenerator}; +} + +QString TextGeneratorV3::Description() const +{ + return tr("Generate rich text."); +} + +void TextGeneratorV3::Retranslate() +{ + super::Retranslate(); + + SetInputName(kTextInput, tr("Text")); +} + +void TextGeneratorV3::Value(const NodeValueRow &value, const NodeGlobals &globals, NodeValueTable *table) const +{ + GenerateJob job; + job.InsertValue(value); + job.SetAlphaChannelRequired(GenerateJob::kAlphaForceOn); + job.SetRequestedFormat(VideoParams::kFormatUnsigned8); + + // FIXME: Provide user override for this + job.SetColorspace(project()->color_manager()->GetDefaultInputColorSpace()); + + if (!job.GetValue(kTextInput).data().toString().isEmpty()) { + table->Push(NodeValue::kGenerateJob, QVariant::fromValue(job), this); + } +} + +void TextGeneratorV3::GenerateFrame(FramePtr frame, const GenerateJob& job) const +{ + QImage img(reinterpret_cast(frame->data()), frame->width(), frame->height(), frame->linesize_bytes(), QImage::Format_RGBA8888_Premultiplied); + img.fill(Qt::transparent); + + // 96 DPI in DPM (96 / 2.54 * 100) + const int dpm = 3780; + img.setDotsPerMeterX(dpm); + img.setDotsPerMeterY(dpm); + + QTextDocument text_doc; + text_doc.documentLayout()->setPaintDevice(&img); + + QString html = job.GetValue(kTextInput).data().toString(); + Html::HtmlToDoc(&text_doc, html); + + QVector2D size = job.GetValue(kSizeInput).data().value(); + text_doc.setTextWidth(size.x()); + + // Draw rich text onto image + QPainter p(&img); + p.scale(1.0 / frame->video_params().divider(), 1.0 / frame->video_params().divider()); + + QVector2D pos = job.GetValue(kPositionInput).data().value(); + p.translate(pos.x() - size.x()/2, pos.y() - size.y()/2); + p.translate(frame->video_params().width()/2, frame->video_params().height()/2); + p.setClipRect(0, 0, size.x(), size.y()); + + // Ensure default text color is white + QAbstractTextDocumentLayout::PaintContext ctx; + ctx.palette.setColor(QPalette::Text, Qt::white); + + text_doc.documentLayout()->draw(&p, ctx); +} + +void TextGeneratorV3::UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) +{ + super::UpdateGizmoPositions(row, globals); + + QRectF rect = poly_gizmo()->GetPolygon().boundingRect(); + text_gizmo_->SetRect(rect); + text_gizmo_->SetHtml(row[kTextInput].data().toString()); +} + +} diff --git a/app/node/generator/text/textv3.h b/app/node/generator/text/textv3.h new file mode 100644 index 000000000..1fd9237c4 --- /dev/null +++ b/app/node/generator/text/textv3.h @@ -0,0 +1,60 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef TEXTGENERATORV3_H +#define TEXTGENERATORV3_H + +#include "node/generator/shape/shapenodebase.h" +#include "node/gizmo/text.h" + +namespace olive { + +class TextGeneratorV3 : public ShapeNodeBase +{ + Q_OBJECT +public: + TextGeneratorV3(); + + NODE_DEFAULT_DESTRUCTOR(TextGeneratorV3) + NODE_COPY_FUNCTION(TextGeneratorV3) + + virtual QString Name() const override; + virtual QString id() const override; + virtual QVector Category() const override; + virtual QString Description() const override; + + virtual void Retranslate() override; + + virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const override; + + virtual void GenerateFrame(FramePtr frame, const GenerateJob &job) const override; + + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals) override; + + static const QString kTextInput; + +private: + TextGizmo *text_gizmo_; + +}; + +} + +#endif // TEXTGENERATORV3_H diff --git a/app/node/gizmo/CMakeLists.txt b/app/node/gizmo/CMakeLists.txt new file mode 100644 index 000000000..3c43aaf85 --- /dev/null +++ b/app/node/gizmo/CMakeLists.txt @@ -0,0 +1,36 @@ +# Olive - Non-Linear Video Editor +# Copyright (C) 2021 Olive Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +set(OLIVE_SOURCES + ${OLIVE_SOURCES} + node/gizmo/draggable.cpp + node/gizmo/draggable.h + node/gizmo/gizmo.cpp + node/gizmo/gizmo.h + node/gizmo/line.cpp + node/gizmo/line.h + node/gizmo/path.cpp + node/gizmo/path.h + node/gizmo/point.cpp + node/gizmo/point.h + node/gizmo/polygon.cpp + node/gizmo/polygon.h + node/gizmo/screen.cpp + node/gizmo/screen.h + node/gizmo/text.cpp + node/gizmo/text.h + PARENT_SCOPE +) diff --git a/app/node/gizmo/draggable.cpp b/app/node/gizmo/draggable.cpp new file mode 100644 index 000000000..d01c2144a --- /dev/null +++ b/app/node/gizmo/draggable.cpp @@ -0,0 +1,52 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "draggable.h" + +namespace olive { + +DraggableGizmo::DraggableGizmo(QObject *parent) + : NodeGizmo{parent}, + drag_value_behavior_(kAbsolute) +{ +} + +void DraggableGizmo::DragStart(const NodeValueRow &row, double abs_x, double abs_y, const rational &time) +{ + for (int i=0; i. + +***/ + +#ifndef DRAGGABLEGIZMO_H +#define DRAGGABLEGIZMO_H + +#include "common/rational.h" +#include "gizmo.h" +#include "node/inputdragger.h" +#include "undo/undocommand.h" + +namespace olive { + +class DraggableGizmo : public NodeGizmo +{ + Q_OBJECT +public: + /// Changes what the X/Y coordinates emitted from HandleMovement specify + enum DragValueBehavior { + /// X/Y will be the exact mouse coordinates (in sequence pixels) + kAbsolute, + + /// X/Y will be the movement since the last time HandleMovement was called + kDeltaFromPrevious, + + /// X/Y will be the movement from the start of the drag + kDeltaFromStart + }; + + explicit DraggableGizmo(QObject *parent = nullptr); + + void DragStart(const NodeValueRow &row, double abs_x, double abs_y, const olive::rational &time); + + void DragMove(double x, double y, const Qt::KeyboardModifiers &modifiers); + + void DragEnd(olive::MultiUndoCommand *command); + + void AddInput(const NodeKeyframeTrackReference &input) + { + inputs_.append(input); + draggers_.append(NodeInputDragger()); + } + + QVector &GetDraggers() + { + return draggers_; + } + + DragValueBehavior GetDragValueBehavior() const { return drag_value_behavior_; } + void SetDragValueBehavior(DragValueBehavior d) { drag_value_behavior_ = d; } + +signals: + void HandleStart(const olive::NodeValueRow &row, double x, double y, const olive::rational &time); + + void HandleMovement(double x, double y, const Qt::KeyboardModifiers &modifiers); + +private: + QVector inputs_; + + QVector draggers_; + + DragValueBehavior drag_value_behavior_; + +}; + +} + +#endif // DRAGGABLEGIZMO_H diff --git a/app/node/gizmo/gizmo.cpp b/app/node/gizmo/gizmo.cpp new file mode 100644 index 000000000..f885a62e9 --- /dev/null +++ b/app/node/gizmo/gizmo.cpp @@ -0,0 +1,30 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "gizmo.h" + +namespace olive { + +NodeGizmo::NodeGizmo(QObject *parent) +{ + setParent(parent); +} + +} diff --git a/app/node/gizmo/gizmo.h b/app/node/gizmo/gizmo.h new file mode 100644 index 000000000..5a3e585d2 --- /dev/null +++ b/app/node/gizmo/gizmo.h @@ -0,0 +1,51 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef NODEGIZMO_H +#define NODEGIZMO_H + +#include +#include + +#include "node/globals.h" + +namespace olive { + +class NodeGizmo : public QObject +{ + Q_OBJECT +public: + explicit NodeGizmo(QObject *parent = nullptr); + + virtual void Draw(QPainter *p) const {} + + const NodeGlobals &GetGlobals() const { return globals_; } + void SetGlobals(const NodeGlobals &globals) { globals_ = globals; } + +signals: + +private: + NodeGlobals globals_; + +}; + +} + +#endif // NODEGIZMO_H diff --git a/app/node/gizmo/line.cpp b/app/node/gizmo/line.cpp new file mode 100644 index 000000000..c1f63027e --- /dev/null +++ b/app/node/gizmo/line.cpp @@ -0,0 +1,38 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "line.h" + +namespace olive { + +LineGizmo::LineGizmo(QObject *parent) : + NodeGizmo(parent) +{ +} + +void LineGizmo::Draw(QPainter *p) const +{ + p->setBrush(Qt::NoBrush); + p->setPen(QPen(Qt::white, 0)); + + p->drawLine(line_); +} + +} diff --git a/app/node/gizmo/line.h b/app/node/gizmo/line.h new file mode 100644 index 000000000..79861b269 --- /dev/null +++ b/app/node/gizmo/line.h @@ -0,0 +1,48 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef LINEGIZMO_H +#define LINEGIZMO_H + +#include + +#include "gizmo.h" + +namespace olive { + +class LineGizmo : public NodeGizmo +{ + Q_OBJECT +public: + LineGizmo(QObject *parent = nullptr); + + const QLineF &GetLine() const { return line_; } + void SetLine(const QLineF &line) { line_ = line; } + + virtual void Draw(QPainter *p) const override; + +private: + QLineF line_; + +}; + +} + +#endif // LINEGIZMO_H diff --git a/app/node/gizmo/path.cpp b/app/node/gizmo/path.cpp new file mode 100644 index 000000000..95262093a --- /dev/null +++ b/app/node/gizmo/path.cpp @@ -0,0 +1,39 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "path.h" + +namespace olive { + +PathGizmo::PathGizmo(QObject *parent) : + DraggableGizmo{parent} +{ + +} + +void PathGizmo::Draw(QPainter *p) const +{ + p->setPen(QPen(Qt::white, 0)); + p->setBrush(Qt::NoBrush); + + p->drawPath(path_); +} + +} diff --git a/app/node/gizmo/path.h b/app/node/gizmo/path.h new file mode 100644 index 000000000..5e1bf0859 --- /dev/null +++ b/app/node/gizmo/path.h @@ -0,0 +1,48 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef PATHGIZMO_H +#define PATHGIZMO_H + +#include + +#include "draggable.h" + +namespace olive { + +class PathGizmo : public DraggableGizmo +{ + Q_OBJECT +public: + explicit PathGizmo(QObject *parent = nullptr); + + const QPainterPath &GetPath() const { return path_; } + void SetPath(const QPainterPath &path) { path_ = path; } + + virtual void Draw(QPainter *p) const override; + +private: + QPainterPath path_; + +}; + +} + +#endif // PATHGIZMO_H diff --git a/app/node/gizmo/point.cpp b/app/node/gizmo/point.cpp new file mode 100644 index 000000000..3926209a6 --- /dev/null +++ b/app/node/gizmo/point.cpp @@ -0,0 +1,99 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "point.h" + +#include + +namespace olive { + +PointGizmo::PointGizmo(const Shape &shape, bool smaller, QObject *parent) : + DraggableGizmo{parent}, + shape_(shape), + smaller_(smaller) +{ +} + +PointGizmo::PointGizmo(const Shape &shape, QObject *parent) : + PointGizmo(shape, false, parent) +{ +} + +PointGizmo::PointGizmo(QObject *parent) : + PointGizmo(kSquare, parent) +{ +} + +void PointGizmo::Draw(QPainter *p) const +{ + QRectF rect = GetDrawingRect(GetStandardRadius() / p->transform().m11()); + + if (shape_ != kAnchorPoint) { + p->setPen(Qt::NoPen); + p->setBrush(Qt::white); + } + + switch (shape_) { + case kSquare: + p->drawRect(rect); + break; + case kCircle: + p->drawEllipse(rect); + break; + case kAnchorPoint: + p->setPen(QPen(Qt::white, 0)); + p->setBrush(Qt::NoBrush); + + p->drawEllipse(rect); + p->drawLines({QPointF(rect.left(), rect.center().y()), + QPointF(rect.right(), rect.center().y()), + QPointF(rect.center().x(), rect.top()), + QPointF(rect.center().x(), rect.bottom())}); + break; + } +} + +QRectF PointGizmo::GetClickingRect(const QTransform &t) const +{ + return GetDrawingRect(GetStandardRadius() / t.m11() * 1.5); +} + +double PointGizmo::GetStandardRadius() +{ + return QFontMetrics(qApp->font()).height() * 0.25; +} + +QRectF PointGizmo::GetDrawingRect(double radius) const +{ + if (shape_ == kAnchorPoint) { + radius *= 2; + } + + if (smaller_) { + radius *= 0.5; + } + + return QRectF(point_.x() - radius, + point_.y() - radius, + 2*radius, + 2*radius); +} + +} diff --git a/app/node/gizmo/point.h b/app/node/gizmo/point.h new file mode 100644 index 000000000..b9ec47488 --- /dev/null +++ b/app/node/gizmo/point.h @@ -0,0 +1,72 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef POINTGIZMO_H +#define POINTGIZMO_H + +#include + +#include "draggable.h" + +namespace olive { + +class PointGizmo : public DraggableGizmo +{ + Q_OBJECT +public: + enum Shape { + kSquare, + kCircle, + kAnchorPoint + }; + + explicit PointGizmo(const Shape &shape, bool smaller, QObject *parent = nullptr); + explicit PointGizmo(const Shape &shape, QObject *parent = nullptr); + explicit PointGizmo(QObject *parent = nullptr); + + const Shape &GetShape() const { return shape_; } + void SetShape(const Shape &s) { shape_ = s; } + + const QPointF &GetPoint() const { return point_; } + void SetPoint(const QPointF &pt) { point_ = pt; } + + bool GetSmaller() const { return smaller_; } + void SetSmaller(bool e) { smaller_ = e; } + + virtual void Draw(QPainter *p) const override; + + QRectF GetClickingRect(const QTransform &t) const; + +private: + static double GetStandardRadius(); + + QRectF GetDrawingRect(double radius) const; + + Shape shape_; + + QPointF point_; + + bool smaller_; + +}; + +} + +#endif // POINTGIZMO_H diff --git a/app/node/gizmo/polygon.cpp b/app/node/gizmo/polygon.cpp new file mode 100644 index 000000000..29614c038 --- /dev/null +++ b/app/node/gizmo/polygon.cpp @@ -0,0 +1,38 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "polygon.h" + +namespace olive { + +PolygonGizmo::PolygonGizmo(QObject *parent) + : DraggableGizmo{parent} +{ +} + +void PolygonGizmo::Draw(QPainter *p) const +{ + p->setPen(QPen(Qt::white, 0)); + p->setBrush(Qt::NoBrush); + + p->drawPolyline(polygon_); +} + +} diff --git a/app/node/gizmo/polygon.h b/app/node/gizmo/polygon.h new file mode 100644 index 000000000..92e2a5398 --- /dev/null +++ b/app/node/gizmo/polygon.h @@ -0,0 +1,48 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef POLYGONGIZMO_H +#define POLYGONGIZMO_H + +#include + +#include "draggable.h" + +namespace olive { + +class PolygonGizmo : public DraggableGizmo +{ + Q_OBJECT +public: + explicit PolygonGizmo(QObject *parent = nullptr); + + const QPolygonF &GetPolygon() const { return polygon_; } + void SetPolygon(const QPolygonF &polygon) { polygon_ = polygon; } + + virtual void Draw(QPainter *p) const override; + +private: + QPolygonF polygon_; + +}; + +} + +#endif // POLYGONGIZMO_H diff --git a/app/node/gizmo/screen.cpp b/app/node/gizmo/screen.cpp new file mode 100644 index 000000000..2848c8979 --- /dev/null +++ b/app/node/gizmo/screen.cpp @@ -0,0 +1,31 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "screen.h" + +namespace olive { + +ScreenGizmo::ScreenGizmo(QObject *parent) + : DraggableGizmo{parent} +{ + +} + +} diff --git a/app/node/gizmo/screen.h b/app/node/gizmo/screen.h new file mode 100644 index 000000000..0f0e76444 --- /dev/null +++ b/app/node/gizmo/screen.h @@ -0,0 +1,38 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef SCREENGIZMO_H +#define SCREENGIZMO_H + +#include "draggable.h" + +namespace olive { + +class ScreenGizmo : public DraggableGizmo +{ + Q_OBJECT +public: + explicit ScreenGizmo(QObject *parent = nullptr); + +}; + +} + +#endif // SCREENGIZMO_H diff --git a/app/node/gizmo/text.cpp b/app/node/gizmo/text.cpp new file mode 100644 index 000000000..031c971f7 --- /dev/null +++ b/app/node/gizmo/text.cpp @@ -0,0 +1,43 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "text.h" + +#include "core.h" +#include "undo/undocommand.h" + +namespace olive { + +TextGizmo::TextGizmo(QObject *parent) + : NodeGizmo{parent} +{ + +} + +void TextGizmo::UpdateInputHtml(const QString &s, const rational &time) +{ + if (input_.IsValid()) { + MultiUndoCommand *command = new MultiUndoCommand(); + Node::SetValueAtTime(input_.input(), time, s, input_.track(), command, true); + Core::instance()->undo_stack()->pushIfHasChildren(command); + } +} + +} diff --git a/app/node/gizmo/text.h b/app/node/gizmo/text.h new file mode 100644 index 000000000..6a8876cee --- /dev/null +++ b/app/node/gizmo/text.h @@ -0,0 +1,56 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef TEXTGIZMO_H +#define TEXTGIZMO_H + +#include "gizmo.h" +#include "node/param.h" + +namespace olive { + +class TextGizmo : public NodeGizmo +{ + Q_OBJECT +public: + explicit TextGizmo(QObject *parent = nullptr); + + const QRectF &GetRect() const { return rect_; } + void SetRect(const QRectF &r) { rect_ = r; } + + const QString &GetHtml() const { return text_; } + void SetHtml(const QString &t) { text_ = t; } + + void SetInput(const NodeKeyframeTrackReference &input) { input_ = input; } + + void UpdateInputHtml(const QString &s, const rational &time); + +private: + QRectF rect_; + + QString text_; + + NodeKeyframeTrackReference input_; + +}; + +} + +#endif // TEXTGIZMO_H diff --git a/app/node/globals.h b/app/node/globals.h index 921caaf33..17ba9d429 100644 --- a/app/node/globals.h +++ b/app/node/globals.h @@ -30,6 +30,8 @@ namespace olive { class NodeGlobals { public: + NodeGlobals(){} + NodeGlobals(const QVector2D &resolution, const rational &pixel_aspect, const TimeRange &time) : resolution_(resolution), pixel_aspect_(pixel_aspect), diff --git a/app/node/inputdragger.h b/app/node/inputdragger.h index 6ab0010f9..5b530d515 100644 --- a/app/node/inputdragger.h +++ b/app/node/inputdragger.h @@ -51,6 +51,16 @@ public: return start_value_; } + const NodeKeyframeTrackReference &GetInput() const + { + return input_; + } + + const rational &GetTime() const + { + return time_; + } + private: NodeKeyframeTrackReference input_; diff --git a/app/node/node.cpp b/app/node/node.cpp index 57bf193b5..dd4f98878 100644 --- a/app/node/node.cpp +++ b/app/node/node.cpp @@ -1248,28 +1248,6 @@ void Node::IgnoreHashingFrom(const QString &input_id) ignore_when_hashing_.append(input_id); } -bool Node::HasGizmos() const -{ - return false; -} - -void Node::DrawGizmos(const NodeValueRow &, const NodeGlobals &, QPainter *) -{ -} - -bool Node::GizmoPress(const NodeValueRow &, const NodeGlobals &, const QPointF &) -{ - return false; -} - -void Node::GizmoMove(const QPointF &, const rational&, const Qt::KeyboardModifiers &) -{ -} - -void Node::GizmoRelease(MultiUndoCommand *) -{ -} - const QString &Node::GetLabel() const { return label_; @@ -1760,39 +1738,6 @@ void Node::ClearElement(const QString& input, int index) SetSplitStandardValue(input, GetSplitDefaultValue(input), index); } -QRectF Node::CreateGizmoHandleRect(const QPointF &pt, int radius) -{ - return QRectF(pt.x() - radius, - pt.y() - radius, - 2*radius, - 2*radius); -} - -double Node::GetGizmoHandleRadius(const QTransform &transform) -{ - double raw_value = QFontMetrics(qApp->font()).height() * 0.25; - - raw_value /= transform.m11(); - - return raw_value; -} - -void Node::DrawAndExpandGizmoHandles(QPainter *p, int handle_radius, QRectF *rects, int count) -{ - p->setPen(Qt::NoPen); - p->setBrush(Qt::white); - - for (int i=0; idrawRect(r); - - // Extend rect so it's easier to drag with handle - r.adjust(-handle_radius, -handle_radius, handle_radius, handle_radius); - } -} - void Node::InputValueChangedEvent(const QString &input, int element) { Q_UNUSED(input) @@ -1827,9 +1772,7 @@ void Node::childEvent(QChildEvent *event) { super::childEvent(event); - NodeKeyframe* key = dynamic_cast(event->child()); - - if (key) { + if (NodeKeyframe* key = dynamic_cast(event->child())) { NodeInput i(this, key->input(), key->element()); if (event->type() == QEvent::ChildAdded) { @@ -1857,6 +1800,12 @@ void Node::childEvent(QChildEvent *event) GetImmediate(key->input(), key->element())->remove_keyframe(key); ParameterValueChanged(i, time_affected); } + } else if (NodeGizmo *gizmo = dynamic_cast(event->child())) { + if (event->type() == QEvent::ChildAdded) { + gizmos_.append(gizmo); + } else if (event->type() == QEvent::ChildRemoved) { + gizmos_.removeOne(gizmo); + } } } @@ -1945,6 +1894,44 @@ void Node::InvalidateFromKeyframeTypeChanged() emit KeyframeTypeChanged(key); } +void Node::SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key) +{ + if (input.IsKeyframing()) { + rational node_time = time; + + NodeKeyframe* existing_key = input.GetKeyframeAtTimeOnTrack(node_time, track); + + if (existing_key) { + command->add_child(new NodeParamSetKeyframeValueCommand(existing_key, value)); + } else { + // No existing key, create a new one + int nb_tracks = NodeValue::get_number_of_keyframe_tracks(input.node()->GetInputDataType(input.input())); + for (int i=0; iGetSplitValueAtTimeOnTrack(input.input(), node_time, i, input.element()); + } + + NodeKeyframe* new_key = new NodeKeyframe(node_time, + track_value, + input.node()->GetBestKeyframeTypeForTimeOnTrack(NodeKeyframeTrackReference(input, i), node_time), + i, + input.element(), + input.input()); + + command->add_child(new NodeParamInsertKeyframeCommand(input.node(), new_key)); + } + } + } else { + command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(input, track), value)); + } +} + Project *Node::ArrayInsertCommand::GetRelevantProject() const { return node_->project(); diff --git a/app/node/node.h b/app/node/node.h index cd168d84f..3268cc34e 100644 --- a/app/node/node.h +++ b/app/node/node.h @@ -35,6 +35,7 @@ #include "common/rational.h" #include "common/timerange.h" #include "common/xmlutils.h" +#include "node/gizmo/draggable.h" #include "node/globals.h" #include "node/keyframe.h" #include "node/inputimmediate.h" @@ -840,13 +841,17 @@ public: */ virtual void Value(const NodeValueRow& value, const NodeGlobals &globals, NodeValueTable *table) const; - virtual bool HasGizmos() const; + bool HasGizmos() const + { + return !gizmos_.isEmpty(); + } - virtual void DrawGizmos(const NodeValueRow& row, const NodeGlobals &globals, QPainter* p); + const QVector &GetGizmos() const + { + return gizmos_; + } - virtual bool GizmoPress(const NodeValueRow& row, const NodeGlobals &globals, const QPointF& p); - virtual void GizmoMove(const QPointF& p, const rational &time, const Qt::KeyboardModifiers &modifiers); - virtual void GizmoRelease(MultiUndoCommand *command); + virtual void UpdateGizmoPositions(const NodeValueRow &row, const NodeGlobals &globals){} const QString& GetLabel() const; void SetLabel(const QString& s); @@ -942,6 +947,8 @@ public: InputFlags GetInputFlags(const QString& input) const; + static void SetValueAtTime(const NodeInput &input, const rational &time, const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key); + protected: virtual void Hash(QCryptographicHash& hash, const NodeGlobals &globals, const VideoParams& video_params) const; @@ -1008,12 +1015,6 @@ protected: kGizmoScaleCount, }; - static QRectF CreateGizmoHandleRect(const QPointF& pt, int radius); - - static double GetGizmoHandleRadius(const QTransform& transform); - - static void DrawAndExpandGizmoHandles(QPainter* p, int handle_radius, QRectF* rects, int count); - virtual void LinkChangeEvent(){} virtual void InputValueChangedEvent(const QString& input, int element); @@ -1038,6 +1039,34 @@ protected: flags_ = f; } + template + T *AddDraggableGizmo(const QVector &inputs = QVector(), DraggableGizmo::DragValueBehavior behavior = DraggableGizmo::kDeltaFromStart) + { + T *gizmo = new T(this); + gizmo->SetDragValueBehavior(behavior); + foreach (const NodeKeyframeTrackReference &input, inputs) { + gizmo->AddInput(input); + } + connect(gizmo, &DraggableGizmo::HandleStart, this, &Node::GizmoDragStart); + connect(gizmo, &DraggableGizmo::HandleMovement, this, &Node::GizmoDragMove); + return gizmo; + } + + template + T *AddDraggableGizmo(const QStringList &inputs, DraggableGizmo::DragValueBehavior behavior = DraggableGizmo::kDeltaFromStart) + { + QVector refs(inputs.size()); + for (int i=0; i(refs, behavior); + } + +protected slots: + virtual void GizmoDragStart(const olive::NodeValueRow &row, double x, double y, const olive::rational &time){} + + virtual void GizmoDragMove(double x, double y, const Qt::KeyboardModifiers &modifiers){} + signals: /** * @brief Signal emitted when SetLabel() is called @@ -1315,6 +1344,8 @@ private: uint64_t flags_; + QVector gizmos_; + private slots: /** * @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time diff --git a/app/node/value.h b/app/node/value.h index 5e131b06e..518517b78 100644 --- a/app/node/value.h +++ b/app/node/value.h @@ -25,6 +25,8 @@ #include #include +#include "undo/undocommand.h" + namespace olive { class Node; diff --git a/app/render/job/generatejob.h b/app/render/job/generatejob.h index c2f7d2da6..465ba1609 100644 --- a/app/render/job/generatejob.h +++ b/app/render/job/generatejob.h @@ -48,11 +48,16 @@ public: void SetRequestedFormat(VideoParams::Format f) { requested_format_ = f; } + const QString &GetColorspace() const { return colorspace_; } + void SetColorspace(const QString &s) { colorspace_ = s; } + private: AlphaChannelSetting alpha_channel_required_; VideoParams::Format requested_format_; + QString colorspace_; + }; } diff --git a/app/render/renderprocessor.cpp b/app/render/renderprocessor.cpp index 970b2f326..480ed417f 100644 --- a/app/render/renderprocessor.cpp +++ b/app/render/renderprocessor.cpp @@ -578,6 +578,15 @@ TexturePtr RenderProcessor::ProcessFrameGeneration(const Node *node, const Gener frame->data(), frame->linesize_pixels()); + if (!job.GetColorspace().isEmpty()) { + // Convert to reference space + TexturePtr dest = render_ctx_->CreateTexture(GetCacheVideoParams()); + ColorManager* color_manager = Node::ValueToPtr(ticket_->property("colormanager")); + ColorProcessorPtr cp = ColorProcessor::Create(color_manager, job.GetColorspace(), color_manager->GetReferenceColorSpace()); + render_ctx_->BlitColorManaged(cp, texture, Renderer::kAlphaAssociated, dest.get()); + texture = dest; + } + return texture; } diff --git a/app/ui/icons/icons.cpp b/app/ui/icons/icons.cpp index 635aaf807..0d61f5843 100644 --- a/app/ui/icons/icons.cpp +++ b/app/ui/icons/icons.cpp @@ -65,6 +65,13 @@ QIcon icon::TriUp; QIcon icon::TriLeft; QIcon icon::TriDown; QIcon icon::TriRight; +QIcon icon::TextBold; +QIcon icon::TextItalic; +QIcon icon::TextUnderline; +QIcon icon::TextAlignLeft; +QIcon icon::TextAlignRight; +QIcon icon::TextAlignCenter; +QIcon icon::TextAlignJustify; QIcon icon::Snapping; QIcon icon::ZoomIn; QIcon icon::ZoomOut; @@ -119,6 +126,14 @@ void icon::LoadAll(const QString& theme) TriDown = Create(theme, "tri-down"); TriRight = Create(theme, "tri-right"); + TextBold = Create(theme, "text-bold"); + TextItalic = Create(theme, "text-italic"); + TextUnderline = Create(theme, "text-underline"); + TextAlignLeft = Create(theme, "align-left"); + TextAlignRight = Create(theme, "align-right"); + TextAlignCenter = Create(theme, "align-center"); + TextAlignJustify = Create(theme, "align-justify-all"); + Snapping = Create(theme, "magnet"); ZoomIn = Create(theme, "zoomin"); ZoomOut = Create(theme, "zoomout"); diff --git a/app/ui/icons/icons.h b/app/ui/icons/icons.h index 2bca32bb5..1cd60e0f9 100644 --- a/app/ui/icons/icons.h +++ b/app/ui/icons/icons.h @@ -74,6 +74,15 @@ extern QIcon TriLeft; extern QIcon TriDown; extern QIcon TriRight; +// Text +extern QIcon TextBold; +extern QIcon TextItalic; +extern QIcon TextUnderline; +extern QIcon TextAlignLeft; +extern QIcon TextAlignRight; +extern QIcon TextAlignCenter; +extern QIcon TextAlignJustify; + // Miscellaneous Icons extern QIcon Snapping; extern QIcon ZoomIn; diff --git a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp index 05a5cccce..feca63029 100644 --- a/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp +++ b/app/widget/nodeparamview/nodeparamviewwidgetbridge.cpp @@ -198,40 +198,7 @@ void NodeParamViewWidgetBridge::SetInputValue(const QVariant &value, int track) void NodeParamViewWidgetBridge::SetInputValueInternal(const QVariant &value, int track, MultiUndoCommand *command, bool insert_on_all_tracks_if_no_key) { - if (GetInnerInput().IsKeyframing()) { - rational node_time = GetCurrentTimeAsNodeTime(); - - NodeKeyframe* existing_key = GetInnerInput().GetKeyframeAtTimeOnTrack(node_time, track); - - if (existing_key) { - command->add_child(new NodeParamSetKeyframeValueCommand(existing_key, value)); - } else { - // No existing key, create a new one - int nb_tracks = NodeValue::get_number_of_keyframe_tracks(GetInnerInput().node()->GetInputDataType(GetInnerInput().input())); - for (int i=0; iGetSplitValueAtTimeOnTrack(GetInnerInput().input(), node_time, i, GetInnerInput().element()); - } - - NodeKeyframe* new_key = new NodeKeyframe(node_time, - track_value, - GetInnerInput().node()->GetBestKeyframeTypeForTimeOnTrack(NodeKeyframeTrackReference(GetInnerInput(), i), node_time), - i, - GetInnerInput().element(), - GetInnerInput().input()); - - command->add_child(new NodeParamInsertKeyframeCommand(GetInnerInput().node(), new_key)); - } - } - } else { - command->add_child(new NodeParamSetStandardValueCommand(NodeKeyframeTrackReference(GetInnerInput(), track), value)); - } + Node::SetValueAtTime(GetInnerInput(), GetCurrentTimeAsNodeTime(), value, track, command, insert_on_all_tracks_if_no_key); } void NodeParamViewWidgetBridge::ProcessSlider(NumericSliderBase *slider, int slider_track, const QVariant &value) diff --git a/app/widget/slider/base/sliderbase.cpp b/app/widget/slider/base/sliderbase.cpp index 7ca14d1f0..7549e9f5c 100644 --- a/app/widget/slider/base/sliderbase.cpp +++ b/app/widget/slider/base/sliderbase.cpp @@ -57,6 +57,7 @@ SliderBase::SliderBase(QWidget *parent) : void SliderBase::SetAlignment(Qt::Alignment alignment) { label_->setAlignment(alignment); + editor_->setAlignment(alignment); } bool SliderBase::IsTristate() const diff --git a/app/widget/timelinewidget/tool/add.cpp b/app/widget/timelinewidget/tool/add.cpp index a9c9d3615..a8f778b5f 100644 --- a/app/widget/timelinewidget/tool/add.cpp +++ b/app/widget/timelinewidget/tool/add.cpp @@ -24,7 +24,7 @@ #include "node/factory.h" #include "node/generator/shape/shapenode.h" #include "node/generator/solid/solid.h" -#include "node/generator/text/text.h" +#include "node/generator/text/textv3.h" #include "widget/timelinewidget/timelinewidget.h" #include "widget/timelinewidget/undo/timelineundopointer.h" @@ -138,7 +138,7 @@ void AddTool::MouseRelease(TimelineViewMouseEvent *event) break; case olive::Tool::kAddableTitle: { - node_to_add = new TextGenerator(); + node_to_add = new TextGeneratorV3(); break; } case olive::Tool::kAddableBars: diff --git a/app/widget/viewer/CMakeLists.txt b/app/widget/viewer/CMakeLists.txt index 345702e69..074b0f3ef 100644 --- a/app/widget/viewer/CMakeLists.txt +++ b/app/widget/viewer/CMakeLists.txt @@ -32,6 +32,8 @@ set(OLIVE_SOURCES widget/viewer/viewersafemargininfo.h widget/viewer/viewersizer.cpp widget/viewer/viewersizer.h + widget/viewer/viewertexteditor.cpp + widget/viewer/viewertexteditor.h widget/viewer/viewerwindow.cpp widget/viewer/viewerwindow.h PARENT_SCOPE diff --git a/app/widget/viewer/viewerdisplay.cpp b/app/widget/viewer/viewerdisplay.cpp index 0c79e7dd2..a0087bc8c 100644 --- a/app/widget/viewer/viewerdisplay.cpp +++ b/app/widget/viewer/viewerdisplay.cpp @@ -21,6 +21,8 @@ #include "viewerdisplay.h" #include +#include +#include #include #include #include @@ -28,12 +30,23 @@ #include #include #include +#include +#include +#include #include "common/define.h" #include "common/functiontimer.h" +#include "common/html.h" #include "config/config.h" #include "core.h" +#include "node/gizmo/path.h" +#include "node/gizmo/point.h" +#include "node/gizmo/polygon.h" +#include "node/gizmo/screen.h" +#include "node/gizmo/text.h" #include "node/traverser.h" +#include "viewertexteditor.h" +#include "window/mainwindow/mainwindow.h" namespace olive { @@ -44,7 +57,8 @@ ViewerDisplayWidget::ViewerDisplayWidget(QWidget *parent) : deinterlace_texture_(nullptr), signal_cursor_color_(false), gizmos_(nullptr), - gizmo_click_(false), + current_gizmo_(nullptr), + gizmo_drag_started_(false), hand_dragging_(false), deinterlace_(false), show_fps_(false), @@ -175,7 +189,7 @@ void ViewerDisplayWidget::SetTime(const rational &time) } } -QPoint ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(QPoint pos) +QPointF ViewerDisplayWidget::TransformViewerSpaceToBufferSpace(const QPointF &pos) { /* * Inversion will only fail if the viewer has been scaled by 0 in any direction @@ -204,12 +218,12 @@ void ViewerDisplayWidget::IncrementSkippedFrames() void ViewerDisplayWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton && gizmos_ - && gizmos_->GizmoPress(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, GenerateGizmoTime()), TransformViewerSpaceToBufferSpace(event->pos()))) { + && (current_gizmo_ = TryGizmoPress(gizmo_db_, TransformViewerSpaceToBufferSpace(event->pos())))) { // Handle gizmo click - gizmo_click_ = true; - gizmo_drag_time_ = GetGizmoTime(); gizmo_start_drag_ = event->pos(); + gizmo_last_drag_ = gizmo_start_drag_; + current_gizmo_->SetGlobals(NodeTraverser::GenerateGlobals(gizmo_params_, GenerateGizmoTime())); } else if (IsHandDrag(event)) { @@ -242,14 +256,38 @@ void ViewerDisplayWidget::mouseMoveEvent(QMouseEvent *event) hand_last_drag_pos_ = event->pos(); - } else if (gizmo_click_) { + } else if (current_gizmo_) { // Signal movement - gizmos_->GizmoMove(TransformViewerSpaceToBufferSpace(event->pos()), - gizmo_drag_time_, - event->modifiers()); - gizmo_start_drag_ = event->pos(); - update(); + if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { + if (!gizmo_drag_started_) { + QPointF start = TransformViewerSpaceToBufferSpace(gizmo_start_drag_); + + rational gizmo_time = GetGizmoTime(); + NodeTraverser t; + t.SetCacheVideoParams(gizmo_params_); + NodeValueRow row = t.GenerateRow(gizmos_, TimeRange(gizmo_time, gizmo_time + gizmo_params_.frame_rate_as_time_base())); + + draggable->DragStart(row, start.x(), start.y(), gizmo_time); + gizmo_drag_started_ = true; + } + + QPointF v = TransformViewerSpaceToBufferSpace(event->pos()); + switch (draggable->GetDragValueBehavior()) { + case DraggableGizmo::kAbsolute: + // Above value is correct + break; + case DraggableGizmo::kDeltaFromPrevious: + v -= TransformViewerSpaceToBufferSpace(gizmo_last_drag_); + gizmo_last_drag_ = event->pos(); + break; + case DraggableGizmo::kDeltaFromStart: + v -= TransformViewerSpaceToBufferSpace(gizmo_start_drag_); + break; + } + + draggable->DragMove(v.x(), v.y(), event->modifiers()); + } } else { @@ -268,13 +306,18 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) hand_dragging_ = false; UpdateCursor(); - } else if (gizmo_click_) { + } else if (current_gizmo_) { // Handle gizmo - MultiUndoCommand *command = new MultiUndoCommand(); - gizmos_->GizmoRelease(command); - Core::instance()->undo_stack()->pushIfHasChildren(command); - gizmo_click_ = false; + if (gizmo_drag_started_) { + MultiUndoCommand *command = new MultiUndoCommand(); + if (DraggableGizmo *draggable = dynamic_cast(current_gizmo_)) { + draggable->DragEnd(command); + } + Core::instance()->undo_stack()->pushIfHasChildren(command); + gizmo_drag_started_ = false; + } + current_gizmo_ = nullptr; } else { @@ -284,6 +327,65 @@ void ViewerDisplayWidget::mouseReleaseEvent(QMouseEvent *event) } } +void ViewerDisplayWidget::mouseDoubleClickEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton && gizmos_) { + QPointF ptr = TransformViewerSpaceToBufferSpace(event->pos()); + foreach (NodeGizmo *g, gizmos_->GetGizmos()) { + if (TextGizmo *text = dynamic_cast(g)) { + if (text->GetRect().contains(ptr)) { + QTransform gizmo_transform = GenerateGizmoTransform(); + + ViewerTextEditor *text_edit = new ViewerTextEditor(gizmo_transform.m11(), this); + Html::HtmlToDoc(text_edit->document(), text->GetHtml()); + text_edit->setProperty("gizmo", reinterpret_cast(text)); + + QRectF transformed_geom = gizmo_transform.map(text->GetRect()).boundingRect(); + text_edit->setGeometry(transformed_geom.toRect()); + + ViewerTextEditorToolBar *toolbar = new ViewerTextEditorToolBar(this); + toolbar->setWindowFlags(Qt::FramelessWindowHint | Qt::Window | Qt::WindowStaysOnTopHint); + toolbar->resize(toolbar->sizeHint()); + + QPoint pos = mapToGlobal(QPoint(transformed_geom.x(), transformed_geom.y() - toolbar->height())); + for (QScreen *screen : qApp->screens()) { + if (screen->geometry().contains(pos)) { + if (pos.x() + toolbar->width() > screen->geometry().right()) { + pos.setX(screen->geometry().right() - toolbar->width()); + } + break; + } + } + toolbar->move(pos); + toolbar->show(); + + text_edit->show(); + + connect(text_edit, &ViewerTextEditor::textChanged, this, &ViewerDisplayWidget::TextEditChanged); + + text_edit->ConnectToolBar(toolbar); + + QPoint text_edit_pos = text_edit->mapFrom(this, event->pos()); + + // Ensure text edit is actually focused rather than the toolbar + connect(toolbar, &ViewerTextEditorToolBar::FirstPaint, this, [this, text_edit, text_edit_pos]{ + // Grab focus back from the toolbar + this->raise(); + this->activateWindow(); + text_edit->setFocus(); + + // Start text cursor where the user clicked + text_edit->setTextCursor(text_edit->cursorForPosition(text_edit_pos)); + }); + break; + } + } + } + } + + super::mouseDoubleClickEvent(event); +} + void ViewerDisplayWidget::dragEnterEvent(QDragEnterEvent *event) { emit DragEntered(event); @@ -396,7 +498,11 @@ void ViewerDisplayWidget::OnPaint() QPainter p(inner_widget()); p.setWorldTransform(GenerateGizmoTransform()); - gizmos_->DrawGizmos(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range), &p); + + gizmos_->UpdateGizmoPositions(gizmo_db_, NodeTraverser::GenerateGlobals(gizmo_params_, range)); + foreach (NodeGizmo *gizmo, gizmos_->GetGizmos()) { + gizmo->Draw(&p); + } } // Draw action/title safe areas @@ -563,6 +669,31 @@ QTransform ViewerDisplayWidget::GenerateGizmoTransform() return gizmo_transform; } +NodeGizmo *ViewerDisplayWidget::TryGizmoPress(const NodeValueRow &row, const QPointF &p) +{ + for (auto it=gizmos_->GetGizmos().crbegin(); it!=gizmos_->GetGizmos().crend(); it++) { + NodeGizmo *gizmo = *it; + if (PointGizmo *point = dynamic_cast(gizmo)) { + if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) { + return point; + } + } else if (PolygonGizmo *poly = dynamic_cast(gizmo)) { + if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) { + return poly; + } + } else if (PathGizmo *path = dynamic_cast(gizmo)) { + if (path->GetPath().contains(p)) { + return path; + } + } else if (ScreenGizmo *screen = dynamic_cast(gizmo)) { + // NOTE: Perhaps this should limit to the actual visible screen space? We'll see. + return screen; + } + } + + return nullptr; +} + void ViewerDisplayWidget::EmitColorAtCursor(QMouseEvent *e) { // Do this no matter what, emits signal to any pixel samplers @@ -657,4 +788,14 @@ void ViewerDisplayWidget::UpdateFromQueue() update(); } +void ViewerDisplayWidget::TextEditChanged() +{ + ViewerTextEditor *editor = static_cast(sender()); + + TextGizmo *gizmo = reinterpret_cast(editor->property("gizmo").value()); + + QString html = Html::DocToHtml(editor->document()); + gizmo->UpdateInputHtml(html, GetGizmoTime()); +} + } diff --git a/app/widget/viewer/viewerdisplay.h b/app/widget/viewer/viewerdisplay.h index f8bd30278..718fd3070 100644 --- a/app/widget/viewer/viewerdisplay.h +++ b/app/widget/viewer/viewerdisplay.h @@ -83,7 +83,7 @@ public: * @brief Transform a point from viewer space to the buffer space. * Multiplies by the inverted transform matrix to undo the scaling and translation. */ - QPoint TransformViewerSpaceToBufferSpace(QPoint pos); + QPointF TransformViewerSpaceToBufferSpace(const QPointF &pos); bool IsDeinterlacing() const { @@ -216,6 +216,8 @@ protected: */ virtual void mouseReleaseEvent(QMouseEvent* event) override; + virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + virtual void dragEnterEvent(QDragEnterEvent* event) override; virtual void dragLeaveEvent(QDragLeaveEvent* event) override; @@ -255,6 +257,8 @@ private: return TimeRange(node_time, node_time + gizmo_params_.frame_rate_as_time_base()); } + NodeGizmo *TryGizmoPress(const NodeValueRow &row, const QPointF &p); + /** * @brief Internal reference to the OpenGL texture to draw. Set in SetTexture() and used in paintGL(). */ @@ -302,10 +306,11 @@ private: Node* gizmos_; NodeValueRow gizmo_db_; - rational gizmo_drag_time_; VideoParams gizmo_params_; QPoint gizmo_start_drag_; - bool gizmo_click_; + QPoint gizmo_last_drag_; + NodeGizmo *current_gizmo_; + bool gizmo_drag_started_; rational time_; @@ -358,6 +363,8 @@ private slots: void UpdateFromQueue(); + void TextEditChanged(); + }; } diff --git a/app/widget/viewer/viewertexteditor.cpp b/app/widget/viewer/viewertexteditor.cpp new file mode 100644 index 000000000..ec630513f --- /dev/null +++ b/app/widget/viewer/viewertexteditor.cpp @@ -0,0 +1,483 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#include "viewertexteditor.h" + +#include +#include +#include +#include +#include + +#include "common/qtutils.h" +#include "ui/icons/icons.h" +#include "widget/colorbutton/colorbutton.h" + +namespace olive { + +#define super QTextEdit + +ViewerTextEditor::ViewerTextEditor(double scale, QWidget *parent) : + super(parent), + transparent_clone_(nullptr) +{ + // Ensure default text color is white + QPalette p = palette(); + p.setColor(QPalette::Text, Qt::white); + setPalette(p); + + document()->setDefaultStyleSheet(QStringLiteral("body { color: white; }")); + + viewport()->setAutoFillBackground(false); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + connect(horizontalScrollBar(), &QScrollBar::rangeChanged, this, &ViewerTextEditor::LockScrollBarMaximumToZero); + connect(verticalScrollBar(), &QScrollBar::rangeChanged, this, &ViewerTextEditor::LockScrollBarMaximumToZero); + + // Force DPI to the same one that we're using in the actual render + dpi_force_ = QImage(1, 1, QImage::Format_RGBA8888_Premultiplied); + + const int dpm = 3780 * scale; + dpi_force_.setDotsPerMeterX(dpm); + dpi_force_.setDotsPerMeterY(dpm); + document()->documentLayout()->setPaintDevice(&dpi_force_); + + connect(qApp, &QApplication::focusChanged, this, &ViewerTextEditor::FocusChanged); + connect(this, &QTextEdit::currentCharFormatChanged, this, &ViewerTextEditor::FormatChanged); + connect(document(), &QTextDocument::contentsChanged, this, &ViewerTextEditor::DocumentChanged, Qt::QueuedConnection); +} + +void ViewerTextEditor::ConnectToolBar(ViewerTextEditorToolBar *toolbar) +{ + connect(this, &ViewerTextEditor::destroyed, toolbar, &ViewerTextEditorToolBar::deleteLater); + + connect(toolbar, &ViewerTextEditorToolBar::FamilyChanged, this, &ViewerTextEditor::SetFamily); + connect(toolbar, &ViewerTextEditorToolBar::SizeChanged, this, &ViewerTextEditor::setFontPointSize); + connect(toolbar, &ViewerTextEditorToolBar::StyleChanged, this, &ViewerTextEditor::SetStyle); + connect(toolbar, &ViewerTextEditorToolBar::UnderlineChanged, this, &ViewerTextEditor::setFontUnderline); + connect(toolbar, &ViewerTextEditorToolBar::StrikethroughChanged, this, &ViewerTextEditor::SetFontStrikethrough); + connect(toolbar, &ViewerTextEditorToolBar::ColorChanged, this, &ViewerTextEditor::setTextColor); + connect(toolbar, &ViewerTextEditorToolBar::SmallCapsChanged, this, &ViewerTextEditor::SetSmallCaps); + connect(toolbar, &ViewerTextEditorToolBar::StretchChanged, this, &ViewerTextEditor::SetFontStretch); + connect(toolbar, &ViewerTextEditorToolBar::KerningChanged, this, &ViewerTextEditor::SetFontKerning); + connect(toolbar, &ViewerTextEditorToolBar::LineHeightChanged, this, &ViewerTextEditor::SetLineHeight); + connect(toolbar, &ViewerTextEditorToolBar::AlignmentChanged, this, [this](Qt::Alignment a){ + this->setAlignment(a); + + // Ensure no buttons are checked that shouldn't be + static_cast(sender())->SetAlignment(a); + }); + + UpdateToolBar(toolbar, this->currentCharFormat(), this->textCursor().blockFormat(), this->alignment()); + + toolbars_.append(toolbar); +} + +void ViewerTextEditor::keyPressEvent(QKeyEvent *event) +{ + super::keyPressEvent(event); + + if (event->key() == Qt::Key_Escape) { + deleteLater(); + } +} + +void ViewerTextEditor::paintEvent(QPaintEvent *e) +{ + QPainter p(this->viewport()); + + QAbstractTextDocumentLayout::PaintContext ctx; + + QRect r = e->rect(); + if (r.isValid()) + p.setClipRect(r, Qt::IntersectClip); + ctx.clip = r; + + ctx.cursorPosition = this->textCursor().position(); + + if (this->textCursor().hasSelection()) { + QAbstractTextDocumentLayout::Selection selection; + selection.cursor = this->textCursor(); + + QPalette::ColorGroup cg = this->hasFocus() ? QPalette::Active : QPalette::Inactive; + QBrush b = ctx.palette.brush(cg, QPalette::Highlight); + QColor bc = b.color(); + bc.setAlpha(128); + b.setColor(bc); + selection.format.setBackground(b); + QStyleOption opt; + opt.initFrom(this); + if (this->style()->styleHint(QStyle::SH_RichText_FullWidthSelection, &opt, this)) { + selection.format.setProperty(QTextFormat::FullWidthSelection, true); + } + ctx.selections.append(selection); + } + + transparent_clone_->documentLayout()->draw(&p, ctx); +} + +void ViewerTextEditor::UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTextCharFormat &f, const QTextBlockFormat &b, Qt::Alignment alignment) +{ + QFontDatabase fd; + + QString family = f.fontFamily(); + if (family.isEmpty()) { + family = qApp->font().family(); + } + + QString style = f.fontStyleName().toString(); + QStringList styles = fd.styles(family); + if (style.isEmpty() || !styles.contains(style)) { + // There seems to be no better way to find the "regular" style outside of this heuristic. + // Feel free to add more if a font isn't working right. + style = QStringLiteral("Regular"); + if (!styles.contains(style)) { + style = QStringLiteral("Normal"); + } + if (!styles.contains(style)) { + style = QStringLiteral("R"); + } + if (!styles.contains(style)) { + style = styles.first(); + } + } + + toolbar->SetFontFamily(family); + toolbar->SetFontSize(f.fontPointSize()); + toolbar->SetStyle(style); + toolbar->SetUnderline(f.fontUnderline()); + toolbar->SetStrikethrough(f.fontStrikeOut()); + toolbar->SetAlignment(alignment); + toolbar->SetColor(f.foreground().color()); + toolbar->SetSmallCaps(f.fontCapitalization() == QFont::SmallCaps); + toolbar->SetStretch(f.fontStretch() == 0 ? 100 : f.fontStretch()); + toolbar->SetKerning(f.fontLetterSpacing() == 0.0 ? 100 : f.fontLetterSpacing()); + toolbar->SetLineHeight(b.lineHeight() == 0.0 ? 100 : b.lineHeight()); +} + +void ViewerTextEditor::FocusChanged(QWidget *old, QWidget *now) +{ + QWidget *test = now; + + if (!test) { + // Ignore null focuses because that could be one of the toolbar widgets simply losing focus + // and that would be undesirable to close the text editor from + return; + } + + while (test) { + if (test == this + || dynamic_cast(test) + || dynamic_cast(test)) { + return; + } + + test = test->parentWidget(); + } + + // If we didn't return in the loop, the user must have focused on something else + deleteLater(); +} + +void ViewerTextEditor::FormatChanged(const QTextCharFormat &f) +{ + foreach (ViewerTextEditorToolBar *toolbar, toolbars_) { + UpdateToolBar(toolbar, f, textCursor().blockFormat(), this->alignment()); + } +} + +void ViewerTextEditor::SetFamily(const QString &s) +{ + QTextCharFormat f; + f.setFontFamily(s); +#if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) + f.setFontFamilies({s}); +#endif + mergeCurrentCharFormat(f); +} + +void ViewerTextEditor::SetStyle(const QString &s) +{ + QTextCharFormat f; + f.setFontStyleName(s); + mergeCurrentCharFormat(f); +} + +void ViewerTextEditor::SetFontStrikethrough(bool e) +{ + QTextCharFormat f; + f.setFontStrikeOut(e); + mergeCurrentCharFormat(f); +} + +void ViewerTextEditor::SetSmallCaps(bool e) +{ + QTextCharFormat f; + f.setFontCapitalization(e ? QFont::SmallCaps : QFont::MixedCase); + mergeCurrentCharFormat(f); +} + +void ViewerTextEditor::SetFontStretch(int i) +{ + QTextCharFormat f; + f.setFontStretch(i); + mergeCurrentCharFormat(f); +} + +void ViewerTextEditor::SetFontKerning(qreal i) +{ + QTextCharFormat f; + f.setFontLetterSpacing(i); + mergeCurrentCharFormat(f); +} + +void ViewerTextEditor::SetLineHeight(qreal i) +{ + QTextBlockFormat f = this->textCursor().blockFormat(); + f.setLineHeight(i, QTextBlockFormat::ProportionalHeight); + this->textCursor().setBlockFormat(f); +} + +void ViewerTextEditor::LockScrollBarMaximumToZero() +{ + static_cast(sender())->setMaximum(0); +} + +void ViewerTextEditor::DocumentChanged() +{ + // HACK: We want to show the text cursor and selections without necessarily rendering the text, + // because the text is already being rendered underneath the gizmo (and rendering twice will + // alter the overall look of the text while editing). This is something that Qt does not + // support by default, and while it could be solved by subclassing QAbstractTextDocumentLayout, + // this seems like overkill since 99% of QTextDocumentLayout's (the subclass used by default) + // functionality and would need to be fully implemented ourselves. So instead, we clone the + // document, set all of its colors to rgba(0,0,0,0) to make them transparent, and draw that + // document instead. The result is cursor and selections being rendered without the text. + // While this isn't the fastest code, nor is it the cleanest solution, it definitely works. + delete transparent_clone_; + transparent_clone_ = document()->clone(this); + transparent_clone_->documentLayout()->setPaintDevice(&dpi_force_); + + QTextCursor cursor(transparent_clone_); + cursor.select(QTextCursor::Document); + + QTextCharFormat fmt; + fmt.setForeground(QColor(0, 0, 0, 0)); + cursor.mergeCharFormat(fmt); +} + +ViewerTextEditorToolBar::ViewerTextEditorToolBar(QWidget *parent) : + QWidget(parent), + painted_(false) +{ + QVBoxLayout *outer_layout = new QVBoxLayout(this); + outer_layout->setSpacing(0); + + QHBoxLayout *basic_layout = new QHBoxLayout(); + outer_layout->addLayout(basic_layout); + + int advanced_slider_width = QtUtils::QFontMetricsWidth(fontMetrics(), QStringLiteral("9999.9%")); + + font_combo_ = new QFontComboBox(); + connect(font_combo_, &QFontComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::FamilyChanged); + connect(font_combo_, &QFontComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::UpdateFontStyleList); + basic_layout->addWidget(font_combo_); + + font_sz_slider_ = new FloatSlider(); + font_sz_slider_->SetMinimum(0.1); + font_sz_slider_->SetMaximum(9999.9); + font_sz_slider_->SetDecimalPlaces(1); + font_sz_slider_->SetAlignment(Qt::AlignCenter); + font_sz_slider_->setFixedWidth(advanced_slider_width); + connect(font_sz_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::SizeChanged); + font_sz_slider_->SetLadderElementCount(2); + basic_layout->addWidget(font_sz_slider_); + + style_combo_ = new QComboBox(); + connect(style_combo_, &QComboBox::currentTextChanged, this, &ViewerTextEditorToolBar::StyleChanged); + basic_layout->addWidget(style_combo_); + + underline_btn_ = new QPushButton(); + connect(underline_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::UnderlineChanged); + underline_btn_->setCheckable(true); + underline_btn_->setIcon(icon::TextUnderline); + basic_layout->addWidget(underline_btn_); + + strikethrough_btn_ = new QPushButton(); + connect(strikethrough_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::StrikethroughChanged); + strikethrough_btn_->setCheckable(true); + strikethrough_btn_->setText(tr("S")); // FIXME: Source icon + basic_layout->addWidget(strikethrough_btn_); + + basic_layout->addWidget(QtUtils::CreateVerticalLine()); + + align_left_btn_ = new QPushButton(); + align_left_btn_->setCheckable(true); + align_left_btn_->setIcon(icon::TextAlignLeft); + connect(align_left_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignLeft);}); + basic_layout->addWidget(align_left_btn_); + + align_center_btn_ = new QPushButton(); + align_center_btn_->setCheckable(true); + align_center_btn_->setIcon(icon::TextAlignCenter); + connect(align_center_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignCenter);}); + basic_layout->addWidget(align_center_btn_); + + align_right_btn_ = new QPushButton(); + align_right_btn_->setCheckable(true); + align_right_btn_->setIcon(icon::TextAlignRight); + connect(align_right_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignRight);}); + basic_layout->addWidget(align_right_btn_); + + align_justify_btn_ = new QPushButton(); + align_justify_btn_->setCheckable(true); + align_justify_btn_->setIcon(icon::TextAlignJustify); + connect(align_justify_btn_, &QPushButton::clicked, this, [this]{emit AlignmentChanged(Qt::AlignJustify);}); + basic_layout->addWidget(align_justify_btn_); + + basic_layout->addWidget(QtUtils::CreateVerticalLine()); + + color_btn_ = new QPushButton(); + color_btn_->setAutoFillBackground(true); + connect(color_btn_, &QPushButton::clicked, this, [this]{ + QColor c = color_btn_->property("color").value(); + + QColorDialog cd(c, this); + if (cd.exec() == QDialog::Accepted) { + c = cd.selectedColor(); + SetColor(c); + emit ColorChanged(c); + } + }); + basic_layout->addWidget(color_btn_); + + basic_layout->addStretch(); + + QHBoxLayout *advanced_layout = new QHBoxLayout(); + outer_layout->addLayout(advanced_layout); + + advanced_layout->addWidget(new QLabel(tr("Stretch: "))); // FIXME: Procure icon + + stretch_slider_ = new IntegerSlider(); + stretch_slider_->SetMinimum(0); + stretch_slider_->SetDefaultValue(100); + stretch_slider_->setFixedWidth(advanced_slider_width); + stretch_slider_->SetFormat(tr("%1%")); + connect(stretch_slider_, &IntegerSlider::ValueChanged, this, &ViewerTextEditorToolBar::StretchChanged); + advanced_layout->addWidget(stretch_slider_); + + advanced_layout->addWidget(new QLabel(tr("Kerning: "))); // FIXME: Procure icon + + kerning_slider_ = new FloatSlider(); + kerning_slider_->SetMinimum(0); + kerning_slider_->SetDefaultValue(100); + kerning_slider_->SetDecimalPlaces(1); + kerning_slider_->setFixedWidth(advanced_slider_width); + kerning_slider_->SetFormat(tr("%1%")); + connect(kerning_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::KerningChanged); + advanced_layout->addWidget(kerning_slider_); + + advanced_layout->addWidget(new QLabel(tr("Line Height: "))); // FIXME: Procure icon + + line_height_slider_ = new FloatSlider(); + line_height_slider_->SetMinimum(0); + line_height_slider_->SetDefaultValue(100); + line_height_slider_->SetDecimalPlaces(1); + line_height_slider_->setFixedWidth(advanced_slider_width); + line_height_slider_->SetFormat(tr("%1%")); + connect(line_height_slider_, &FloatSlider::ValueChanged, this, &ViewerTextEditorToolBar::LineHeightChanged); + advanced_layout->addWidget(line_height_slider_); + + small_caps_btn_ = new QPushButton(tr("Small Caps")); // FIXME: Procure icon + small_caps_btn_->setCheckable(true); + connect(small_caps_btn_, &QPushButton::clicked, this, &ViewerTextEditorToolBar::SmallCapsChanged); + advanced_layout->addWidget(small_caps_btn_); + + advanced_layout->addStretch(); + + setAutoFillBackground(true); +} + +void ViewerTextEditorToolBar::SetAlignment(Qt::Alignment a) +{ + align_left_btn_->setChecked(a == Qt::AlignLeft); + align_center_btn_->setChecked(a == Qt::AlignCenter); + align_right_btn_->setChecked(a == Qt::AlignRight); + align_justify_btn_->setChecked(a == Qt::AlignJustify); +} + +void ViewerTextEditorToolBar::SetColor(const QColor &c) +{ + color_btn_->setProperty("color", c); + color_btn_->setStyleSheet(QStringLiteral("QPushButton { background: %1; }").arg(c.name())); +} + +void ViewerTextEditorToolBar::closeEvent(QCloseEvent *event) +{ + event->ignore(); +} + +void ViewerTextEditorToolBar::paintEvent(QPaintEvent *event) +{ + if (!painted_) { + emit FirstPaint(); + painted_ = true; + } + QWidget::paintEvent(event); +} + +void ViewerTextEditorToolBar::UpdateFontStyleList(const QString &family) +{ + QString temp = style_combo_->currentText(); + + style_combo_->blockSignals(true); + style_combo_->clear(); + QStringList l = QFontDatabase().styles(family); + foreach (const QString &style, l) { + style_combo_->addItem(style); + } + style_combo_->setCurrentText(temp); + style_combo_->blockSignals(false); +} + +void ViewerTextEditorToolBar::mousePressEvent(QMouseEvent *event) +{ + QWidget::mousePressEvent(event); + + if (event->button() == Qt::LeftButton) { + drag_anchor_ = event->pos(); + } +} + +void ViewerTextEditorToolBar::mouseMoveEvent(QMouseEvent *event) +{ + QWidget::mouseMoveEvent(event); + + if (event->buttons() & Qt::LeftButton) { + this->move(mapToParent(QPoint(event->pos() - drag_anchor_))); + } +} + +void ViewerTextEditorToolBar::mouseReleaseEvent(QMouseEvent *event) +{ + QWidget::mouseReleaseEvent(event); +} + +} diff --git a/app/widget/viewer/viewertexteditor.h b/app/widget/viewer/viewertexteditor.h new file mode 100644 index 000000000..12a2e3ee2 --- /dev/null +++ b/app/widget/viewer/viewertexteditor.h @@ -0,0 +1,178 @@ +/*** + + Olive - Non-Linear Video Editor + Copyright (C) 2021 Olive Team + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +***/ + +#ifndef VIEWERTEXTEDITOR_H +#define VIEWERTEXTEDITOR_H + +#include +#include +#include +#include +#include + +#include "widget/slider/floatslider.h" +#include "widget/slider/integerslider.h" + +namespace olive { + +class ViewerTextEditorToolBar : public QWidget +{ + Q_OBJECT +public: + ViewerTextEditorToolBar(QWidget *parent = nullptr); + + QString GetFontFamily() const + { + return font_combo_->currentText(); + } + +public slots: + void SetFontFamily(QString s) + { + font_combo_->blockSignals(true); + font_combo_->setCurrentFont(s); + UpdateFontStyleList(s); + font_combo_->blockSignals(false); + } + + void SetStyle(QString style) + { + style_combo_->blockSignals(true); + style_combo_->setCurrentText(style); + style_combo_->blockSignals(false); + } + + void SetFontSize(double d) { font_sz_slider_->SetValue(d); } + void SetUnderline(bool e) { underline_btn_->setChecked(e); } + void SetStrikethrough(bool e) { strikethrough_btn_->setChecked(e); } + void SetAlignment(Qt::Alignment a); + void SetColor(const QColor &c); + void SetSmallCaps(bool e) { small_caps_btn_->setChecked(e); } + void SetStretch(int i) { stretch_slider_->SetValue(i); } + void SetKerning(qreal i) { kerning_slider_->SetValue(i); } + void SetLineHeight(qreal i) { line_height_slider_->SetValue(i); } + +signals: + void FamilyChanged(const QString &s); + void SizeChanged(double d); + void StyleChanged(const QString &s); + void UnderlineChanged(bool e); + void StrikethroughChanged(bool e); + void AlignmentChanged(Qt::Alignment alignment); + void ColorChanged(const QColor &c); + void SmallCapsChanged(bool e); + void StretchChanged(int i); + void KerningChanged(qreal i); + void LineHeightChanged(qreal i); + + void FirstPaint(); + +protected: + virtual void mousePressEvent(QMouseEvent *event) override; + + virtual void mouseMoveEvent(QMouseEvent *event) override; + + virtual void mouseReleaseEvent(QMouseEvent *event) override; + + virtual void closeEvent(QCloseEvent *event) override; + + virtual void paintEvent(QPaintEvent *event) override; + +private: + QPoint drag_anchor_; + + QFontComboBox *font_combo_; + + FloatSlider *font_sz_slider_; + + QComboBox *style_combo_; + + QPushButton *underline_btn_; + QPushButton *strikethrough_btn_; + + QPushButton *align_left_btn_; + QPushButton *align_center_btn_; + QPushButton *align_right_btn_; + QPushButton *align_justify_btn_; + + IntegerSlider *stretch_slider_; + FloatSlider *kerning_slider_; + FloatSlider *line_height_slider_; + QPushButton *small_caps_btn_; + + QPushButton *color_btn_; + + bool painted_; + +private slots: + void UpdateFontStyleList(const QString &family); + +}; + +class ViewerTextEditor : public QTextEdit +{ + Q_OBJECT +public: + ViewerTextEditor(double scale, QWidget *parent = nullptr); + + void ConnectToolBar(ViewerTextEditorToolBar *toolbar); + +protected: + virtual void keyPressEvent(QKeyEvent *event) override; + + virtual void paintEvent(QPaintEvent *event) override; + +private: + static void UpdateToolBar(ViewerTextEditorToolBar *toolbar, const QTextCharFormat &f, const QTextBlockFormat &b, Qt::Alignment alignment); + + QVector toolbars_; + + QImage dpi_force_; + + QTextDocument *transparent_clone_; + +private slots: + void FocusChanged(QWidget *old, QWidget *now); + + void FormatChanged(const QTextCharFormat &f); + + void SetFamily(const QString &s); + + void SetStyle(const QString &s); + + void SetFontStrikethrough(bool e); + + void SetSmallCaps(bool e); + + void SetFontStretch(int i); + + void SetFontKerning(qreal i); + + void SetLineHeight(qreal i); + + void LockScrollBarMaximumToZero(); + + void DocumentChanged(); + +}; + +} + +#endif // VIEWERTEXTEDITOR_H