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
This commit is contained in:
itsmattkc
2022-03-28 00:01:48 -07:00
parent 1c30c4d361
commit 16580841a6
61 changed files with 3257 additions and 1015 deletions
+1 -1
View File
@@ -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)
+3
View File
@@ -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
+469
View File
@@ -0,0 +1,469 @@
#include "html.h"
#include <QDebug>
#include <QTextBlock>
#include "xmlutils.h"
namespace olive {
const QVector<QString> 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<HtmlNode> &stack)
{
QTextCharFormat f;
for (int i=0; i<stack.size(); i++) {
f.merge(stack.at(i).format);
}
return f;
}
void Html::HtmlToDoc(QTextDocument *doc, const QString &html)
{
// Empty doc
doc->clear();
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("<html>").append(html).append("</html>");
QXmlStreamReader reader(wrapped);
QVector<HtmlNode> 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<QString, QStringList> Html::GetCSSFromStyle(const QString &s)
{
QMap<QString, QStringList> 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<val.size(); i++) {
const QChar &current_char = val.at(i);
if (!in_quote.isNull()) {
// If inside quotes and character isn't quote, indiscriminately append char
if (current_char == in_quote) {
in_quote = 0;
} else {
current_str.append(current_char);
}
} else if (current_char.isSpace() || current_char == QChar(',')) {
// Dump current
if (!current_str.isEmpty()) {
values.append(current_str);
current_str.clear();
}
} else if (in_quote.isNull() && (current_char == QChar('\'') || current_char == QChar('"'))) {
in_quote = current_char;
} else {
current_str.append(current_char);
}
}
if (!current_str.isEmpty()) {
values.append(current_str);
}
// Not sure if this will ever happen, but just in case, we will avoid assert failures with this
if (values.isEmpty()) {
values.append(QString());
}
map[kv.at(0).trimmed().toLower()] = values;
}
return map;
}
}
+60
View File
@@ -0,0 +1,60 @@
#ifndef HTML_H
#define HTML_H
#include <QTextDocument>
#include <QTextFragment>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>
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<QString, QStringList> GetCSSFromStyle(const QString &s);
static const QVector<QString> kBlockTags;
};
}
#endif // HTML_H
+7
View File
@@ -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);
+2
View File
@@ -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);
+30
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef UTIL_H
#define UTIL_H
template <typename T>
inline T mid(T a, T b)
{
return (a + b) * 0.5;
}
#endif // UTIL_H
+1
View File
@@ -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)
@@ -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<PolygonGizmo>();
gizmo_resize_handle_[0] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 0), NodeKeyframeTrackReference(NodeInput(this, kTopLeftInput), 1)});
gizmo_resize_handle_[1] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kTopRightInput), 1)});
gizmo_resize_handle_[2] = AddDraggableGizmo<PointGizmo>({NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 0), NodeKeyframeTrackReference(NodeInput(this, kBottomRightInput), 1)});
gizmo_resize_handle_[3] = AddDraggableGizmo<PointGizmo>({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<DraggableGizmo*>(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);
}
}
@@ -23,6 +23,8 @@
#include <QVector2D>
#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<NodeInputDragger> gizmo_dragger_;
QPointF gizmo_drag_start_;
QVector2D gizmo_res_;
PointGizmo *gizmo_resize_handle_[kGizmoCornerCount];
PolygonGizmo *gizmo_whole_rect_;
};
+41 -143
View File
@@ -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<PolygonGizmo>({kLeftInput, kTopInput, kRightInput, kBottomInput});
point_gizmo_[kGizmoScaleTopLeft] = AddDraggableGizmo<PointGizmo>({kLeftInput, kTopInput});
point_gizmo_[kGizmoScaleTopCenter] = AddDraggableGizmo<PointGizmo>({kTopInput});
point_gizmo_[kGizmoScaleTopRight] = AddDraggableGizmo<PointGizmo>({kRightInput, kTopInput});
point_gizmo_[kGizmoScaleBottomLeft] = AddDraggableGizmo<PointGizmo>({kLeftInput, kBottomInput});
point_gizmo_[kGizmoScaleBottomCenter] = AddDraggableGizmo<PointGizmo>({kBottomInput});
point_gizmo_[kGizmoScaleBottomRight] = AddDraggableGizmo<PointGizmo>({kRightInput, kBottomInput});
point_gizmo_[kGizmoScaleCenterLeft] = AddDraggableGizmo<PointGizmo>({kLeftInput});
point_gizmo_[kGizmoScaleCenterRight] = AddDraggableGizmo<PointGizmo>({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<DraggableGizmo*>(sender());
bool gizmo_active[kGizmoScaleCount] = {false};
QVector2D res = gizmo->GetGlobals().resolution();
x_diff /= res.x();
y_diff /= res.y();
for (int i=0; i<kGizmoScaleCount; i++) {
gizmo_active[i] = gizmo_resize_handle_[i].contains(p);
if (gizmo_active[i]) {
found_handle = true;
break;
for (int j=0; j<gizmo->GetDraggers().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)
+8 -27
View File
@@ -23,6 +23,8 @@
#include <QVector2D>
#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<NodeInputDragger> gizmo_dragger_;
QVector<QVariant> gizmo_start_;
QPointF gizmo_drag_start_;
QVector2D gizmo_res_;
PointGizmo *point_gizmo_[kGizmoScaleCount];
PolygonGizmo *poly_gizmo_;
};
@@ -41,6 +41,29 @@ TransformDistortNode::TransformDistortNode()
AddInput(kInterpolationInput, NodeValue::kCombo, 2);
PrependInput(kTextureInput, NodeValue::kTexture, InputFlags(kInputFlagNotKeyframable));
// Initiate gizmos
rotation_gizmo_ = AddDraggableGizmo<ScreenGizmo>();
rotation_gizmo_->AddInput(NodeInput(this, kRotationInput));
rotation_gizmo_->SetDragValueBehavior(ScreenGizmo::kAbsolute);
poly_gizmo_ = AddDraggableGizmo<PolygonGizmo>();
poly_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0));
poly_gizmo_->AddInput(NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1));
anchor_gizmo_ = AddDraggableGizmo<PointGizmo>();
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<kGizmoScaleCount; i++) {
point_gizmo_[i] = AddDraggableGizmo<PointGizmo>();
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<TexturePtr>();
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; i<kGizmoScaleCount; i++) {
gizmo_scale_active[i] = gizmo_resize_handle_[i].contains(p);
if (gizmo_scale_active[i]) {
scaling = true;
break;
}
}
if (scaling) {
// Dragging scale handle
gizmo_start_ = {row[kScaleInput].data()};
gizmo_drag_ = kScaleInput;
gizmo_scale_uniform_ = row[kUniformScaleInput].data().toBool();
if (gizmo_scale_active[kGizmoScaleTopLeft] || gizmo_scale_active[kGizmoScaleTopRight]
|| gizmo_scale_active[kGizmoScaleBottomLeft] || gizmo_scale_active[kGizmoScaleBottomRight]) {
gizmo_scale_axes_ = kGizmoScaleBoth;
} else if (gizmo_scale_active[kGizmoScaleCenterLeft] || gizmo_scale_active[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<QVector2D>() + 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<QVector2D>() + inverted_movement;
QVector2D position_cont = gizmo_start_[1].value<QVector2D>() + 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<QVector2D>() + 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<DraggableGizmo*>(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<TexturePtr>();
if (!tex) {
return;
}
gizmo_scale_uniform_ = row[kUniformScaleInput].data().toBool();
gizmo_anchor_pt_ = (row[kAnchorInput].data().value<QVector2D>() + 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<QVector2D>() + 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<QVector2D>() + 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<DraggableGizmo*>(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<AutoScaleType>(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<TexturePtr>();
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<AutoScaleType>(value[kAutoscaleInput].data().toInt());
return AdjustMatrixByResolutions(generated_matrix,
sequence_res,
texture_res,
autoscale);
}
bool TransformDistortNode::IsAScaleGizmo(NodeGizmo *g) const
{
for (int i=0; i<kGizmoScaleCount; i++) {
if (point_gizmo_[i] == g) {
return true;
}
}
return false;
}
TransformDistortNode::RotationDirection TransformDistortNode::GetDirectionFromAngles(double last, double current)
{
return (current > last) ? kDirectionPositive : kDirectionNegative;
}
}
@@ -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<QVariant> gizmo_start_;
QVector<NodeInputDragger> 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_;
};
+11 -7
View File
@@ -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:
+3 -2
View File
@@ -48,8 +48,9 @@ public:
kSolidGenerator,
kMerge,
kStrokeFilter,
kTextGeneratorLegacy,
kTextGenerator,
kTextGeneratorV1,
kTextGeneratorV2,
kTextGeneratorV3,
kCrossDissolveTransition,
kDipToColorTransition,
kMosaicFilter,
+70 -113
View File
@@ -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<typename T>
void PolygonGenerator::ValidateGizmoVectorSize(QVector<T*> &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<NodeValue> points = row[kPointsInput].data().value< QVector<NodeValue> >();
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<QLineF> lines(points.size() * 2);
for (int i=0; i<points.size(); i++) {
const Bezier &pt = points.at(i).data().value<Bezier>();
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<gizmo_position_handles_.size(); i++) {
if (gizmo_position_handles_.at(i).contains(adjusted)) {
gizmo_x_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 0));
gizmo_y_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 1));
break;
}
}
// Next, if no main points were found, look for beziers
if (gizmo_x_active_.isEmpty() && gizmo_y_active_.isEmpty()) {
for (int i=0; i<gizmo_bezier_handles_.size(); i++) {
if (gizmo_bezier_handles_.at(i).contains(adjusted)) {
int start = (i%2 == 0) ? 2 : 4;
int element = i/2;
gizmo_x_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, element), start + 0));
gizmo_y_active_.append(NodeKeyframeTrackReference(NodeInput(this, kPointsInput, element), start + 1));
break;
if (old_sz != new_sz) {
if (old_sz > new_sz) {
for (int i=new_sz; i<old_sz; i++) {
delete vec.at(i);
}
}
// Finally, see if the cursor is inside the polygon
if (gizmo_x_active_.isEmpty() && gizmo_y_active_.isEmpty()) {
if (gizmo_polygon_path_.contains(adjusted)) {
gizmo_x_active_.resize(gizmo_position_handles_.size());
gizmo_y_active_.resize(gizmo_position_handles_.size());
for (int i=0; i<gizmo_position_handles_.size(); i++) {
gizmo_x_active_[i] = NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 0);
gizmo_y_active_[i] = NodeKeyframeTrackReference(NodeInput(this, kPointsInput, i), 1);
vec.resize(new_sz);
if (old_sz < new_sz) {
for (int i=old_sz; i<new_sz; i++) {
if constexpr(std::is_same_v<T, PointGizmo>) {
vec[i] = AddDraggableGizmo<T>();
} 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<gizmo_x_active_.size(); i++) {
gizmo_x_draggers_[i].Start(gizmo_x_active_.at(i), time);
}
for (int i=0; i<gizmo_y_active_.size(); i++) {
gizmo_y_draggers_[i].Start(gizmo_y_active_.at(i), time);
QPointF half_res(globals.resolution_by_par().x()/2, globals.resolution_by_par().y()/2);
QVector<NodeValue> points = row[kPointsInput].data().value< QVector<NodeValue> >();
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; i<gizmo_position_handles_.size(); i++) {
gizmo_position_handles_.at(i)->AddInput(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<points.size(); i++) {
const Bezier &pt = points.at(i).data().value<Bezier>();
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<DraggableGizmo*>(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)
+13 -14
View File
@@ -24,6 +24,9 @@
#include <QPainterPath>
#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<NodeValue> &points);
QPainterPath gizmo_polygon_path_;
QVector<QRectF> gizmo_position_handles_;
QVector<QRectF> gizmo_bezier_handles_;
template<typename T>
void ValidateGizmoVectorSize(QVector<T*> &vec, int new_sz);
QVector<NodeKeyframeTrackReference> gizmo_x_active_;
QVector<NodeKeyframeTrackReference> gizmo_y_active_;
QVector<NodeInputDragger> gizmo_x_draggers_;
QVector<NodeInputDragger> gizmo_y_draggers_;
QPointF gizmo_drag_start_;
PathGizmo *poly_gizmo_;
QVector<PointGizmo*> gizmo_position_handles_;
QVector<PointGizmo*> gizmo_bezier_handles_;
QVector<LineGizmo*> gizmo_bezier_lines_;
};
+148 -147
View File
@@ -23,7 +23,7 @@
#include <QtMath>
#include <QVector2D>
#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<NodeKeyframeTrackReference> 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<PolygonGizmo>({
NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 0),
NodeKeyframeTrackReference(NodeInput(this, kPositionInput), 1),
});
for (int i=0; i<kGizmoScaleCount; i++) {
point_gizmo_[i] = AddDraggableGizmo<PointGizmo>(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>();
QVector2D sz = row[kSizeInput].data().value<QVector2D>();
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<DraggableGizmo*>(sender());
// See if any of our resize handles have the pointer
for (int i=0; i<kGizmoScaleCount; i++) {
if (gizmo_resize_handle_[i].contains(p)) {
gizmo_drag_ = i;
break;
}
}
NodeInputDragger &x_drag = gizmo->GetDraggers()[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<QVector2D>();
gizmo_sz_start_ = row[kSizeInput].data().value<QVector2D>();
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];
}
}
+21 -19
View File
@@ -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<NodeInputDragger> 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_;
};
+6 -4
View File
@@ -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
)
@@ -18,7 +18,7 @@
***/
#include "textlegacy.h"
#include "textv1.h"
#include <QAbstractTextDocumentLayout>
#include <QTextDocument>
@@ -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<Node::CategoryID> TextGeneratorLegacy::Category() const
QVector<Node::CategoryID> 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
@@ -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
@@ -18,7 +18,7 @@
***/
#include "text.h"
#include "textv2.h"
#include <QAbstractTextDocumentLayout>
#include <QDateTime>
@@ -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<Node::CategoryID> TextGenerator::Category() const
QVector<Node::CategoryID> 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
@@ -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
+140
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "textv3.h"
#include <QAbstractTextDocumentLayout>
#include <QDateTime>
#include <QTextDocument>
#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("<p style='font-size: 72pt; color: white;'>%1</p>").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<Node::CategoryID> 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<uchar*>(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<QVector2D>();
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<QVector2D>();
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());
}
}
+60
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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<CategoryID> 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
+36
View File
@@ -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 <http://www.gnu.org/licenses/>.
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
)
+52
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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<draggers_.size(); i++) {
draggers_[i].Start(inputs_[i], time);
}
emit HandleStart(row, abs_x, abs_y, time);
}
void DraggableGizmo::DragMove(double x, double y, const Qt::KeyboardModifiers &modifiers)
{
emit HandleMovement(x, y, modifiers);
}
void DraggableGizmo::DragEnd(MultiUndoCommand *command)
{
for (int i=0; i<draggers_.size(); i++) {
draggers_[i].End(command);
}
}
}
+85
View File
@@ -0,0 +1,85 @@
/***
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 <http://www.gnu.org/licenses/>.
***/
#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<NodeInputDragger> &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<NodeKeyframeTrackReference> inputs_;
QVector<NodeInputDragger> draggers_;
DragValueBehavior drag_value_behavior_;
};
}
#endif // DRAGGABLEGIZMO_H
+30
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "gizmo.h"
namespace olive {
NodeGizmo::NodeGizmo(QObject *parent)
{
setParent(parent);
}
}
+51
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef NODEGIZMO_H
#define NODEGIZMO_H
#include <QObject>
#include <QPainter>
#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
+38
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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_);
}
}
+48
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef LINEGIZMO_H
#define LINEGIZMO_H
#include <QLineF>
#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
+39
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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_);
}
}
+48
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef PATHGIZMO_H
#define PATHGIZMO_H
#include <QPainterPath>
#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
+99
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "point.h"
#include <QApplication>
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);
}
}
+72
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef POINTGIZMO_H
#define POINTGIZMO_H
#include <QPointF>
#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
+38
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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_);
}
}
+48
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef POLYGONGIZMO_H
#define POLYGONGIZMO_H
#include <QPolygonF>
#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
+31
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "screen.h"
namespace olive {
ScreenGizmo::ScreenGizmo(QObject *parent)
: DraggableGizmo{parent}
{
}
}
+38
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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
+43
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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);
}
}
}
+56
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#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
+2
View File
@@ -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),
+10
View File
@@ -51,6 +51,16 @@ public:
return start_value_;
}
const NodeKeyframeTrackReference &GetInput() const
{
return input_;
}
const rational &GetTime() const
{
return time_;
}
private:
NodeKeyframeTrackReference input_;
+45 -58
View File
@@ -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; i<count; i++) {
QRectF& r = rects[i];
// Draw rect on screen
p->drawRect(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<NodeKeyframe*>(event->child());
if (key) {
if (NodeKeyframe* key = dynamic_cast<NodeKeyframe*>(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<NodeGizmo*>(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; i<nb_tracks; i++) {
QVariant track_value;
if (i == track) {
track_value = value;
} else if (!insert_on_all_tracks_if_no_key) {
continue;
} else {
track_value = input.node()->GetSplitValueAtTimeOnTrack(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();
+42 -11
View File
@@ -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<NodeGizmo*> &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<typename T>
T *AddDraggableGizmo(const QVector<NodeKeyframeTrackReference> &inputs = QVector<NodeKeyframeTrackReference>(), 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<typename T>
T *AddDraggableGizmo(const QStringList &inputs, DraggableGizmo::DragValueBehavior behavior = DraggableGizmo::kDeltaFromStart)
{
QVector<NodeKeyframeTrackReference> refs(inputs.size());
for (int i=0; i<refs.size(); i++) {
refs[i] = NodeInput(this, inputs[i]);
}
return AddDraggableGizmo<T>(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<NodeGizmo*> gizmos_;
private slots:
/**
* @brief Slot when a keyframe's time changes to keep the keyframes correctly sorted by time
+2
View File
@@ -25,6 +25,8 @@
#include <QVariant>
#include <QVector>
#include "undo/undocommand.h"
namespace olive {
class Node;
+5
View File
@@ -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_;
};
}
+9
View File
@@ -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<ColorManager>(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;
}
+15
View File
@@ -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");
+9
View File
@@ -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;
@@ -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; i<nb_tracks; i++) {
QVariant track_value;
if (i == track) {
track_value = value;
} else if (!insert_on_all_tracks_if_no_key) {
continue;
} else {
track_value = GetInnerInput().node()->GetSplitValueAtTimeOnTrack(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)
+1
View File
@@ -57,6 +57,7 @@ SliderBase::SliderBase(QWidget *parent) :
void SliderBase::SetAlignment(Qt::Alignment alignment)
{
label_->setAlignment(alignment);
editor_->setAlignment(alignment);
}
bool SliderBase::IsTristate() const
+2 -2
View File
@@ -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:
+2
View File
@@ -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
+158 -17
View File
@@ -21,6 +21,8 @@
#include "viewerdisplay.h"
#include <OpenImageIO/imagebuf.h>
#include <QAbstractTextDocumentLayout>
#include <QApplication>
#include <QFileInfo>
#include <QMessageBox>
#include <QMouseEvent>
@@ -28,12 +30,23 @@
#include <QOpenGLFunctions>
#include <QOpenGLTexture>
#include <QPainter>
#include <QPushButton>
#include <QScreen>
#include <QTextEdit>
#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<DraggableGizmo*>(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<DraggableGizmo*>(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<TextGizmo*>(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<quintptr>(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<PointGizmo*>(gizmo)) {
if (point->GetClickingRect(GenerateGizmoTransform()).contains(p)) {
return point;
}
} else if (PolygonGizmo *poly = dynamic_cast<PolygonGizmo*>(gizmo)) {
if (poly->GetPolygon().containsPoint(p, Qt::OddEvenFill)) {
return poly;
}
} else if (PathGizmo *path = dynamic_cast<PathGizmo*>(gizmo)) {
if (path->GetPath().contains(p)) {
return path;
}
} else if (ScreenGizmo *screen = dynamic_cast<ScreenGizmo*>(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<ViewerTextEditor *>(sender());
TextGizmo *gizmo = reinterpret_cast<TextGizmo*>(editor->property("gizmo").value<quintptr>());
QString html = Html::DocToHtml(editor->document());
gizmo->UpdateInputHtml(html, GetGizmoTime());
}
}
+10 -3
View File
@@ -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();
};
}
+483
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#include "viewertexteditor.h"
#include <QAbstractTextDocumentLayout>
#include <QColorDialog>
#include <QKeyEvent>
#include <QHBoxLayout>
#include <QScrollBar>
#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<ViewerTextEditorToolBar*>(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<ViewerTextEditorToolBar*>(test)
|| dynamic_cast<SliderLadder*>(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<QScrollBar*>(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<QColor>();
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);
}
}
+178
View File
@@ -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 <http://www.gnu.org/licenses/>.
***/
#ifndef VIEWERTEXTEDITOR_H
#define VIEWERTEXTEDITOR_H
#include <QApplication>
#include <QDebug>
#include <QFontComboBox>
#include <QTextEdit>
#include <QPushButton>
#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<ViewerTextEditorToolBar *> 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