--User changes

-Add a palette editor.
 -Add support for reading .ugr/.gradient/.gradients palette files.
 -Allow toggling on spinners whose minimum value is not zero.
 -Allow toggling display of image, affines and grid.
 -Add new variations: cylinder2, circlesplit, tile_log, truchet_fill, waves2_radial.

--Bug fixes
 -cpow2 was wrong.
 -Palettes with rapid changes in color would produce slightly different outputs from Apo/Chaotica. This was due to a long standing bug from flam3.
 -Use exec() on Apple and show() on all other OSes for dialog boxes.
 -Trying to render a sequence with no frames would crash.
 -Selecting multiple xforms and rotating them would produce the wrong rotation.
 -Better handling when parsing flames using different encoding, such as unicode and UTF-8.
 -Switching between SP/DP didn't reselect the selected flame in the Library tab.

--Code changes
 -Make all types concerning palettes be floats, including PaletteTableWidgetItem.
 -PaletteTableWidgetItem is no longer templated because all palettes are float.
 -Include the source colors for user created gradients.
 -Change parallel_for() calls to work with very old versions of TBB which are lingering on some systems.
 -Split conditional out of accumulation loop on the CPU for better performance.
 -Vectorize summing when doing density filter for better performance.
 -Make all usage of palettes be of type float, double is pointless.
 -Allow palettes to reside in multiple folders, while ensuring only one of each name is added.
 -Refactor some palette path searching code.
 -Make ReadFile() throw and catch an exception if the file operation fails.
 -A little extra safety in foci and foci3D with a call to Zeps().
 -Cast to (real_t) in the OpenCL string for the w variation, which was having trouble compiling on Mac.
 -Fixing missing comma between paths in InitPaletteList().
 -Move Xml and PaletteList classes into cpp to shorten build times when working on them.
 -Remove default param values for IterOpenCLKernelCreator<T>::SharedDataIndexDefines in cpp file.
 -Change more NULL to nullptr.
This commit is contained in:
Person
2017-02-26 00:02:21 -08:00
parent 8a75d5d227
commit 8a4127d5d7
102 changed files with 242668 additions and 3713 deletions

View File

@ -58,7 +58,7 @@
<enum>QFrame::NoFrame</enum>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;&lt;br/&gt;Fractorium 1.0.0.2&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://fractorium.com&quot;&gt;fractorium.com&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;Lead: Matt Feemster&lt;br/&gt;Contributors: Simon Detheridge&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p align=&quot;center&quot;&gt;Fractorium 1.0.0.2&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;A Qt-based fractal flame editor which uses a C++ re-write of the flam3 algorithm named Ember and a GPU capable version named EmberCL which implements a portion of the cuburn algorithm in OpenCL.&lt;/span&gt;&lt;/p&gt;&lt;p align=&quot;center&quot;&gt;&lt;a href=&quot;http://fractorium.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;fractorium.com&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-size:10pt;&quot;&gt;&lt;br/&gt;Lead: Matt Feemster&lt;br/&gt;Contributors: Simon Detheridge, Michel Mastriani&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@ -86,9 +86,9 @@
<property name="geometry">
<rect>
<x>5</x>
<y>180</y>
<y>170</y>
<width>585</width>
<height>483</height>
<height>500</height>
</rect>
</property>
<layout class="QVBoxLayout" name="AboutVerticalLayout">
@ -128,7 +128,7 @@
<enum>QFrame::NoFrame</enum>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/scottdraves/flam3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;flam3&lt;/span&gt;&lt;/a&gt;: Scott Draves, Erik Reckase (GPL v2)&lt;br/&gt;&lt;a href=&quot;http://github.com/stevenrobertson/cuburn&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;cuburn&lt;/span&gt;&lt;/a&gt;: Steven Robertson, Michael Semeniuk, Matthew Znoj, Nicolas Mejia (GPL v3)&lt;br/&gt;&lt;a href=&quot;http://fractron9000.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Fractron 9000&lt;/span&gt;&lt;/a&gt;: Mike Thiesen (GPL)&lt;br/&gt;&lt;a href=&quot;http://sourceforge.net/projects/apophysis7x&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Apophysis&lt;/span&gt;&lt;/a&gt;: Mark Townsend, Ronald Hordijk, Peter Sdobnov, Piotr Borys, Georg Kiehne (GPL)&lt;br/&gt;&lt;a href=&quot;http://jwildfire.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;JWildfire&lt;/span&gt;&lt;/a&gt;: Andreas Maschke (LGPL)&lt;br/&gt;Numerous Apophysis plugin developers (GPL)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;https://github.com/scottdraves/flam3&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;flam3&lt;/span&gt;&lt;/a&gt;: Scott Draves, Erik Reckase (GPL v2)&lt;br/&gt;&lt;a href=&quot;http://github.com/stevenrobertson/cuburn&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;cuburn&lt;/span&gt;&lt;/a&gt;: Steven Robertson, Michael Semeniuk, Matthew Znoj, Nicolas Mejia (GPL v3)&lt;br/&gt;&lt;a href=&quot;http://fractron9000.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Fractron 9000&lt;/span&gt;&lt;/a&gt;: Mike Thiesen (GPL)&lt;br/&gt;&lt;a href=&quot;http://sourceforge.net/projects/apophysis7x&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Apophysis&lt;/span&gt;&lt;/a&gt;: Mark Townsend, Ronald Hordijk, Peter Sdobnov, Piotr Borys, Georg Kiehne (GPL)&lt;br/&gt;&lt;a href=&quot;http://jwildfire.org/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;JWildfire&lt;/span&gt;&lt;/a&gt;: Andreas Maschke (LGPL)&lt;br/&gt;gradLib: Stian Broen&lt;br/&gt;ColorPickerWidget: Etienne Moutot&lt;br/&gt;Numerous Apophysis plugin developers (GPL)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@ -211,7 +211,7 @@
</sizepolicy>
</property>
<property name="title">
<string>Icons Used</string>
<string>Icons and Palettes Used</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_3">
<property name="topMargin">
@ -232,7 +232,7 @@
<enum>QFrame::NoFrame</enum>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;a href=&quot;http://famfamfam.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Silk&lt;/span&gt;&lt;/a&gt;: Mark James (Creative Commons Attribution 2.5 License)&lt;br/&gt;&lt;a href=&quot;http://momentumdesignlab.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Momentum&lt;/span&gt;&lt;/a&gt;: Momentum Design Lab (Creative Commons Attribution-ShareAlike 3.5 License)&lt;br/&gt;&lt;a href=&quot;http://everaldo.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Crystal Clear&lt;/span&gt;&lt;/a&gt;: Everaldo Coelho (LGPL)&lt;br/&gt;&lt;a href=&quot;http://openiconlibrary.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Open Icon Library&lt;/span&gt;&lt;/a&gt;: Jeff Israel (GPL, LGPL, Creative Commons, Public Domain)&lt;br/&gt;&lt;a href=&quot;http://icons.mysitemyway.com/category/3d-transparent-glass-icons/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;3D Transparent Glass&lt;/span&gt;&lt;/a&gt;: iconsETC (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://p.yusukekamiyamane.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Fugue&lt;/span&gt;&lt;/a&gt;: Yusuke Kamiyamane (Creative Commons Attribution 3.0 License)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Palettes&lt;/span&gt;: Boxtail, FarDareisMai, FractalDesire, Rce, Tatasz (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://famfamfam.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Silk&lt;/span&gt;&lt;/a&gt;: Mark James (Creative Commons Attribution 2.5 License)&lt;br/&gt;&lt;a href=&quot;http://momentumdesignlab.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Momentum&lt;/span&gt;&lt;/a&gt;: Momentum Design Lab (Creative Commons Attribution-ShareAlike 3.5 License)&lt;br/&gt;&lt;a href=&quot;http://everaldo.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Crystal Clear&lt;/span&gt;&lt;/a&gt;: Everaldo Coelho (LGPL)&lt;br/&gt;&lt;a href=&quot;http://openiconlibrary.sourceforge.net&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Open Icon Library&lt;/span&gt;&lt;/a&gt;: Jeff Israel (GPL, LGPL, Creative Commons, Public Domain)&lt;br/&gt;&lt;a href=&quot;http://icons.mysitemyway.com/category/3d-transparent-glass-icons/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;3D Transparent Glass&lt;/span&gt;&lt;/a&gt;: iconsETC (Public Domain)&lt;br/&gt;&lt;a href=&quot;http://p.yusukekamiyamane.com&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Fugue&lt;/span&gt;&lt;/a&gt;: Yusuke Kamiyamane (Creative Commons Attribution 3.0 License)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
@ -253,7 +253,7 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="">
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>5</x>

View File

@ -32,7 +32,7 @@ class CurvesGraphicsView : public QGraphicsView
Q_OBJECT
public:
CurvesGraphicsView(QWidget* parent = 0);
CurvesGraphicsView(QWidget* parent = nullptr);
void PointChanged(int curveIndex, int pointIndex, const QPointF& point);
QPointF Get(int curveIndex, int pointIndex);
@ -82,7 +82,7 @@ public:
/// <param name="pointIndex">The point index within the curve</param>
/// <param name="viewParent">The graphics view this point is displayed on</param>
/// <param name="p">The parent widget of this item</param>
EllipseItem(const QRectF& rect, int curveIndex, int pointIndex, CurvesGraphicsView* viewParent, QGraphicsItem* parent = 0)
EllipseItem(const QRectF& rect, int curveIndex, int pointIndex, CurvesGraphicsView* viewParent, QGraphicsItem* parent = nullptr)
: QGraphicsEllipseItem(rect, parent)
{
setFlag(QGraphicsItem::ItemSendsScenePositionChanges);

View File

@ -1,6 +1,5 @@
#include "FractoriumPch.h"
#include "DoubleSpinBox.h"
#include "FractoriumSettings.h"
QTimer DoubleSpinBox::s_Timer;
@ -17,10 +16,12 @@ DoubleSpinBox::DoubleSpinBox(QWidget* p, int h, double step)
: QDoubleSpinBox(p)
{
m_DoubleClick = false;
m_DoubleClickLowVal = 0;
m_DoubleClickNonZero = 0;
m_DoubleClickZero = 1;
m_Step = step;
m_SmallStep = step / 10.0;
m_Settings = FractoriumSettings::DefInstance();
setSingleStep(step);
setFrame(false);
setButtonSymbols(QAbstractSpinBox::NoButtons);
@ -53,6 +54,16 @@ void DoubleSpinBox::DoubleClick(bool b)
m_DoubleClick = b;
}
/// <summary>
/// Set the value to be used instead of zero to represent the lower value
/// used when responding to a double click.
/// </summary>
/// <param name="val">The value to be used for the lower value instead of zero</param>
void DoubleSpinBox::DoubleClickLowVal(double val)
{
m_DoubleClickLowVal = val;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains zero.
@ -120,7 +131,7 @@ QLineEdit* DoubleSpinBox::lineEdit()
/// <summary>
/// Another workaround for the persistent text selection bug in Qt.
/// </summary>
void DoubleSpinBox::OnSpinBoxValueChanged(double d)
void DoubleSpinBox::OnSpinBoxValueChanged(double)
{
lineEdit()->deselect();//Gets rid of nasty "feature" that always has text selected.
}
@ -159,35 +170,34 @@ void DoubleSpinBox::OnTimeout()
bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e)
{
QMouseEvent* me = dynamic_cast<QMouseEvent*>(e);
auto settings = FractoriumSettings::DefInstance();
if (isEnabled() && me)
{
if (!settings->ToggleType() &&//Ensure double click toggles, not right click.
if (!m_Settings->ToggleType() &&//Ensure double click toggles, not right click.
me->type() == QMouseEvent::MouseButtonPress &&
me->button() == Qt::RightButton)
{
m_MouseDownPoint = m_MouseMovePoint = me->pos();
StartTimer();
}
else if (!settings->ToggleType() &&
else if (!m_Settings->ToggleType() &&
me->type() == QMouseEvent::MouseButtonRelease &&
me->button() == Qt::RightButton)
{
StopTimer();
m_MouseDownPoint = m_MouseMovePoint = me->pos();
}
else if (!settings->ToggleType() &&
else if (!m_Settings->ToggleType() &&
me->type() == QMouseEvent::MouseMove &&
QGuiApplication::mouseButtons() & Qt::RightButton)
{
m_MouseMovePoint = me->pos();
}
else if (m_DoubleClick &&
((!settings->ToggleType() && e->type() == QMouseEvent::MouseButtonDblClick && me->button() == Qt::LeftButton) ||
(settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton)))
((!m_Settings->ToggleType() && e->type() == QMouseEvent::MouseButtonDblClick && me->button() == Qt::LeftButton) ||
(m_Settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton)))
{
if (IsNearZero(value()))
if (IsClose(m_DoubleClickLowVal, value()))
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);
@ -276,4 +286,4 @@ void DoubleSpinBox::StopTimer()
{
s_Timer.stop();
disconnect(&s_Timer, SIGNAL(timeout()), this, SLOT(OnTimeout()));
}
}

View File

@ -1,6 +1,7 @@
#pragma once
#include "FractoriumPch.h"
#include "FractoriumSettings.h"
/// <summary>
/// DoubleSpinBox and VariationTreeDoubleSpinBox classes.
@ -22,6 +23,7 @@ public:
virtual ~DoubleSpinBox() { }
void SetValueStealth(double d);
void DoubleClick(bool b);
void DoubleClickLowVal(double val);
void DoubleClickZero(double val);
void DoubleClickNonZero(double val);
double Step();
@ -46,12 +48,14 @@ private:
void StopTimer();
bool m_DoubleClick;
double m_DoubleClickLowVal;
double m_DoubleClickNonZero;
double m_DoubleClickZero;
double m_Step;
double m_SmallStep;
QPoint m_MouseDownPoint;
QPoint m_MouseMovePoint;
shared_ptr<FractoriumSettings> m_Settings;
static QTimer s_Timer;
};

View File

@ -31,8 +31,8 @@ public:
/// The re-parenting is done so that the DoubleSpinBox appears directly on top of the cell.
/// </summary>
/// <param name="parent">The parent cell</param>
/// <param name="option">Unused</param>
/// <param name="index">unused</param>
/// <param name="option">Ignored</param>
/// <param name="index">Ignored</param>
/// <returns>The DoubleSpinBox member</returns>
virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override
{
@ -44,8 +44,8 @@ public:
/// <summary>
/// Prevent DoubleSpinBox control from being destroyed when the cell loses focus.
/// </summary>
/// <param name="editor">Unused</param>
/// <param name="index">Unused</param>
/// <param name="editor">Ignored</param>
/// <param name="index">Ignored</param>
virtual void destroyEditor(QWidget* editor, const QModelIndex& index) const override
{
}
@ -53,8 +53,8 @@ public:
/// <summary>
/// Set the value of the DoubleSpinBox as well as its tableindex property.
/// </summary>
/// <param name="editor">Unused</param>
/// <param name="index">Unused</param>
/// <param name="editor">Ignored</param>
/// <param name="index">Ignored</param>
virtual void setEditorData(QWidget* editor, const QModelIndex& index) const override
{
QPoint p(index.row(), index.column());
@ -67,7 +67,7 @@ public:
/// <summary>
/// Set the cell in the model to the value of the DoubleSpinBox.
/// </summary>
/// <param name="editor">Unused</param>
/// <param name="editor">Ignored</param>
/// <param name="model">The model whose value will be set</param>
/// <param name="index">The cell index of the model</param>
virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override
@ -80,7 +80,7 @@ public:
/// </summary>
/// <param name="editor">The DoubleSpinBox member</param>
/// <param name="option">Contains the rectangle to be used for the geometry of the DoubleSpinBox</param>
/// <param name="index">Unused</param>
/// <param name="index">Ignored</param>
virtual void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override
{
editor->setGeometry(option.rect);

View File

@ -196,7 +196,11 @@ FractoriumFinalRenderDialog::FractoriumFinalRenderDialog(QWidget* p, Qt::WindowF
void FractoriumFinalRenderDialog::Show(bool fromSequence)
{
m_FromSequence = fromSequence;
#ifdef __APPLE__
exec();
#else
show();
#endif
}
/// <summary>

View File

@ -186,6 +186,19 @@ Fractorium::Fractorium(QWidget* p)
}
}
#ifdef __APPLE__
for (auto dock : m_Docks)//Fixes focus problem on OSX.
{
if (!dock->isHidden())
{
dock->setFloating(!dock->isFloating());
dock->setFloating(!dock->isFloating());
break;
}
}
#endif
//At this point, everything has been setup except the renderer. Shortly after
//this constructor exits, GLWidget::InitGL() will create the initial flock and start the rendering timer
//which executes whenever the program is idle. Upon starting the timer, the renderer
@ -201,6 +214,8 @@ Fractorium::~Fractorium()
{
SyncSequenceSettings();
m_VarDialog->SyncSettings();
m_Settings->ShowXforms(ui.ActionDrawXforms->isChecked());
m_Settings->ShowGrid(ui.ActionDrawGrid->isChecked());
m_Settings->setValue("windowState", saveState());
m_Settings->sync();
}
@ -497,16 +512,13 @@ QStringList Fractorium::SetupOpenXmlDialog()
m_FileDialog->setViewMode(QFileDialog::List);
}
if (!m_FileDialog)
return QStringList("");
QStringList filenames;
m_FileDialog->disconnect(SIGNAL(filterSelected(const QString&)));
connect(m_FileDialog, &QFileDialog::filterSelected, [&](const QString & filter) { m_Settings->OpenXmlExt(filter); });
m_FileDialog->setFileMode(QFileDialog::ExistingFiles);
m_FileDialog->setAcceptMode(QFileDialog::AcceptOpen);
m_FileDialog->setNameFilter("Flam3 (*.flam3);;Flame (*.flame);;Xml (*.xml)");
m_FileDialog->setWindowTitle("Open flame");
m_FileDialog->setWindowTitle("Open Flame");
m_FileDialog->setDirectory(m_Settings->OpenFolder());
m_FileDialog->selectNameFilter(m_Settings->OpenXmlExt());
@ -709,15 +721,20 @@ void Fractorium::SetTabOrders()
w = SetTabOrder(this, w, ui.SequenceRandomizeStaggerCheckBox);
w = SetTabOrder(this, w, ui.SequenceStaggerSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomStaggerMaxSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomizeRotationsCheckBox);
w = SetTabOrder(this, w, ui.SequenceRotationsSpinBox);
w = SetTabOrder(this, w, ui.SequenceRotationsCWCheckBox);
w = SetTabOrder(this, w, ui.SequenceRandomRotationsMaxSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomizeFramesPerRotCheckBox);
w = SetTabOrder(this, w, ui.SequenceFramesPerRotSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomFramesPerRotMaxSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomizeRotationsCheckBox);
w = SetTabOrder(this, w, ui.SequenceRotationsSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomRotationsMaxSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomizeBlendFramesCheckBox);
w = SetTabOrder(this, w, ui.SequenceBlendFramesSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomBlendMaxFramesSpinBox);
w = SetTabOrder(this, w, ui.SequenceRandomizeRotationsPerBlendCheckBox);
w = SetTabOrder(this, w, ui.SequenceRotationsPerBlendSpinBox);
w = SetTabOrder(this, w, ui.SequenceRotationsPerBlendCWCheckBox);
w = SetTabOrder(this, w, ui.SequenceRotationsPerBlendMaxSpinBox);
w = SetTabOrder(this, w, ui.SequenceGenerateButton);
w = SetTabOrder(this, w, ui.SequenceRenderButton);
w = SetTabOrder(this, w, ui.SequenceSaveButton);
@ -738,7 +755,6 @@ void Fractorium::SetTabOrders()
w = SetTabOrder(this, w, m_XformOpacitySpin);
w = SetTabOrder(this, w, m_XformDirectColorSpin);
w = SetTabOrder(this, w, ui.SoloXformCheckBox);
w = SetTabOrder(this, ui.LockAffineCheckBox, ui.PreAffineGroupBox);//Xforms affine.
w = SetTabOrder(this, w, m_PreX1Spin);
w = SetTabOrder(this, w, m_PreX2Spin);
w = SetTabOrder(this, w, m_PreY1Spin);
@ -802,8 +818,9 @@ void Fractorium::SetTabOrders()
w = SetTabOrder(this, w, m_PaletteBlurSpin);
w = SetTabOrder(this, w, m_PaletteBrightnessSpin);
w = SetTabOrder(this, w, m_PaletteFrequencySpin);
w = SetTabOrder(this, w, ui.PaletteRandomSelect);
w = SetTabOrder(this, w, ui.PaletteRandomAdjust);
w = SetTabOrder(this, w, ui.PaletteRandomSelectButton);
w = SetTabOrder(this, w, ui.PaletteRandomAdjustButton);
w = SetTabOrder(this, w, ui.PaletteEditorButton);
w = SetTabOrder(this, w, ui.PaletteFilterLineEdit);
w = SetTabOrder(this, w, ui.PaletteFilterClearButton);
w = SetTabOrder(this, w, ui.PaletteListTable);

View File

@ -13,6 +13,7 @@
#include "AboutDialog.h"
#include "CurvesGraphicsView.h"
#include "DoubleSpinBoxTableItemDelegate.h"
#include "PaletteEditor/PaletteEditor.h"
/// <summary>
/// Fractorium class.
@ -90,6 +91,11 @@ public:
Fractorium(QWidget* p = nullptr);
~Fractorium();
//Toolbar.
bool DrawXforms();
bool DrawImage();
bool DrawGrid();
//Library.
void SyncFileCountToSequenceCount();
@ -135,6 +141,8 @@ public slots:
void OnActionPasteSelectedXforms(bool checked);
void OnActionResetWorkspace(bool checked);//View
void OnActionAlternateEditorImage(bool checked);
void OnActionResetScale(bool checked);
void OnActionAddReflectiveSymmetry(bool checked);//Tools.
void OnActionAddRotationalSymmetry(bool checked);
@ -158,6 +166,9 @@ public slots:
void OnActionDP(bool checked);
void OnActionStyle(bool checked);
void OnActionStartStopRenderer(bool checked);
void OnActionDrawXforms(bool checked);
void OnActionDrawImage(bool checked);
void OnActionDrawGrid(bool checked);
//Library.
void OnEmberTreeItemChanged(QTreeWidgetItem* item, int col);
@ -237,7 +248,6 @@ public slots:
void OnXformAnimateCheckBoxStateChanged(int state);
//Xforms Affine.
void OnLockAffineScaleCheckBoxStateChanged(int state);
void OnPreAffineRowDoubleClicked(int logicalIndex);
void OnPreAffineColDoubleClicked(int logicalIndex);
void OnPostAffineRowDoubleClicked(int logicalIndex);
@ -275,6 +285,9 @@ public slots:
void OnXformScrollColorIndexChanged(int d);
void OnRandomColorIndicesButtonClicked(bool b);
void OnToggleColorIndicesButtonClicked(bool b);
void OnRandomColorSpeedButtonClicked(bool b);
void OnToggleColorSpeedButtonClicked(bool b);
void OnXformColorSpeedChanged(double d);
void OnXformOpacityChanged(double d);
void OnXformDirectColorChanged(double d);
@ -313,9 +326,12 @@ public slots:
void OnPaletteCellDoubleClicked(int row, int col);
void OnPaletteRandomSelectButtonClicked(bool checked);
void OnPaletteRandomAdjustButtonClicked(bool checked);
void OnPaletteEditorButtonClicked(bool checked);
void OnPaletteFilterLineEditTextChanged(const QString& text);
void OnPaletteFilterClearButtonClicked(bool checked);
void OnPaletteHeaderSectionClicked(int col);
void OnPaletteEditorColorChanged();
void OnPaletteEditorFileChanged();
//Info.
void OnSummaryTableHeaderResized(int logicalIndex, int oldSize, int newSize);
@ -371,6 +387,7 @@ private:
void SyncOptionsToToolbar();
//Library.
void SelectLibraryItem(size_t index);
vector<pair<size_t, QTreeWidgetItem*>> GetCurrentEmberIndex();
void SyncSequenceSettings();
@ -384,7 +401,6 @@ private:
//Xforms Variations.
void Filter();
void Filter(const QString& text);
//Xforms Selection.
void ClearXformsSelections();
@ -397,6 +413,7 @@ private:
void ResetPaletteControls();
void SetPaletteFileComboIndex(const string& filename);
void SetPaletteTableItem(QPixmap* pixmap, QTableWidget* table, QTableWidgetItem* item, int row, int col);
bool PaletteChanged();
//Info.
void FillSummary();
@ -415,11 +432,12 @@ private:
QString SetupSaveXmlDialog(const QString& defaultFilename);
QString SetupSaveImageDialog(const QString& defaultFilename);
QString SetupSaveFolderDialog();
QColorDialog* m_ColorDialog;
FractoriumFinalRenderDialog* m_FinalRenderDialog;
FractoriumOptionsDialog* m_OptionsDialog;
FractoriumVariationsDialog* m_VarDialog;
FractoriumAboutDialog* m_AboutDialog;
QColorDialog* m_ColorDialog = nullptr;
FractoriumFinalRenderDialog* m_FinalRenderDialog = nullptr;
FractoriumOptionsDialog* m_OptionsDialog = nullptr;
FractoriumVariationsDialog* m_VarDialog = nullptr;
FractoriumAboutDialog* m_AboutDialog = nullptr;
PaletteEditor* m_PaletteEditor = nullptr;
//Params.
DoubleSpinBox* m_BrightnessSpin;//Color.
@ -494,6 +512,8 @@ private:
DoubleSpinBoxTableItemDelegate* m_XaosTableItemDelegate;
//Palette.
bool m_PaletteChanged;
bool m_PaletteFileChanged;
SpinBox* m_PaletteHueSpin;
SpinBox* m_PaletteSaturationSpin;
SpinBox* m_PaletteBrightnessSpin;
@ -510,9 +530,9 @@ private:
QTableWidgetItem* m_InfoFinalXformItem;
//Files.
QFileDialog* m_FileDialog;
QFileDialog* m_FolderDialog;
QssDialog* m_QssDialog;
QFileDialog* m_FileDialog = nullptr;
QFileDialog* m_FolderDialog = nullptr;
QssDialog* m_QssDialog = nullptr;
QString m_LastSaveAll;
QString m_LastSaveCurrent;
QString m_Style;

View File

@ -47,5 +47,8 @@
<file>Icons/checkbox_unchecked.png</file>
<file>Icons/control.png</file>
<file>Icons/control-stop-square.png</file>
<file>Icons/Function-512.png</file>
<file>Icons/pic.png</file>
<file>Icons/grid.png</file>
</qresource>
</RCC>

View File

@ -74,7 +74,7 @@
<rect>
<x>0</x>
<y>0</y>
<width>1284</width>
<width>1269</width>
<height>987</height>
</rect>
</property>
@ -2007,13 +2007,13 @@
<rect>
<x>770</x>
<y>0</y>
<width>255</width>
<width>294</width>
<height>881</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>255</width>
<width>294</width>
<height>617</height>
</size>
</property>
@ -2420,7 +2420,7 @@
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="PaletteRandomSelect">
<widget class="QPushButton" name="PaletteRandomSelectButton">
<property name="minimumSize">
<size>
<width>0</width>
@ -2442,7 +2442,7 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="PaletteRandomAdjust">
<widget class="QPushButton" name="PaletteRandomAdjustButton">
<property name="minimumSize">
<size>
<width>0</width>
@ -2466,6 +2466,16 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="PaletteEditorButton">
<property name="toolTip">
<string>Open the palette editor to make a custom palette</string>
</property>
<property name="text">
<string>Palette Editor...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="9" column="0">
@ -3050,7 +3060,7 @@
<enum>QTabWidget::Triangular</enum>
</property>
<property name="currentIndex">
<number>2</number>
<number>1</number>
</property>
<widget class="QWidget" name="XformColorTab">
<property name="sizePolicy">
@ -3370,6 +3380,53 @@
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_5">
<property name="leftMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="RandomColorIndicesButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set all xform color indices to random numbers between 0 and 1, inclusive&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Random Indices</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="ToggleColorIndicesButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set all xform color indices to 0 or 1&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Toggle Indices</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="RandomColorSpeedButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set all xform color indices to 0 or 1&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Random Color Speed</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="ToggleColorSpeedButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set all xform color indices to 0 or 1&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Toggle Color Speed</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0" colspan="2">
<widget class="TableWidget" name="XformColorValuesTable">
<property name="sizePolicy">
@ -3565,36 +3622,6 @@
</item>
</widget>
</item>
<item row="3" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>4</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<item>
<widget class="QPushButton" name="RandomColorIndicesButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set all xform color indices to random numbers between 0 and 1, inclusive&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Random Indices</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="ToggleColorIndicesButton">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Set all xform color indices to 0 or 1&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Toggle Indices</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="SoloXformCheckBox">
<property name="toolTip">
@ -3642,16 +3669,6 @@
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QCheckBox" name="LockAffineCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Checking this box will lock the display size of the affines at their current level, allowing for easy manipulation no matter what zoom level is used.&lt;/p&gt;&lt;p&gt;Unchecking this resets the scale used to draw the affines to its native value.&lt;/p&gt;&lt;p&gt;Note this only affects the display and does not alter any internal values used for rendering.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Lock Affine Scale</string>
</property>
</widget>
</item>
<item>
<widget class="QScrollArea" name="AffineTabScrollArea">
<property name="autoFillBackground">
@ -3672,7 +3689,7 @@
<x>0</x>
<y>0</y>
<width>263</width>
<height>700</height>
<height>722</height>
</rect>
</property>
<property name="autoFillBackground">
@ -6843,6 +6860,8 @@
<string>&amp;View</string>
</property>
<addaction name="ActionResetWorkspace"/>
<addaction name="ActionAlternateEditorImage"/>
<addaction name="ActionResetScale"/>
</widget>
<addaction name="MenuFile"/>
<addaction name="MenuEdit"/>
@ -6997,7 +7016,7 @@
<x>0</x>
<y>0</y>
<width>432</width>
<height>572</height>
<height>589</height>
</rect>
</property>
<property name="sizePolicy">
@ -7685,6 +7704,11 @@
<addaction name="ActionOptions"/>
<addaction name="separator"/>
<addaction name="ActionStyle"/>
<addaction name="separator"/>
<addaction name="ActionDrawXforms"/>
<addaction name="ActionDrawImage"/>
<addaction name="ActionDrawGrid"/>
<addaction name="ActionResetScale"/>
</widget>
<action name="ActionNewFlock">
<property name="icon">
@ -7870,6 +7894,9 @@
<property name="toolTip">
<string>Add a copy of the current flame to the end of the current file</string>
</property>
<property name="shortcut">
<string>Ctrl+J</string>
</property>
</action>
<action name="ActionClearFlame">
<property name="icon">
@ -8128,6 +8155,67 @@
<string>Ctrl+P</string>
</property>
</action>
<action name="ActionDrawImage">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="Fractorium.qrc">
<normaloff>:/Fractorium/Icons/pic.png</normaloff>:/Fractorium/Icons/pic.png</iconset>
</property>
<property name="text">
<string>Show/Hide Image</string>
</property>
<property name="toolTip">
<string>Show/Hide Image</string>
</property>
<property name="shortcut">
<string>Ctrl+I</string>
</property>
</action>
<action name="ActionDrawGrid">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="Fractorium.qrc">
<normaloff>:/Fractorium/Icons/grid.png</normaloff>:/Fractorium/Icons/grid.png</iconset>
</property>
<property name="text">
<string>Show/Hide Grid</string>
</property>
<property name="toolTip">
<string>Show/Hide Grid</string>
</property>
</action>
<action name="ActionDrawXforms">
<property name="checkable">
<bool>true</bool>
</property>
<property name="icon">
<iconset resource="Fractorium.qrc">
<normaloff>:/Fractorium/Icons/Function-512.png</normaloff>:/Fractorium/Icons/Function-512.png</iconset>
</property>
<property name="text">
<string>Show/Hide Xforms</string>
</property>
<property name="toolTip">
<string>Show/Hide Xforms</string>
</property>
</action>
<action name="ActionAlternateEditorImage">
<property name="text">
<string>Alternate Editor/Image</string>
</property>
<property name="shortcut">
<string>Ctrl+W</string>
</property>
</action>
<action name="ActionResetScale">
<property name="text">
<string>Reset Scale</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>

View File

@ -2,10 +2,12 @@
#include "FractoriumPch.h"
#include "FractoriumSettings.h"
#include "PaletteTableWidgetItem.h"
/// <summary>
/// Fractorium global utility functions.
/// </summary>
#define PALETTE_CELL_HEIGHT 16
/// <summary>
/// Setup a spinner to be placed in a table cell.
@ -368,6 +370,116 @@ static void HandleDeviceTableCheckChanged(QTableWidget* table, int row, int col)
primaryItem->setCheckState(Qt::Checked);
}
/// <summary>
/// Set a row in a table to represent a palette.
/// This will place the palette name as a string value in the first column,
/// and a QPixmap representing the palette in the second column.
/// </summary>
/// <param name="paletteTable">The table write to the row to</param>
/// <param name="palette">A pointer to the palette to write to the row</param>
/// <param name="row">The row to write the palette to</param>
static void AddPaletteToTable(QTableWidget* paletteTable, Palette<float>* palette, int row)
{
auto v = palette->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);
auto nameCol = new QTableWidgetItem(palette->m_Name.c_str());
nameCol->setToolTip(palette->m_Name.c_str());
paletteTable->setItem(row, 0, nameCol);
QImage image(v.data(), int(palette->Size()), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);
auto paletteItem = new PaletteTableWidgetItem(palette);
paletteItem->setData(Qt::DecorationRole, QPixmap::fromImage(image));
paletteItem->setFlags(paletteItem->flags() & ~Qt::ItemIsEditable);
paletteTable->setItem(row, 1, paletteItem);
}
/// <summary>
/// Read a palette Xml file and populate the palette table with the contents.
/// This will clear any previous contents.
/// Called upon initialization, palette combo index change, and controller type change.
/// </summary>
/// <param name="s">The name of the palette file without the path</param>
/// <returns>True if successful, else false.</returns>
static bool FillPaletteTable(const string& s, QTableWidget* paletteTable, PaletteList<float>& paletteList)
{
if (!s.empty())//This occasionally seems to get called with an empty string for reasons unknown.
{
if (auto palettes = paletteList.GetPaletteListByFilename(s))
{
paletteTable->clear();
paletteTable->blockSignals(true);
paletteTable->setRowCount(int(palettes->size()));
//Headers get removed when clearing, so must re-create here.
auto nameHeader = new QTableWidgetItem("Name");
auto paletteHeader = new QTableWidgetItem("Palette");
nameHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
paletteHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
paletteTable->setHorizontalHeaderItem(0, nameHeader);
paletteTable->setHorizontalHeaderItem(1, paletteHeader);
//Palette list table.
for (auto i = 0; i < palettes->size(); i++)
if (auto palette = &(*palettes)[i])
AddPaletteToTable(paletteTable, palette, i);
paletteTable->blockSignals(false);
return true;
}
}
return false;
}
/// <summary>
/// Get the default search paths for config and palette files.
/// </summary>
/// <returns>vector<QString> of paths</returns>
static vector<QString> GetDefaultPaths()
{
static vector<QString> paths =
{
(QDir::homePath() + "/AppData/Roaming/Fractorium").toLocal8Bit().data(),
#ifndef _WIN32
QString("/usr/share/fractorium").toLocal8Bit().data(),
QString("/usr/local/share/fractorium").toLocal8Bit().data(),
(QDir::homePath() + "/.config/fractorium").toLocal8Bit().data(),
#endif
QDir::currentPath().toLocal8Bit().data(),
QCoreApplication::applicationDirPath().toLocal8Bit().data()
};
return paths;
}
/// <summary>
/// Get the default user path for config and palette files.
/// </summary>
/// <returns>vector<QString> of paths</returns>
static QString GetDefaultUserPath()
{
#ifdef _WIN32
return (QDir::homePath() + "/AppData/Roaming/Fractorium").toLocal8Bit().data();
#else
return (QDir::homePath() + "/.config/fractorium").toLocal8Bit().data();
#endif
}
/// <summary>
/// Get the first flam3-palettes.xml file in the default search paths.
/// </summary>
/// <returns>The full path and filename if found, else empty string.</returns>
static QString FindFirstDefaultPalette()
{
auto paths = GetDefaultPaths();
for (auto& path : paths)
{
auto full = path + "/flam3-palettes.xml";
if (QFile::exists(full))
return full;
}
return "";
}
/// <summary>
/// The basic style that is needed for things to look right, this varies by OS.
/// </summary>

View File

@ -42,33 +42,38 @@ FractoriumEmberController<T>::FractoriumEmberController(Fractorium* fractorium)
: FractoriumEmberControllerBase(fractorium),
m_VariationList(VariationList<T>::Instance())
{
bool b = false;
size_t b = 0;
m_GLController = make_unique<GLEmberController<T>>(fractorium, fractorium->ui.GLDisplay, this);
m_LibraryPreviewRenderer = make_unique<TreePreviewRenderer<T>>(this, m_Fractorium->ui.LibraryTree, m_EmberFile);
m_SequencePreviewRenderer = make_unique<TreePreviewRenderer<T>>(this, m_Fractorium->ui.SequenceTree, m_SequenceFile);
m_PaletteList.Clear();
m_Fractorium->ui.PaletteFilenameCombo->clear();
//Initial combo change event to fill the palette table will be called automatically later.
//Look hard for a palette.
static vector<string> paths =
{
QDir::currentPath().toLocal8Bit().data(),
QDir::homePath().toLocal8Bit().data(),
QCoreApplication::applicationDirPath().toLocal8Bit().data(),
QString(QDir::homePath() + "/.config/fractorium").toLocal8Bit().data(),
QString("/usr/share/fractorium").toLocal8Bit().data(),
QString("/usr/local/share/fractorium").toLocal8Bit().data()
};
auto paths = GetDefaultPaths();
for (auto& path : paths)
b |= InitPaletteList(path);
if (b)
{
if (b = InitPaletteList(path))
{
m_SheepTools = make_unique<SheepTools<T, float>>(m_PaletteList.Name(0), new EmberNs::Renderer<T, float>());
break;
}
m_SheepTools = make_unique<SheepTools<T, float>>(m_PaletteList.Name(0), new EmberNs::Renderer<T, float>());
}
else
{
QString allPaths;
for (auto& path : paths)
allPaths += path + "\r\n";
allPaths = QString("No palettes found in paths:\r\n") + allPaths + "\r\nExiting.";
std::runtime_error ex(allPaths.toStdString());
throw ex;
}
if (!b)
throw "No palettes found, exiting.";
if (m_PaletteList.Size() >= 1)//Only add the user palette if the folder already had a palette, which means we'll be using this folder.
if (m_PaletteList.AddEmptyPaletteFile((GetDefaultUserPath() + "/user-palettes.xml").toStdString()))
m_Fractorium->ui.PaletteFilenameCombo->addItem("user-palettes.xml");
BackgroundChanged(QColor(0, 0, 0));//Default to black.
ClearUndo();
@ -146,18 +151,7 @@ void FractoriumEmberController<T>::SetEmber(size_t index, bool verbatim)
{
if (index < m_EmberFile.Size())
{
if (auto top = m_Fractorium->ui.LibraryTree->topLevelItem(0))
{
for (int i = 0; i < top->childCount(); i++)
{
if (auto emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(i)))
{
emberItem->setSelected(i == index);
emberItem->setCheckState(0, i == index ? Qt::Checked : Qt::Unchecked);
}
}
}
m_Fractorium->SelectLibraryItem(index);
ClearUndo();
SetEmber(*m_EmberFile.Get(index), verbatim, true);
}
@ -335,6 +329,13 @@ void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& ember, bool v
static EmberToXml<T> writer;//Save parameters of last full render just in case there is a crash.
#ifdef _WIN32
string filename = "last.flame";
#elif defined(__APPLE__)
QDir dir(QDir::applicationDirPath());
if (!dir.exists())
dir.mkpath(".");
string filename = QDir::applicationDirPath().toStdString() + "/last.flame";
#else
QDir dir(QDir::homePath() + "/.config/fractorium");

View File

@ -49,6 +49,8 @@ template <typename T> class TreePreviewRenderer;
/// </summary>
class FractoriumEmberControllerBase : public RenderCallback
{
friend Fractorium;
public:
FractoriumEmberControllerBase(Fractorium* fractorium);
FractoriumEmberControllerBase(const FractoriumEmberControllerBase& controller) = delete;
@ -194,13 +196,15 @@ public:
double LockedX() { return m_LockedX; }
double LockedY() { return m_LockedY; }
void LockedScale(double scale) { m_LockedScale = scale; }
virtual void LockAffineScaleCheckBoxStateChanged(int state) { }
virtual void InitLockedScale() { }
//Xforms Color.
virtual void XformColorIndexChanged(double d, bool updateRender) { }
virtual void XformScrollColorIndexChanged(int d) { }
virtual void RandomColorIndicesButtonClicked() { }
virtual void ToggleColorIndicesButtonClicked() { }
virtual void RandomColorSpeedButtonClicked() { }
virtual void ToggleColorSpeedButtonClicked() { }
virtual void XformColorSpeedChanged(double d) { }
virtual void XformOpacityChanged(double d) { }
virtual void XformDirectColorChanged(double d) { }
@ -222,11 +226,13 @@ public:
virtual void RandomXaos() { }
//Palette.
virtual size_t InitPaletteList(const string& s) { return 0; }
virtual size_t InitPaletteList(const QString& s) { return 0; }
virtual bool FillPaletteTable(const string& s) { return false; }
virtual void ApplyPaletteToEmber() { }
virtual void PaletteAdjust() { }
virtual void PaletteCellClicked(int row, int col) { }
virtual void SetBasePaletteAndAdjust(const Palette<float>& palette) { }
virtual void PaletteEditorButtonClicked() { }
QImage& FinalPaletteImage() { return m_FinalPaletteImage; }
//Info.
@ -287,6 +293,8 @@ protected:
unique_ptr<EmberNs::RendererBase> m_Renderer;
QTIsaac<ISAAC_SIZE, ISAAC_INT> m_Rand;
Fractorium* m_Fractorium;
Palette<float> m_TempPalette;
PaletteList<float> m_PaletteList;
std::unique_ptr<QTimer> m_RenderTimer;
std::unique_ptr<QTimer> m_RenderRestartTimer;
shared_ptr<OpenCLInfo> m_Info = OpenCLInfo::Instance();
@ -451,8 +459,9 @@ public:
virtual void ResetXformsAffine(bool pre) override;
virtual void RandomXformsAffine(bool pre) override;
virtual void FillBothAffines() override;
virtual void LockAffineScaleCheckBoxStateChanged(int state) override;
virtual void InitLockedScale() override;
void FillAffineWithXform(Xform<T>* xform, bool pre);
void ChangeLockedScale(T value);
T AffineScaleCurrentToLocked();
T AffineScaleLockedToCurrent();
@ -461,6 +470,8 @@ public:
virtual void XformScrollColorIndexChanged(int d) override;
virtual void RandomColorIndicesButtonClicked() override;
virtual void ToggleColorIndicesButtonClicked() override;
virtual void RandomColorSpeedButtonClicked() override;
virtual void ToggleColorSpeedButtonClicked() override;
virtual void XformColorSpeedChanged(double d) override;
virtual void XformOpacityChanged(double d) override;
virtual void XformDirectColorChanged(double d) override;
@ -486,11 +497,13 @@ public:
bool XformCheckboxAt(Xform<T>* xform, std::function<void(QCheckBox*)> func);
//Palette.
virtual size_t InitPaletteList(const string& s) override;
virtual size_t InitPaletteList(const QString& s) override;
virtual bool FillPaletteTable(const string& s) override;
virtual void ApplyPaletteToEmber() override;
virtual void PaletteAdjust() override;
virtual void PaletteCellClicked(int row, int col) override;
virtual void SetBasePaletteAndAdjust(const Palette<float>& palette) override;
virtual void PaletteEditorButtonClicked() override;
//Info.
virtual void FillSummary() override;
@ -522,7 +535,7 @@ private:
QString MakeXformCaption(size_t i);
//Palette.
void UpdateAdjustedPaletteGUI(Palette<T>& palette);
void UpdateAdjustedPaletteGUI(Palette<float>& palette);
//Rendering/progress.
void Update(std::function<void (void)> func, bool updateRender = true, eProcessAction action = eProcessAction::FULL_RENDER);
@ -540,8 +553,6 @@ private:
deque<Ember<T>> m_UndoList;
vector<Xform<T>> m_CopiedXforms;
Xform<T> m_CopiedFinalXform;
Palette<T> m_TempPalette;
PaletteList<T> m_PaletteList;
shared_ptr<VariationList<T>> m_VariationList;
unique_ptr<SheepTools<T, float>> m_SheepTools;
unique_ptr<GLEmberController<T>> m_GLController;

View File

@ -43,6 +43,25 @@ void Fractorium::InitLibraryUI()
connect(ui.SequenceRandomBlendMaxFramesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSequenceRandomBlendMaxFramesSpinBoxChanged(int)), Qt::QueuedConnection);
}
/// <summary>
/// Select the item in the library tree specified by the passed in index.
/// </summary>
/// <param name="index">The 0-based index of the item in the library tree to select</param>
void Fractorium::SelectLibraryItem(size_t index)
{
if (auto top = ui.LibraryTree->topLevelItem(0))
{
for (int i = 0; i < top->childCount(); i++)
{
if (auto emberItem = dynamic_cast<EmberTreeWidgetItemBase*>(top->child(i)))
{
emberItem->setSelected(i == index);
emberItem->setCheckState(0, i == index ? Qt::Checked : Qt::Unchecked);
}
}
}
}
/// <summary>
/// Get the index of the currently selected ember in the library tree.
/// </summary>
@ -153,9 +172,7 @@ void FractoriumEmberController<T>::FillLibraryTree(int selectIndex)
tree->blockSignals(false);
if (selectIndex != -1)
if (auto top = tree->topLevelItem(0))
if (auto emberItem = dynamic_cast<EmberTreeWidgetItem<T>*>(top->child(selectIndex)))
emberItem->setSelected(true);
m_Fractorium->SelectLibraryItem(selectIndex);
m_Fractorium->SyncFileCountToSequenceCount();
QCoreApplication::flush();
@ -568,12 +585,13 @@ void FractoriumEmberController<T>::SequenceGenerateButtonClicked()
vector<pair<size_t, size_t>> devices;//Dummy.
EmberReport emberReport;
ostringstream os;
string palettePath =
#ifdef _WIN32
"./flam3-palettes.xml";
#else
"~/.config/fractorium";
#endif
string palettePath = FindFirstDefaultPalette().toStdString();
if (palettePath.empty())//This should never happen because the program requires a palette file to run this far.
{
QMessageBox::warning(nullptr, "Sequence", "No flam3-palettes.xml file found, sequence will not be generated.");
return;
}
if (!randRot && !randBlend)
{
@ -700,12 +718,15 @@ void Fractorium::OnSequenceGenerateButtonClicked(bool checked) { m_Controller->S
/// <param name="checked">Ignored.</param>
void Fractorium::OnSequenceRenderButtonClicked(bool checked)
{
//First completely stop what the current rendering process is doing.
m_Controller->DeleteRenderer();//Delete the renderer, but not the controller.
m_Controller->StopAllPreviewRenderers();
m_Controller->SaveCurrentToOpenedFile(false);//Save whatever was edited back to the current open file.
m_RenderStatusLabel->setText("Renderer stopped.");
m_FinalRenderDialog->Show(true);//Show with a bool specifying that it came from the sequence generator.
if (ui.SequenceTree->topLevelItemCount() > 0)
{
//First completely stop what the current rendering process is doing.
m_Controller->DeleteRenderer();//Delete the renderer, but not the controller.
m_Controller->StopAllPreviewRenderers();
m_Controller->SaveCurrentToOpenedFile(false);//Save whatever was edited back to the current open file.
m_RenderStatusLabel->setText("Renderer stopped.");
m_FinalRenderDialog->Show(true);//Show with a bool specifying that it came from the sequence generator.
}
}
/// <summary>
@ -825,5 +846,5 @@ void Fractorium::SyncSequenceSettings()
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
template class FractoriumEmberController<double>;
#endif

View File

@ -27,7 +27,9 @@ void Fractorium::InitMenusUI()
connect(ui.ActionPasteSelectedXforms, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteSelectedXforms(bool)), Qt::QueuedConnection);
ui.ActionPasteSelectedXforms->setEnabled(false);
//View menu.
connect(ui.ActionResetWorkspace, SIGNAL(triggered(bool)), this, SLOT(OnActionResetWorkspace(bool)), Qt::QueuedConnection);
connect(ui.ActionResetWorkspace, SIGNAL(triggered(bool)), this, SLOT(OnActionResetWorkspace(bool)), Qt::QueuedConnection);
connect(ui.ActionAlternateEditorImage, SIGNAL(triggered(bool)), this, SLOT(OnActionAlternateEditorImage(bool)), Qt::QueuedConnection);
connect(ui.ActionResetScale, SIGNAL(triggered(bool)), this, SLOT(OnActionResetScale(bool)), Qt::QueuedConnection);
//Tools menu.
connect(ui.ActionAddReflectiveSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddReflectiveSymmetry(bool)), Qt::QueuedConnection);
connect(ui.ActionAddRotationalSymmetry, SIGNAL(triggered(bool)), this, SLOT(OnActionAddRotationalSymmetry(bool)), Qt::QueuedConnection);
@ -698,6 +700,38 @@ void Fractorium::OnActionResetWorkspace(bool checked)
ui.LibraryDockWidget->show();
}
/// <summary>
/// Alternate between Editor/Image.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionAlternateEditorImage(bool checked)
{
if (DrawImage())
{
ui.ActionDrawImage->setChecked(false);
ui.ActionDrawXforms->setChecked(true);
ui.ActionDrawGrid->setChecked(true);
}
else
{
ui.ActionDrawXforms->setChecked(false);
ui.ActionDrawGrid->setChecked(false);
ui.ActionDrawImage->setChecked(true);
}
ui.GLDisplay->update();
}
/// <summary>
/// Reset the scale used to draw affines, which was adjusted by zooming with the Alt key pressed.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnActionResetScale(bool checked)
{
m_Controller->InitLockedScale();
ui.GLDisplay->update();
}
/// <summary>
/// Add reflective symmetry to the current ember.
/// Resets the rendering process.
@ -877,5 +911,5 @@ void Fractorium::OnActionAbout(bool checked)
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
template class FractoriumEmberController<double>;
#endif

View File

@ -2,8 +2,6 @@
#include "Fractorium.h"
#include "PaletteTableWidgetItem.h"
#define PALETTE_CELL_HEIGHT 16
/// <summary>
/// Initialize the palette UI.
/// </summary>
@ -25,8 +23,10 @@ void Fractorium::InitPaletteUI()
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteContrastSpin, spinHeight, -100, 100, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteBlurSpin, spinHeight, 0, 127, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 0, 0, 0);
SetupSpinner<SpinBox, int>(table, this, row, 3, m_PaletteFrequencySpin, spinHeight, 1, 10, 1, SIGNAL(valueChanged(int)), SLOT(OnPaletteAdjust(int)), true, 1, 1, 1);
connect(ui.PaletteRandomSelect, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomSelectButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PaletteRandomAdjust, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomAdjustButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PaletteRandomSelectButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomSelectButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.PaletteRandomAdjustButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteRandomAdjustButtonClicked(bool)), Qt::QueuedConnection);
//Palette editor.
connect(ui.PaletteEditorButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteEditorButtonClicked(bool)), Qt::QueuedConnection);
//Preview table.
palettePreviewTable->setRowCount(1);
palettePreviewTable->setColumnWidth(1, 260);//256 plus small margin on each side.
@ -38,13 +38,13 @@ void Fractorium::InitPaletteUI()
connect(ui.PaletteFilterClearButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteFilterClearButtonClicked(bool)));
paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side.
paletteTable->horizontalHeader()->setSectionsClickable(true);
connect(paletteTable->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(OnPaletteHeaderSectionClicked(int)), Qt::QueuedConnection);
connect(ui.ResetCurvesButton, SIGNAL(clicked(bool)), this, SLOT(OnResetCurvesButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.CurvesView, SIGNAL(PointChangedSignal(int, int, const QPointF&)), this, SLOT(OnCurvesPointChanged(int, int, const QPointF&)), Qt::QueuedConnection);
connect(ui.CurvesAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesAllRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.CurvesRedRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesRedRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.CurvesGreenRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesGreenRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.CurvesBlueRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesBlueRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(paletteTable->horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(OnPaletteHeaderSectionClicked(int)), Qt::QueuedConnection);
connect(ui.ResetCurvesButton, SIGNAL(clicked(bool)), this, SLOT(OnResetCurvesButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.CurvesView, SIGNAL(PointChangedSignal(int, int, const QPointF&)), this, SLOT(OnCurvesPointChanged(int, int, const QPointF&)), Qt::QueuedConnection);
connect(ui.CurvesAllRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesAllRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.CurvesRedRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesRedRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.CurvesGreenRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesGreenRadioButtonToggled(bool)), Qt::QueuedConnection);
connect(ui.CurvesBlueRadio, SIGNAL(toggled(bool)), this, SLOT(OnCurvesBlueRadioButtonToggled(bool)), Qt::QueuedConnection);
}
/// <summary>
@ -55,20 +55,28 @@ void Fractorium::InitPaletteUI()
/// <param name="s">The full path to the palette files folder</param>
/// <returns>The number of palettes successfully added</returns>
template <typename T>
size_t FractoriumEmberController<T>::InitPaletteList(const string& s)
size_t FractoriumEmberController<T>::InitPaletteList(const QString& s)
{
QDirIterator it(s.c_str(), QStringList() << "*.xml", QDir::Files, QDirIterator::FollowSymlinks);
m_PaletteList.Clear();
m_Fractorium->ui.PaletteFilenameCombo->clear();
m_Fractorium->ui.PaletteFilenameCombo->setProperty("path", QString::fromStdString(s));
QDirIterator it(s, QStringList() << "*.xml" << "*.ugr" << "*.gradient" << "*.gradients", QDir::Files, QDirIterator::FollowSymlinks);
while (it.hasNext())
{
auto path = it.next().toStdString();
auto path = it.next();
auto qfilename = it.fileName();
if (m_PaletteList.Add(path))
m_Fractorium->ui.PaletteFilenameCombo->addItem(qfilename);
try
{
if (QFile::exists(path) && m_PaletteList.Add(path.toStdString()))
m_Fractorium->ui.PaletteFilenameCombo->addItem(qfilename);
}
catch (const std::exception& e)
{
QMessageBox::critical(nullptr, "Palette Parsing Error", QString::fromStdString(e.what()));
}
catch (const char* e)
{
QMessageBox::critical(nullptr, "Palette Parsing Error", e);
}
}
return m_PaletteList.Size();
@ -79,7 +87,7 @@ size_t FractoriumEmberController<T>::InitPaletteList(const string& s)
/// This will clear any previous contents.
/// Called upon initialization, palette combo index change, and controller type change.
/// </summary>
/// <param name="s">The name to the palette file without the path</param>
/// <param name="s">The name of the palette file without the path</param>
/// <returns>True if successful, else false.</returns>
template <typename T>
bool FractoriumEmberController<T>::FillPaletteTable(const string& s)
@ -87,38 +95,10 @@ bool FractoriumEmberController<T>::FillPaletteTable(const string& s)
if (!s.empty())//This occasionally seems to get called with an empty string for reasons unknown.
{
auto paletteTable = m_Fractorium->ui.PaletteListTable;
m_CurrentPaletteFilePath = m_Fractorium->ui.PaletteFilenameCombo->property("path").toString().toStdString() + "/" + s;
m_CurrentPaletteFilePath = s;
if (int paletteSize = int(m_PaletteList.Size(m_CurrentPaletteFilePath)))
if (::FillPaletteTable(m_CurrentPaletteFilePath, paletteTable, m_PaletteList))
{
paletteTable->clear();
paletteTable->blockSignals(true);
paletteTable->setRowCount(paletteSize);
//Headers get removed when clearing, so must re-create here.
auto nameHeader = new QTableWidgetItem("Name");
auto paletteHeader = new QTableWidgetItem("Palette");
nameHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
paletteHeader->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
paletteTable->setHorizontalHeaderItem(0, nameHeader);
paletteTable->setHorizontalHeaderItem(1, paletteHeader);
//Palette list table.
for (auto i = 0; i < paletteSize; i++)
{
if (auto p = m_PaletteList.GetPalette(m_CurrentPaletteFilePath, i))
{
auto v = p->MakeRgbPaletteBlock(PALETTE_CELL_HEIGHT);
auto nameCol = new QTableWidgetItem(p->m_Name.c_str());
nameCol->setToolTip(p->m_Name.c_str());
paletteTable->setItem(i, 0, nameCol);
QImage image(v.data(), int(p->Size()), PALETTE_CELL_HEIGHT, QImage::Format_RGB888);
auto paletteItem = new PaletteTableWidgetItem<T>(p);
paletteItem->setData(Qt::DecorationRole, QPixmap::fromImage(image));
paletteTable->setItem(i, 1, paletteItem);
}
}
paletteTable->blockSignals(false);
return true;
}
else
@ -126,15 +106,24 @@ bool FractoriumEmberController<T>::FillPaletteTable(const string& s)
vector<string> errors = m_PaletteList.ErrorReport();
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit);
m_Fractorium->ShowCritical("Palette Read Error", "Could not load palette file, all images will be black. See info tab for details.");
m_PaletteList.ClearErrorReport();
}
}
return false;
}
/// <summary>
/// Fill the palette table with the passed in string.
/// Called when the palette name combo box changes.
/// </summary>
/// <param name="text">The full path to the palette file</param>
void Fractorium::OnPaletteFilenameComboChanged(const QString& text)
{
m_Controller->FillPaletteTable(text.toStdString());
auto s = text.toStdString();
m_Controller->FillPaletteTable(s);
auto fullname = m_Controller->m_PaletteList.GetFullPathFromFilename(s);
ui.PaletteFilenameCombo->setToolTip(QString::fromStdString(fullname));
ui.PaletteListTable->sortItems(0, m_PaletteSortMode == 0 ? Qt::AscendingOrder : Qt::DescendingOrder);
}
@ -161,7 +150,7 @@ void FractoriumEmberController<T>::ApplyPaletteToEmber()
/// <param name="palette">The palette to use</param>
/// <param name="paletteName">Name of the palette</param>
template <typename T>
void FractoriumEmberController<T>::UpdateAdjustedPaletteGUI(Palette<T>& palette)
void FractoriumEmberController<T>::UpdateAdjustedPaletteGUI(Palette<float>& palette)
{
auto xform = CurrentXform();
auto palettePreviewTable = m_Fractorium->ui.PalettePreviewTable;
@ -205,6 +194,20 @@ void FractoriumEmberController<T>::PaletteAdjust()
void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); }
/// <summary>
/// Set the passed in palette as the current one,
/// applying any adjustments previously specified.
/// Resets the rendering process.
/// </summary>
/// <param name="palette">The palette to assign to the temporary palette</param>
template <typename T>
void FractoriumEmberController<T>::SetBasePaletteAndAdjust(const Palette<float>& palette)
{
m_TempPalette = palette;//Deep copy.
ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments.
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted palette.
}
/// <summary>
/// Set the selected palette as the current one,
/// applying any adjustments previously specified.
@ -219,12 +222,8 @@ void Fractorium::OnPaletteAdjust(int d) { m_Controller->PaletteAdjust(); }
template <typename T>
void FractoriumEmberController<T>::PaletteCellClicked(int row, int col)
{
if (auto palette = m_PaletteList.GetPalette(m_CurrentPaletteFilePath, row))
{
m_TempPalette = *palette;//Deep copy.
ApplyPaletteToEmber();//Copy temp palette to ember palette and apply adjustments.
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Show the adjusted palette.
}
if (auto palette = m_PaletteList.GetPaletteByFilename(m_CurrentPaletteFilePath, row))
SetBasePaletteAndAdjust(*palette);
}
/// <summary>
@ -237,7 +236,7 @@ void FractoriumEmberController<T>::PaletteCellClicked(int row, int col)
/// <param name="col">The table column clicked, ignored</param>
void Fractorium::OnPaletteCellClicked(int row, int col)
{
if (auto item = dynamic_cast<PaletteTableWidgetItemBase*>(ui.PaletteListTable->item(row, 1)))
if (auto item = dynamic_cast<PaletteTableWidgetItem*>(ui.PaletteListTable->item(row, 1)))
{
auto index = int(item->Index());
@ -311,6 +310,85 @@ void Fractorium::OnPaletteRandomAdjustButtonClicked(bool checked)
OnPaletteAdjust(0);
}
/// <summary>
/// Open the palette editor dialog.
/// Called when the palette editor button is clicked.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::PaletteEditorButtonClicked()
{
auto ed = m_Fractorium->m_PaletteEditor;
Palette<float> prevPal = m_TempPalette;
ed->SetPalette(m_TempPalette);
if (ed->exec() == QDialog::Accepted)
{
if (!m_Fractorium->PaletteChanged())//If the clicked ok, but never synced, set the palette now.
SetBasePaletteAndAdjust(ed->GetPalette(int(256)));
}
else if (m_Fractorium->PaletteChanged())//They clicked cancel, but synced at least once, restore the previous palette.
{
SetBasePaletteAndAdjust(prevPal);
}
//If the palette was modifiable, and any palette was changed at least once
if (m_Fractorium->m_PaletteFileChanged && m_PaletteList.IsModifiable(m_CurrentPaletteFilePath))
{
if (!::FillPaletteTable(m_CurrentPaletteFilePath, m_Fractorium->ui.PaletteListTable, m_PaletteList))
{
vector<string> errors = m_PaletteList.ErrorReport();
m_Fractorium->ErrorReportToQTextEdit(errors, m_Fractorium->ui.InfoFileOpeningTextEdit);
m_Fractorium->ShowCritical("Palette Read Error", "Could not re-load modified palette file, all images will be black. See info tab for details.");
m_PaletteList.ClearErrorReport();
}
}
}
/// <summary>
/// Slot called when the palette editor changes the palette and the Sync checkbox is checked.
/// </summary>
bool Fractorium::PaletteChanged()
{
return m_PaletteChanged;
}
/// <summary>
/// Open the palette editor dialog.
/// This creates the palette editor dialog if it has not been created at least once.
/// Called when the palette editor button is clicked.
/// </summary>
/// <param name="checked">Ignored</param>
void Fractorium::OnPaletteEditorButtonClicked(bool checked)
{
if (!m_PaletteEditor)
{
m_PaletteEditor = new PaletteEditor(m_Controller->m_PaletteList, this);
connect(m_PaletteEditor, SIGNAL(PaletteChanged()), this, SLOT(OnPaletteEditorColorChanged()), Qt::QueuedConnection);
connect(m_PaletteEditor, SIGNAL(PaletteFileChanged()), this, SLOT(OnPaletteEditorFileChanged()), Qt::QueuedConnection);
}
m_PaletteChanged = false;
m_PaletteFileChanged = false;
m_Controller->PaletteEditorButtonClicked();
}
/// <summary>
/// Slot called every time a color is changed in the palette editor.
/// </summary>
void Fractorium::OnPaletteEditorColorChanged()
{
m_PaletteChanged = true;
m_Controller->SetBasePaletteAndAdjust(m_PaletteEditor->GetPalette(int(256)));
}
/// <summary>
/// Slot called every time a palette file is changed in the palette editor.
/// </summary>
void Fractorium::OnPaletteEditorFileChanged()
{
m_PaletteFileChanged = true;
}
/// <summary>
/// Apply the text in the palette filter text box to only show palettes whose names
/// contain the substring.
@ -427,7 +505,7 @@ void Fractorium::OnCurvesPointChanged(int curveIndex, int pointIndex, const QPoi
/// select a point by putting it on top of all the others.
/// Called when the any of the curve color radio buttons are toggled.
/// </summary>
/// <param name="curveIndex">The curve index, 0-1/</param>
/// <param name="checked">Ignored</param>
void Fractorium::OnCurvesAllRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::ALL); }
void Fractorium::OnCurvesRedRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::RED); }
void Fractorium::OnCurvesGreenRadioButtonToggled(bool checked) { if (checked) ui.CurvesView->SetTop(CurveIndex::GREEN); }
@ -457,5 +535,5 @@ void FractoriumEmberController<T>::FillCurvesControl()
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
template class FractoriumEmberController<double>;
#endif

View File

@ -22,11 +22,12 @@ void Fractorium::InitParamsUI()
SetFixedTableHeader(ui.IterationTableHeader->horizontalHeader());
SetFixedTableHeader(ui.AnimationTableHeader->horizontalHeader());
//Color.
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_BrightnessSpin, spinHeight, 0.05, 1000, 1, SIGNAL(valueChanged(double)), SLOT(OnBrightnessChanged(double)), true, 4.0, 4.0, 4.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_GammaSpin, spinHeight, 1, 9999, 0.5, SIGNAL(valueChanged(double)), SLOT(OnGammaChanged(double)), true, 4.0, 4.0, 4.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_GammaThresholdSpin, spinHeight, 0, 10, 0.01, SIGNAL(valueChanged(double)), SLOT(OnGammaThresholdChanged(double)), true, 0.1, 0.1, 0.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_VibrancySpin, spinHeight, 0, 30, 0.01, SIGNAL(valueChanged(double)), SLOT(OnVibrancyChanged(double)), true, 1.0, 1.0, 0.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_HighlightSpin, spinHeight, -1.0, 10, 0.1, SIGNAL(valueChanged(double)), SLOT(OnHighlightPowerChanged(double)), true, 1.0, -1.0, -1.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_BrightnessSpin, spinHeight, 0.05, 1000, 1, SIGNAL(valueChanged(double)), SLOT(OnBrightnessChanged(double)), true, 4.0, 4.0, 4.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_GammaSpin, spinHeight, 1, 9999, 0.5, SIGNAL(valueChanged(double)), SLOT(OnGammaChanged(double)), true, 4.0, 4.0, 4.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_GammaThresholdSpin, spinHeight, 0, 10, 0.01, SIGNAL(valueChanged(double)), SLOT(OnGammaThresholdChanged(double)), true, 0.1, 0.1, 0.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_VibrancySpin, spinHeight, 0, 30, 0.01, SIGNAL(valueChanged(double)), SLOT(OnVibrancyChanged(double)), true, 1.0, 1.0, 0.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_HighlightSpin, spinHeight, -1.0, 10, 0.1, SIGNAL(valueChanged(double)), SLOT(OnHighlightPowerChanged(double)), true, 1.0, 1.0, -1.0);
m_HighlightSpin->DoubleClickLowVal(-1.0);
m_GammaThresholdSpin->setDecimals(4);
m_BackgroundColorButton = new QPushButton("...", table);
m_BackgroundColorButton->setMinimumWidth(21);
@ -63,7 +64,8 @@ void Fractorium::InitParamsUI()
//Filter.
row = 0;
table = ui.FilterTable;
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_SpatialFilterWidthSpin, spinHeight, 0.1, 2, 0.1, SIGNAL(valueChanged(double)), SLOT(OnSpatialFilterWidthChanged(double)), true, 1.0, 1.0, 1.0);
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_SpatialFilterWidthSpin, spinHeight, 0.1, 2, 0.1, SIGNAL(valueChanged(double)), SLOT(OnSpatialFilterWidthChanged(double)), true, 1.0, 1.0, 0.1);
m_SpatialFilterWidthSpin->DoubleClickLowVal(0.1);
comboVals = SpatialFilterCreator<float>::FilterTypes();
SetupCombo(table, this, row, 1, m_SpatialFilterTypeCombo, comboVals, SIGNAL(currentIndexChanged(const QString&)), SLOT(OnSpatialFilterTypeComboCurrentIndexChanged(const QString&)));
SetupSpinner<DoubleSpinBox, double>(table, this, row, 1, m_DEFilterMinRadiusSpin, spinHeight, 0, 25, 1, SIGNAL(valueChanged(double)), SLOT(OnDEFilterMinRadiusWidthChanged(double)), true, 0, 0, 0);
@ -192,7 +194,7 @@ void Fractorium::OnHighlightPowerChanged(double d) { m_Controller->HighlightPowe
/// <param name="checked">Ignored</param>
void Fractorium::OnBackgroundColorButtonClicked(bool checked)
{
m_ColorDialog->show();
m_ColorDialog->exec();
}
/// <summary>
@ -725,6 +727,7 @@ void FractoriumEmberController<T>::FillParamTablesAndPalette()
//Since the controls were cleared above, the adjusted palette will be identical to the base palette.
//Callers can set, apply and display palette adjustments after this function exits if needed.
UpdateAdjustedPaletteGUI(m_Ember.m_Palette);//Updating the palette GUI will trigger a full render.
InitLockedScale();
}
/// <summary>
@ -803,5 +806,5 @@ void Fractorium::SetScale(double scale)
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
template class FractoriumEmberController<double>;
#endif

View File

@ -26,41 +26,69 @@
#include <QtWidgets>
#endif
#include <math.h>
#include <deque>
#include "qfunctions.h"
#include <QApplication>
#include <QBrush>
#include <QCheckBox>
#include <QClipboard>
#include <QColor>
#include <QColorDialog>
#include <QComboBox>
#include <QConicalGradient>
#include <QDebug>
#include <QDesktopWidget>
#include <QDial>
#include <QDoubleSpinBox>
#include <QEvent>
#include <QFile>
#include <QFileInfo>
#include <QFont>
#include <QFontDialog>
#include <QFontMetrics>
#include <QFrame>
#include <QFuture>
#include <QGraphicsView>
#include <QGridLayout>
#include <QGroupBox>
#include <QHash>
#include <QHBoxLayout>
#include <QIcon>
#include <QImage>
#include <QImageReader>
#include <QItemDelegate>
#include <QKeyEvent>
#include <QLabel>
#include <QLayout>
#include <QLinearGradient>
#include <QLineEdit>
#include <QMap>
#include <QMenu>
#include <QMessageBox>
#include <QMimeData>
#include <QModelIndex>
#include <QMouseEvent>
#include <qopenglfunctions_2_0.h>
#include <QOpenGLWidget>
#include <QPainter>
#include <QPainterPath>
#include <QPaintEvent>
#include <QPixmap>
#include <QPoint>
#include <QPolygon>
#include <QPushButton>
#include <QRect>
#include <QResizeEvent>
#include <QSettings>
#include <QSignalMapper>
#include <QSize>
#include <QSizePolicy>
#include <QSpinBox>
#include <QStandardPaths>
#include <QTextEdit>
#include <QTimer>
#include <QToolBar>
#include <QTreeWidget>
#include <QWheelEvent>
#include <QtConcurrentRun>
#include <QtCore/qglobal.h>
#include <QtCore/QMultiHash>
#include <QtCore/QPair>
#include <QtCore/QSharedData>
@ -68,11 +96,31 @@
#include <QtCore/QStringList>
#include <QtCore/QVariant>
#include <QtCore/QVector>
#include <QtCore/qglobal.h>
#include <QTextEdit>
#include <QTextStream>
#include <QtGui/QFont>
#include <QtGui/QPalette>
#include <QtGui/QSyntaxHighlighter>
#include <QThread>
#include <QTime>
#include <QTimer>
#include <QToolBar>
#include <QToolTip>
#include <QTreeWidget>
#include <QtWidgets/QMainWindow>
#include <QVarLengthArray>
#include <QVBoxLayout>
#include <QVector>
#include <QWheelEvent>
#include <QWidget>
#include <QWidgetAction>
#define GLM_FORCE_RADIANS 1
#define GLM_ENABLE_EXPERIMENTAL 1
#ifndef __APPLE__
#define GLM_FORCE_INLINE 1
#endif
#include "glm/glm.hpp"
#include "glm/gtc/matrix_transform.hpp"

View File

@ -109,6 +109,12 @@ void FractoriumSettings::Double(bool b) { setValue(DOUBLEPRECISION, b);
bool FractoriumSettings::ShowAllXforms() { return value(SHOWALLXFORMS).toBool(); }
void FractoriumSettings::ShowAllXforms(bool b) { setValue(SHOWALLXFORMS, b); }
bool FractoriumSettings::ShowXforms() { return value(SHOWXFORMS, QVariant::fromValue(true)).toBool(); }
void FractoriumSettings::ShowXforms(bool b) { setValue(SHOWXFORMS, b); }
bool FractoriumSettings::ShowGrid() { return value(SHOWGRID, QVariant::fromValue(true)).toBool(); }
void FractoriumSettings::ShowGrid(bool b) { setValue(SHOWGRID, b); }
bool FractoriumSettings::ToggleType() { return value(TOGGLETYPE).toBool(); }
void FractoriumSettings::ToggleType(bool b) { setValue(TOGGLETYPE, b); }

View File

@ -13,6 +13,8 @@
#define DOUBLEPRECISION "render/dp64"
#define CONTUPDATE "render/continuousupdate"
#define SHOWALLXFORMS "render/dragshowallxforms"
#define SHOWXFORMS "render/showxforms"
#define SHOWGRID "render/showgrid"
#define TOGGLETYPE "render/toggletype"
#define DEVICES "render/devices"
#define THREADCOUNT "render/threadcount"
@ -112,6 +114,12 @@ public:
bool ShowAllXforms();
void ShowAllXforms(bool b);
bool ShowXforms();
void ShowXforms(bool b);
bool ShowGrid();
void ShowGrid(bool b);
bool ToggleType();
void ToggleType(bool b);

View File

@ -14,14 +14,26 @@ void Fractorium::InitToolbarUI()
spGroup->addAction(ui.ActionSP);
spGroup->addAction(ui.ActionDP);
SyncOptionsToToolbar();
ui.ActionDrawImage->setChecked(true);
connect(ui.ActionCpu, SIGNAL(triggered(bool)), this, SLOT(OnActionCpu(bool)), Qt::QueuedConnection);
connect(ui.ActionCL, SIGNAL(triggered(bool)), this, SLOT(OnActionCL(bool)), Qt::QueuedConnection);
connect(ui.ActionSP, SIGNAL(triggered(bool)), this, SLOT(OnActionSP(bool)), Qt::QueuedConnection);
connect(ui.ActionDP, SIGNAL(triggered(bool)), this, SLOT(OnActionDP(bool)), Qt::QueuedConnection);
connect(ui.ActionStyle, SIGNAL(triggered(bool)), this, SLOT(OnActionStyle(bool)), Qt::QueuedConnection);
connect(ui.ActionStartStopRenderer, SIGNAL(triggered(bool)), this, SLOT(OnActionStartStopRenderer(bool)), Qt::QueuedConnection);
connect(ui.ActionDrawXforms, SIGNAL(triggered(bool)), this, SLOT(OnActionDrawXforms(bool)), Qt::QueuedConnection);
connect(ui.ActionDrawImage, SIGNAL(triggered(bool)), this, SLOT(OnActionDrawImage(bool)), Qt::QueuedConnection);
connect(ui.ActionDrawGrid, SIGNAL(triggered(bool)), this, SLOT(OnActionDrawGrid(bool)), Qt::QueuedConnection);
}
/// <summary>
/// GUI wrapper functions, getters only.
/// </summary>
bool Fractorium::DrawXforms() { return ui.ActionDrawXforms->isChecked(); }
bool Fractorium::DrawImage() { return ui.ActionDrawImage->isChecked(); }
bool Fractorium::DrawGrid() { return ui.ActionDrawGrid->isChecked(); }
/// <summary>
/// Called when the CPU render option on the toolbar is clicked.
/// </summary>
@ -80,7 +92,11 @@ void Fractorium::OnActionDP(bool checked)
/// <param name="checked">Ignored</param>
void Fractorium::OnActionStyle(bool checked)
{
#ifdef __APPLE__
m_QssDialog->exec();
#else
m_QssDialog->show();
#endif
}
/// <summary>
@ -105,6 +121,42 @@ void Fractorium::OnActionStartStopRenderer(bool checked)
}
}
/// <summary>
/// Toggle whether to show the affines.
/// Called when the editor image button is clicked.
/// </summary>
/// <param name="checked">Check state, show editor if true, else hide.</param>
void Fractorium::OnActionDrawXforms(bool checked)
{
if (!ui.ActionDrawImage->isChecked() && !ui.ActionDrawXforms->isChecked())
ui.ActionDrawImage->setChecked(true);
ui.GLDisplay->update();
}
/// <summary>
/// Toggle whether to show the image.
/// Called when the image button is clicked.
/// </summary>
/// <param name="checked">Check state, show image if true, else hide.</param>
void Fractorium::OnActionDrawImage(bool checked)
{
if (!ui.ActionDrawImage->isChecked() && !ui.ActionDrawXforms->isChecked())
ui.ActionDrawXforms->setChecked(true);
ui.GLDisplay->update();
}
/// <summary>
/// Toggle whether to show the grid.
/// Called when the grid image button is clicked.
/// </summary>
/// <param name="checked">Check state, show grid if true, else hide.</param>
void Fractorium::OnActionDrawGrid(bool checked)
{
ui.GLDisplay->update();
}
/// <summary>
/// Sync options data to the check state of the toolbar buttons.
/// This does not trigger a clicked() event.
@ -139,4 +191,7 @@ void Fractorium::SyncOptionsToToolbar()
ui.ActionSP->setChecked(true);
ui.ActionDP->setChecked(false);
}
ui.ActionDrawGrid->setChecked(m_Settings->ShowGrid());
ui.ActionDrawXforms->setChecked(m_Settings->ShowXforms());
}

View File

@ -70,7 +70,7 @@ void FractoriumEmberController<T>::XaosChanged(int x, int y, double val)
void Fractorium::OnXaosChanged(double d)
{
if (auto senderSpinBox = qobject_cast<DoubleSpinBox*>(this->sender()))
if (auto senderSpinBox = qobject_cast<DoubleSpinBox*>(sender()))
{
auto p = senderSpinBox->property("tableindex").toPoint();
m_Controller->XaosChanged(p.x(), p.y(), d);

View File

@ -9,7 +9,6 @@ void Fractorium::InitXformsAffineUI()
int affinePrec = 6, spinHeight = 20;
double affineStep = 0.01, affineMin = std::numeric_limits<double>::lowest(), affineMax = std::numeric_limits<double>::max();
auto table = ui.PreAffineTable;
connect(ui.LockAffineCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnLockAffineScaleCheckBoxStateChanged(int)), Qt::QueuedConnection);
table->verticalHeader()->setVisible(true);//The designer continually clobbers these values, so must manually set them here.
table->horizontalHeader()->setVisible(true);
table->verticalHeader()->setSectionsClickable(true);
@ -155,20 +154,29 @@ void Fractorium::InitXformsAffineUI()
}
/// <summary>
/// Toggle whether to lock the visual scale of the affine spinners.
/// Called when the user checks LockAffineCheckBox.
/// Set the scale used for drawing the affines to a default value.
/// </summary>
/// <param name="state">True if checked, else false.</param>
template <typename T>
void FractoriumEmberController<T>::LockAffineScaleCheckBoxStateChanged(int state)
void FractoriumEmberController<T>::InitLockedScale()
{
m_LockedScale = m_Ember.m_PixelsPerUnit;
m_LockedScale = (T)std::min<size_t>(m_Ember.m_FinalRasW, m_Ember.m_FinalRasH) / 4.0;
m_LockedX = m_Ember.m_CenterX;
m_LockedY = m_Ember.m_CenterY;
m_Fractorium->ui.GLDisplay->update();
}
void Fractorium::OnLockAffineScaleCheckBoxStateChanged(int state) { m_Controller->LockAffineScaleCheckBoxStateChanged(state); }
/// <summary>
/// Multiply the scale used for drawing the affines by the passed in value.
/// </summary>
/// <param name="value">The value to scale by</param>
template <typename T>
void FractoriumEmberController<T>::ChangeLockedScale(T value)
{
T min_size = 25.0;
m_LockedScale *= value;
if (m_LockedScale < min_size)
m_LockedScale = min_size;
}
/// <summary>
/// Return the value needed to multiply the current scale by to get back to the locked scale.
@ -177,10 +185,7 @@ void Fractorium::OnLockAffineScaleCheckBoxStateChanged(int state) { m_Controller
template <typename T>
T FractoriumEmberController<T>::AffineScaleCurrentToLocked()
{
if (m_Fractorium->ui.LockAffineCheckBox->isChecked())
return LockedScale() / m_Ember.m_PixelsPerUnit;
else
return 1;
return LockedScale() / m_Ember.m_PixelsPerUnit;
}
/// <summary>
@ -190,10 +195,7 @@ T FractoriumEmberController<T>::AffineScaleCurrentToLocked()
template <typename T>
T FractoriumEmberController<T>::AffineScaleLockedToCurrent()
{
if (m_Fractorium->ui.LockAffineCheckBox->isChecked())
return m_Ember.m_PixelsPerUnit / LockedScale();
else
return 1;
return m_Ember.m_PixelsPerUnit / LockedScale();
}
/// <summary>
@ -736,5 +738,5 @@ bool Fractorium::LocalPivot() { return ui.LocalPivotRadio->isChecked();
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
template class FractoriumEmberController<double>;
#endif

View File

@ -18,6 +18,8 @@ void Fractorium::InitXformsColorUI()
connect(ui.XformPaletteRefTable->horizontalHeader(), SIGNAL(sectionResized(int, int, int)), this, SLOT(OnXformRefPaletteResized(int, int, int)), Qt::QueuedConnection);
connect(ui.RandomColorIndicesButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomColorIndicesButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.ToggleColorIndicesButton, SIGNAL(clicked(bool)), this, SLOT(OnToggleColorIndicesButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.RandomColorSpeedButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomColorSpeedButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.ToggleColorSpeedButton, SIGNAL(clicked(bool)), this, SLOT(OnToggleColorSpeedButtonClicked(bool)), Qt::QueuedConnection);
SetupSpinner<DoubleSpinBox, double>(ui.XformColorIndexTable, this, row, 1, m_XformColorIndexSpin, spinHeight, 0, 1, 0.01, SIGNAL(valueChanged(double)), SLOT(OnXformColorIndexChanged(double)), false, 0, 1, 0);
SetupSpinner<DoubleSpinBox, double>(ui.XformColorValuesTable, this, row, 1, m_XformColorSpeedSpin, spinHeight, -1, 1, 0.1, SIGNAL(valueChanged(double)), SLOT(OnXformColorSpeedChanged(double)), true, 0.5, 0.5, 0.5);
SetupSpinner<DoubleSpinBox, double>(ui.XformColorValuesTable, this, row, 1, m_XformOpacitySpin, spinHeight, 0, 1, 0.1, SIGNAL(valueChanged(double)), SLOT(OnXformOpacityChanged(double)), true, 1, 1, 0);
@ -104,6 +106,33 @@ void FractoriumEmberController<T>::ToggleColorIndicesButtonClicked()
}
void Fractorium::OnToggleColorIndicesButtonClicked(bool b) { m_Controller->ToggleColorIndicesButtonClicked(); }
/// <summary>
/// Set all xform color speeds to a random value between 0 and 1, inclusive.
/// Called when the Random Color Speed button is clicked.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::RandomColorSpeedButtonClicked()
{
UpdateXform([&](Xform<T>* xform) { xform->m_ColorSpeed = m_Rand.Frand01<T>(); }, eXformUpdate::UPDATE_ALL);
m_Fractorium->m_XformColorSpeedSpin->SetValueStealth(CurrentXform()->m_ColorSpeed);
}
void Fractorium::OnRandomColorSpeedButtonClicked(bool b) { m_Controller->RandomColorSpeedButtonClicked(); }
/// <summary>
/// Set all xform color speeds to either 0 and 0.5, sequentially toggling.
/// Called when the Toggle Color Speed button is clicked.
/// Resets the rendering process.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::ToggleColorSpeedButtonClicked()
{
char ch = 1;
UpdateXform([&](Xform<T>* xform) { xform->m_ColorSpeed = (T(ch ^= 1) ? 0.5 : 0.0); }, eXformUpdate::UPDATE_ALL);
m_Fractorium->m_XformColorSpeedSpin->SetValueStealth(CurrentXform()->m_ColorSpeed);
}
void Fractorium::OnToggleColorSpeedButtonClicked(bool b) { m_Controller->ToggleColorSpeedButtonClicked(); }
/// <summary>
/// Set the color speed of the selected xforms.
/// Called when xform color speed spinner is changed.
@ -184,7 +213,7 @@ void Fractorium::OnXformRefPaletteResized(int logicalIndex, int oldSize, int new
template <typename T>
QColor FractoriumEmberController<T>::ColorIndexToQColor(double d)
{
v4T entry = m_Ember.m_Palette[Clamp<size_t>(d * COLORMAP_LENGTH_MINUS_1, 0, m_Ember.m_Palette.Size())];
v4F entry = m_Ember.m_Palette[Clamp<size_t>(d * COLORMAP_LENGTH_MINUS_1, 0, m_Ember.m_Palette.Size())];
entry.r *= 255;
entry.g *= 255;
entry.b *= 255;
@ -227,5 +256,5 @@ void Fractorium::SetPaletteTableItem(QPixmap* pixmap, QTableWidget* table, QTabl
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;
template class FractoriumEmberController<double>;
#endif

View File

@ -35,7 +35,7 @@ template <typename T>
GLEmberController<T>::GLEmberController(Fractorium* fractorium, GLWidget* glWidget, FractoriumEmberController<T>* controller)
: GLEmberControllerBase(fractorium, glWidget)
{
GridStep = T(1.0 / 8.0);
GridStep = T(1.0 / 4.0); // michel, needs to insert on GUI to be flexible//TODO
m_FractoriumEmberController = controller;
m_HoverXform = nullptr;
m_SelectedXform = nullptr;

View File

@ -112,9 +112,8 @@ public:
void CalcDragXAxis();
void CalcDragYAxis();
void CalcDragTranslation();
void SetEmber(Ember<T>* ember);
void SetSelectedXform(Xform<T>* xform);
void DrawGrid();
void DrawAffine(Xform<T>* xform, bool pre, bool selected);
int UpdateHover(v3T& glCoords);
bool CheckXformHover(Xform<T>* xform, v3T& glCoords, T& bestDist, bool pre, bool post);
@ -136,7 +135,6 @@ private:
v3T m_MouseWorldPos;
v3T m_MouseDownWorldPos;
v3T m_DragHandlePos;
v3T m_DragHandleOffset;
v3T m_HoverHandlePos;
m4T m_Modelview;

View File

@ -135,8 +135,8 @@ void GLEmberController<T>::ClearWindow()
{
auto ember = m_FractoriumEmberController->CurrentEmber();
m_GL->makeCurrent();
m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0);
m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
/// <summary>
@ -209,7 +209,17 @@ void GLWidget::paintGL()
{
auto renderer = controller->Renderer();
m_Drawing = true;
GLController()->DrawImage();
if (m_Fractorium->DrawImage())
{
GLController()->DrawImage();
}
else
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
//Affine drawing.
bool pre = m_Fractorium->ui.PreAffineGroupBox->isChecked();
bool post = m_Fractorium->ui.PostAffineGroupBox->isChecked();
@ -249,8 +259,8 @@ void GLEmberController<T>::DrawImage()
{
auto renderer = m_FractoriumEmberController->Renderer();
auto ember = m_FractoriumEmberController->CurrentEmber();
m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_GL->glClearColor(ember->m_Background.r, ember->m_Background.g, ember->m_Background.b, 1.0);
m_GL->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_GL->glDisable(GL_DEPTH_TEST);
renderer->EnterFinalAccum();//Lock, may not be necessary, but just in case.
renderer->EnterResize();
@ -276,16 +286,16 @@ void GLEmberController<T>::DrawImage()
template <typename T>
void GLEmberController<T>::DrawAffines(bool pre, bool post)
{
if (!m_Fractorium->DrawXforms() || (m_DragState == eDragState::DragRotateScale))
return;
QueryVMP();//Resolves to float or double specialization function depending on T.
auto ember = m_FractoriumEmberController->CurrentEmber();
bool dragging = m_DragState == eDragState::DragDragging;
//Draw grid if control key is pressed.
if (m_GL->hasFocus() && GetControl())
{
m_GL->glLineWidth(1.0f);
m_GL->DrawGrid(m_FractoriumEmberController->AffineScaleLockedToCurrent());
}
if ((m_GL->hasFocus() && GetControl()) || m_Fractorium->DrawGrid())
DrawGrid();
//When dragging, only draw the selected xform's affine and hide all others.
if (!m_Fractorium->m_Settings->ShowAllXforms() && dragging)
@ -376,7 +386,7 @@ bool GLEmberControllerBase::KeyPress_(QKeyEvent* e)
}
/// <summary>
/// Call controller KeyPress_().
/// Call controller KeyPress().
/// </summary>
/// <param name="e">The event</param>
void GLWidget::keyPressEvent(QKeyEvent* e)
@ -466,15 +476,7 @@ void GLEmberController<T>::MousePress(QMouseEvent* e)
m_DragSrcTransforms.push_back(m_AffineType == eAffineType::AffinePre ? xform->m_Affine : xform->m_Post);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Don't update renderer here.
m_DragHandlePos = m_HoverHandlePos;//The location in local coordinates of the point selected on the spinner, x, y or center.
m_DragHandleOffset = m_DragHandlePos - m_MouseWorldPos;//The distance in world coordinates from the point selected to the center of the spinner.
m_DragState = eDragState::DragDragging;
//Draw large yellow dot on select or drag.
m_GL->glPointSize(6.0f);
m_GL->glBegin(GL_POINTS);
m_GL->glColor4f(1.0f, 1.0f, 0.5f, 1.0f);
m_GL->glVertex2f(m_DragHandlePos.x, m_DragHandlePos.y);
m_GL->glEnd();
m_GL->glPointSize(1.0f);//Restore point size.
m_GL->repaint();
}
else//Nothing was selected.
@ -491,13 +493,16 @@ void GLEmberController<T>::MousePress(QMouseEvent* e)
}
else if (e->button() == Qt::RightButton)//Right button does whole image rotation and scaling.
{
UpdateHover(mouseFlipped);
m_SelectedXform = m_HoverXform;
m_CenterDownX = ember->m_CenterX;//Capture these because they will change when rotating and scaling.
m_CenterDownY = ember->m_CenterY;
m_RotationDown = ember->m_Rotate;
m_ScaleDown = ember->m_PixelsPerUnit;
m_DragState = eDragState::DragRotateScale;
if (m_Fractorium->DrawImage())
{
UpdateHover(mouseFlipped);
m_SelectedXform = m_HoverXform;
m_CenterDownX = ember->m_CenterX;//Capture these because they will change when rotating and scaling.
m_CenterDownY = ember->m_CenterY;
m_RotationDown = ember->m_Rotate;
m_ScaleDown = ember->m_PixelsPerUnit;
m_DragState = eDragState::DragRotateScale;
}
}
}
}
@ -535,7 +540,7 @@ void GLEmberController<T>::MouseRelease(QMouseEvent* e)
m_DragState = eDragState::DragNone;
m_DragModifier = 0;
m_GL->repaint();//Force immediate redraw.
m_GL->update();
}
/// <summary>
@ -670,15 +675,27 @@ void GLWidget::mouseMoveEvent(QMouseEvent* e)
/// will zoom in the image in our out, while sacrificing quality.
/// If the user needs to preserve quality, they can use the zoom spinner
/// on the main window.
/// If Alt is pressed, only the scale of the affines is changed, the scale of the image remains untouched.
/// </summary>
/// <param name="e">The event</param>
template <typename T>
void GLEmberController<T>::Wheel(QWheelEvent* e)
{
auto ember = m_FractoriumEmberController->CurrentEmber();
if ((e->modifiers() & Qt::AltModifier) && m_Fractorium->DrawXforms())
{
m_FractoriumEmberController->ChangeLockedScale(e->angleDelta().x() >= 0 ? 1.0981 : 0.9);
m_GL->update();
}
else
{
if (m_Fractorium->DrawImage())
{
auto ember = m_FractoriumEmberController->CurrentEmber();
if (m_Fractorium && !(e->buttons() & Qt::MiddleButton))//Middle button does whole image translation, so ignore the mouse wheel while panning to avoid inadvertent zooming.
m_Fractorium->SetScale(ember->m_PixelsPerUnit + (e->angleDelta().y() >= 0 ? 50 : -50));
if (!(e->buttons() & Qt::MiddleButton))//Middle button does whole image translation, so ignore the mouse wheel while panning to avoid inadvertent zooming.
m_Fractorium->SetScale(ember->m_PixelsPerUnit + (e->angleDelta().y() >= 0 ? 50 : -50));
}
}
}
/// <summary>
@ -799,76 +816,6 @@ bool GLEmberController<T>::SizesMatch()
m_GL->m_TexHeight == m_GL->m_ViewHeight);
}
/// <summary>
/// Draw the grid in response to the control key being pressed.
/// The frequency of the grid lines will change depending on the zoom.
/// Calculated with the frame always centered, the renderer just moves the camera.
/// </summary>
/// <param name="scale">A value to scale by, used when locking the affine scale</param>
void GLWidget::DrawGrid(double scale)
{
auto renderer = m_Fractorium->m_Controller->Renderer();
float unitX = std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f;
float unitY = std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f;
float rad = std::max(unitX * scale, unitY * scale);
float xLow = floor(-unitX);
float xHigh = ceil(unitX);
float yLow = floor(-unitY);
float yHigh = ceil(unitY);
glBegin(GL_LINES);
if (rad <= 8.0f)
{
glColor4f(0.5f, 0.5f, 0.5f, 0.5f);
for (float fx = xLow; fx <= xHigh; fx += GridStep)
{
glVertex2f(fx, yLow);
glVertex2f(fx, yHigh);
}
for (float fy = yLow; fy < yHigh; fy += GridStep)
{
glVertex2f(xLow, fy);
glVertex2f(xHigh, fy);
}
}
unitX *= scale;
unitY *= scale;
if (unitX <= 64.0f)
{
glColor4f(0.5f, 0.5f, 0.5f, 1.0f);
for (float fx = xLow; fx <= xHigh; fx += 1.0f)
{
glVertex2f(fx, yLow);
glVertex2f(fx, yHigh);
}
for (float fy = yLow; fy < yHigh; fy += 1.0f)
{
glVertex2f(xLow, fy);
glVertex2f(xHigh, fy);
}
}
glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
glVertex2f(0.0f, 0.0f);
glVertex2f(xHigh, 0.0f);
glColor4f(0.5f, 0.0f, 0.0f, 1.0f);
glVertex2f(0.0f, 0.0f);
glVertex2f(xLow, 0.0f);
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
glVertex2f(0.0f, 0.0f);
glVertex2f(0.0f, yHigh);
glColor4f(0.0f, 0.5f, 0.0f, 1.0f);
glVertex2f(0.0f, 0.0f);
glVertex2f(0.0f, yLow);
glEnd();
}
/// <summary>
/// Draw the unit square.
/// </summary>
@ -894,6 +841,60 @@ void GLWidget::DrawUnitSquare()
glEnd();
}
/// <summary>
/// Draw the grid
/// The frequency of the grid lines will change depending on the zoom (ALT+WHEEL).
/// Calculated with the frame always centered, the renderer just moves the camera.
/// </summary>
template <typename T>
void GLEmberController<T>::DrawGrid()
{
auto renderer = m_Fractorium->m_Controller->Renderer();
double scale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
float unitX = (std::abs(renderer->UpperRightX(false) - renderer->LowerLeftX(false)) / 2.0f) / scale;
float unitY = (std::abs(renderer->UpperRightY(false) - renderer->LowerLeftY(false)) / 2.0f) / scale;
float xLow = floor(-unitX);
float xHigh = ceil(unitX);
float yLow = floor(-unitY);
float yHigh = ceil(unitY);
float alpha = 0.25f;
Affine2D<T> temp;
m4T mat = (temp * scale).ToMat4RowMajor();
m_GL->glPushMatrix();
m_GL->glLoadIdentity();
MultMatrix(mat);
m_GL->glLineWidth(1.0f);
m_GL->glBegin(GL_LINES);
m_GL->glColor4f(0.5f, 0.5f, 0.5f, alpha);
for (float fx = xLow; fx <= xHigh; fx += GridStep)
{
m_GL->glVertex2f(fx, yLow);
m_GL->glVertex2f(fx, yHigh);
}
for (float fy = yLow; fy < yHigh; fy += GridStep)
{
m_GL->glVertex2f(xLow, fy);
m_GL->glVertex2f(xHigh, fy);
}
m_GL->glColor4f(1.0f, 0.0f, 0.0f, alpha);
m_GL->glVertex2f(0.0f, 0.0f);
m_GL->glVertex2f(xHigh, 0.0f);
m_GL->glColor4f(0.5f, 0.0f, 0.0f, alpha);
m_GL->glVertex2f(0.0f, 0.0f);
m_GL->glVertex2f(xLow, 0.0f);
m_GL->glColor4f(0.0f, 1.0f, 0.0f, alpha);
m_GL->glVertex2f(0.0f, 0.0f);
m_GL->glVertex2f(0.0f, yHigh);
m_GL->glColor4f(0.0f, 0.5f, 0.0f, alpha);
m_GL->glVertex2f(0.0f, 0.0f);
m_GL->glVertex2f(0.0f, yLow);
m_GL->glEnd();
m_GL->glPopMatrix();
}
/// <summary>
/// Draw the pre or post affine circle for the passed in xform.
/// For drawing affine transforms, multiply the identity model view matrix by the
@ -1028,42 +1029,45 @@ int GLEmberController<T>::UpdateHover(v3T& glCoords)
auto ember = m_FractoriumEmberController->CurrentEmber();
m_HoverType = eHoverType::HoverNone;
//If there's a selected/current xform, check it first so it gets precedence over the others.
if (m_SelectedXform)
if (m_Fractorium->DrawXforms())//Don't bother checking anything if the user wants to see no xforms.
{
//These checks prevent highlighting the pre/post selected xform circle, when one is set to show all, and the other
//is set to show current, and the user hovers over another xform, but doesn't select it, then moves the mouse
//back over the hidden circle for the pre/post that was set to only show current.
bool checkSelPre = preAll || (pre && m_HoverXform == m_SelectedXform);
bool checkSelPost = postAll || (post && m_HoverXform == m_SelectedXform);
if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkSelPre, checkSelPost))
//If there's a selected/current xform, check it first so it gets precedence over the others.
if (m_SelectedXform)
{
m_HoverXform = m_SelectedXform;
bestIndex = int(ember->GetTotalXformIndex(m_SelectedXform));
}
}
//These checks prevent highlighting the pre/post selected xform circle, when one is set to show all, and the other
//is set to show current, and the user hovers over another xform, but doesn't select it, then moves the mouse
//back over the hidden circle for the pre/post that was set to only show current.
bool checkSelPre = preAll || (pre && m_HoverXform == m_SelectedXform);
bool checkSelPost = postAll || (post && m_HoverXform == m_SelectedXform);
//Check all xforms.
for (int i = 0; i < int(ember->TotalXformCount()); i++)
{
auto xform = ember->GetTotalXform(i);
if (preAll || (pre && m_HoverXform == xform))//Only check pre affine if they are shown.
{
if (CheckXformHover(xform, glCoords, bestDist, true, false))
if (CheckXformHover(m_SelectedXform, glCoords, bestDist, checkSelPre, checkSelPost))
{
m_HoverXform = xform;
bestIndex = i;
m_HoverXform = m_SelectedXform;
bestIndex = int(ember->GetTotalXformIndex(m_SelectedXform));
}
}
if (postAll || (post && m_HoverXform == xform))//Only check post affine if they are shown.
//Check all xforms.
for (int i = 0; i < int(ember->TotalXformCount()); i++)
{
if (CheckXformHover(xform, glCoords, bestDist, false, true))
auto xform = ember->GetTotalXform(i);
if (preAll || (pre && m_HoverXform == xform))//Only check pre affine if they are shown.
{
m_HoverXform = xform;
bestIndex = i;
if (CheckXformHover(xform, glCoords, bestDist, true, false))
{
m_HoverXform = xform;
bestIndex = i;
}
}
if (postAll || (post && m_HoverXform == xform))//Only check post affine if they are shown.
{
if (CheckXformHover(xform, glCoords, bestDist, false, true))
{
m_HoverXform = xform;
bestIndex = i;
}
}
}
}
@ -1207,18 +1211,17 @@ template <typename T>
void GLEmberController<T>::CalcDragXAxis()
{
size_t index = 0;
auto scale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
auto scaleBack = m_FractoriumEmberController->AffineScaleCurrentToLocked();
auto affineToWorldScale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
auto worldToAffineScale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
bool pre = m_AffineType == eAffineType::AffinePre;
bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
auto startDiff = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();
T startAngle = std::atan2(startDiff.y, startDiff.x);
if (GetShift())
{
auto posOffset = m_MouseWorldPos + m_DragHandleOffset;
v3T snapped = GetControl() ? SnapToNormalizedAngle(posOffset, 24u) : posOffset;
auto startDiff = (v2T(m_MouseDownWorldPos) * scale) - m_DragSrcTransform.O();
auto endDiff = (v2T(snapped) * scale) - m_DragSrcTransform.O();
T startAngle = std::atan2(startDiff.y, startDiff.x);
v3T snapped = GetControl() ? SnapToNormalizedAngle(m_MouseWorldPos, 24u) : m_MouseWorldPos;
auto endDiff = (v2T(snapped) * affineToWorldScale) - m_DragSrcTransform.O();
T endAngle = std::atan2(endDiff.y, endDiff.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
@ -1245,32 +1248,47 @@ void GLEmberController<T>::CalcDragXAxis()
}
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T((affine.O() + affine.X()) * scaleBack, 0);
m_DragHandlePos = v3T((affine.O() + affine.X()) * worldToAffineScale, 0);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
}
else
{
v3T diff;
auto posOffset = m_MouseWorldPos + m_DragHandleOffset;
v3T diff = m_MouseWorldPos - m_MouseDownWorldPos;
auto diffscale = diff * affineToWorldScale;
auto origmag = Zeps(glm::length(m_DragSrcTransform.X()));
auto origXPlusOff = v3T(m_DragSrcTransform.X(), 0) + diffscale;
if (GetControl())
diff = SnapToGrid(posOffset) - m_MouseDownWorldPos;
else
diff = posOffset - m_MouseDownWorldPos;
{
auto o3 = v3T(m_DragSrcTransform.O(), 0);
auto o3x = origXPlusOff + o3;
origXPlusOff = SnapToGrid(o3x);
origXPlusOff -= o3;
}
auto origXPlusOff = v3T(m_DragSrcTransform.X(), 0) + (diff * scale);
auto newmag = glm::length(origXPlusOff);
auto newprc = newmag / origmag;
auto endDiff = (v2T(origXPlusOff) * affineToWorldScale);
T endAngle = std::atan2(endDiff.y, endDiff.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
auto axis = v3T(m_DragSrcTransforms[index++].X(), 0) + (diff * scale);
auto src = m_DragSrcTransforms[index++];
if (GetAlt())
{
affine.X(v2T(origXPlusOff));//Absolute, not ratio.
}
else
affine.RotateScaleXTo(v2T(axis));
{
src.ScaleXY(newprc);
src.Rotate(angle);
affine = src;
}
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T((affine.O() + affine.X()) * scaleBack, 0);
m_DragHandlePos = v3T((affine.O() + affine.X()) * worldToAffineScale, 0);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);
}
}
@ -1297,18 +1315,17 @@ template <typename T>
void GLEmberController<T>::CalcDragYAxis()
{
size_t index = 0;
auto scale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
auto scaleBack = m_FractoriumEmberController->AffineScaleCurrentToLocked();
auto affineToWorldScale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
auto worldToAffineScale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
bool pre = m_AffineType == eAffineType::AffinePre;
bool worldPivotShiftAlt = !m_Fractorium->LocalPivot() && GetShift() && GetAlt();
auto startDiff = (v2T(m_HoverHandlePos) * affineToWorldScale) - m_DragSrcTransform.O();
T startAngle = std::atan2(startDiff.y, startDiff.x);
if (GetShift())
{
auto posOffset = m_MouseWorldPos + m_DragHandleOffset;
v3T snapped = GetControl() ? SnapToNormalizedAngle(posOffset, 24u) : posOffset;
auto startDiff = (v2T(m_MouseDownWorldPos) * scale) - m_DragSrcTransform.O();
auto endDiff = (v2T(snapped) * scale) - m_DragSrcTransform.O();
T startAngle = std::atan2(startDiff.y, startDiff.x);
v3T snapped = GetControl() ? SnapToNormalizedAngle(m_MouseWorldPos, 24u) : m_MouseWorldPos;
auto endDiff = (v2T(snapped) * affineToWorldScale) - m_DragSrcTransform.O();
T endAngle = std::atan2(endDiff.y, endDiff.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
@ -1335,32 +1352,47 @@ void GLEmberController<T>::CalcDragYAxis()
}
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T((affine.O() + affine.Y()) * scaleBack, 0);
m_DragHandlePos = v3T((affine.O() + affine.Y()) * worldToAffineScale, 0);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
}
else
{
v3T diff;
auto posOffset = m_MouseWorldPos + m_DragHandleOffset;
v3T diff = m_MouseWorldPos - m_MouseDownWorldPos;
auto diffscale = diff * affineToWorldScale;
auto origmag = Zeps(glm::length(m_DragSrcTransform.Y()));
auto origYPlusOff = v3T(m_DragSrcTransform.Y(), 0) + diffscale;
if (GetControl())
diff = SnapToGrid(posOffset) - m_MouseDownWorldPos;
else
diff = posOffset - m_MouseDownWorldPos;
{
auto o3 = v3T(m_DragSrcTransform.O(), 0);
auto o3y = origYPlusOff + o3;
origYPlusOff = SnapToGrid(o3y);
origYPlusOff -= o3;
}
auto origXPlusOff = v3T(m_DragSrcTransform.Y(), 0) + (diff * scale);
auto newmag = glm::length(origYPlusOff);
auto newprc = newmag / origmag;
auto endDiff = (v2T(origYPlusOff) * affineToWorldScale);
T endAngle = std::atan2(endDiff.y, endDiff.x);
T angle = startAngle - endAngle;
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
auto axis = v3T(m_DragSrcTransforms[index++].Y(), 0) + (diff * scale);
auto src = m_DragSrcTransforms[index++];
if (GetAlt())
affine.Y(v2T(origXPlusOff));//Absolute, not ratio.
{
affine.Y(v2T(origYPlusOff));//Absolute, not ratio.
}
else
affine.RotateScaleYTo(v2T(axis));
{
src.ScaleXY(newprc);
src.Rotate(angle);
affine = src;
}
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T((affine.O() + affine.Y()) * scaleBack, 0);
m_DragHandlePos = v3T((affine.O() + affine.Y()) * worldToAffineScale, 0);
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);
}
}
@ -1382,8 +1414,8 @@ template <typename T>
void GLEmberController<T>::CalcDragTranslation()
{
size_t index = 0;
auto scale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
auto scaleBack = m_FractoriumEmberController->AffineScaleCurrentToLocked();
auto affineToWorldScale = m_FractoriumEmberController->AffineScaleLockedToCurrent();
auto worldToAffineScale = m_FractoriumEmberController->AffineScaleCurrentToLocked();
bool worldPivotShift = !m_Fractorium->LocalPivot() && GetShift();
bool pre = m_AffineType == eAffineType::AffinePre;
@ -1409,7 +1441,7 @@ void GLEmberController<T>::CalcDragTranslation()
affine.O(srcRotated.O());
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T(srcRotated.O(), 0) * scaleBack;
m_DragHandlePos = v3T(srcRotated.O(), 0) * worldToAffineScale;
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);//Calling code will update renderer.
}
else
@ -1421,20 +1453,24 @@ void GLEmberController<T>::CalcDragTranslation()
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
auto offset = m_DragSrcTransforms[index++].O() + (scale * v2T(diff));
auto offset = m_DragSrcTransforms[index++].O() + (affineToWorldScale * v2T(diff));
auto snapped = SnapToGrid(offset);
affine.O(v2T(snapped.x, snapped.y));
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T(affine.O(), 0) * worldToAffineScale;
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);
m_DragHandlePos = SnapToGrid(m_MouseWorldPos);
}
else
{
m_FractoriumEmberController->UpdateXform([&](Xform<T>* xform)
{
auto& affine = pre ? xform->m_Affine : xform->m_Post;
affine.O(m_DragSrcTransforms[index++].O() + (scale * v2T(diff)));
affine.O(m_DragSrcTransforms[index++].O() + (affineToWorldScale * v2T(diff)));
if (xform == m_FractoriumEmberController->CurrentXform())
m_DragHandlePos = v3T(affine.O(), 0) * worldToAffineScale;
}, eXformUpdate::UPDATE_CURRENT_AND_SELECTED, false);
m_DragHandlePos = m_MouseWorldPos;
}
}
}
@ -1454,5 +1490,5 @@ GLEmberControllerBase* GLWidget::GLController()
template class GLEmberController<float>;
#ifdef DO_DOUBLE
template class GLEmberController<double>;
template class GLEmberController<double>;
#endif

View File

@ -11,8 +11,6 @@ class GLEmberControllerBase;
template<typename T> class GLEmberController;
template<typename T> class FractoriumEmberController;
static const float GridStep = 1.0f / 8.0f;
/// <summary>
/// The main drawing area.
/// This uses the Qt wrapper around OpenGL to draw the output of the render to a texture whose
@ -68,7 +66,6 @@ private:
bool Allocate(bool force = false);
bool Deallocate();
void SetViewport();
void DrawGrid(double scale);
void DrawUnitSquare();
void DrawAffineHelper(int index, bool selected, bool pre, bool final, bool background);
GLEmberControllerBase* GLController();

View File

@ -10,6 +10,7 @@
/// <returns>0 if successful, else 1.</returns>
int main(int argc, char* argv[])
{
int rv = -1;
QApplication a(argc, argv);
#ifdef TEST_CL
QMessageBox::critical(QApplication::desktop(), "Error", "Fractorium cannot be run in test mode, undefine TEST_CL first.");
@ -19,16 +20,9 @@ int main(int argc, char* argv[])
QMessageBox::critical(QApplication::desktop(), "Error", "Fractorium cannot be run in test mode, undefine ISAAC_FLAM3_DEBUG first.");
return 1;
#endif
//Required for large allocs, else GPU memory usage will be severely limited to small sizes.
//This must be done in the application and not in the EmberCL DLL.
#ifdef _WIN32
_putenv_s("GPU_MAX_ALLOC_PERCENT", "100");
#else
putenv(const_cast<char*>("GPU_MAX_ALLOC_PERCENT=100"));
#endif
int rv = -1;
auto vf = VarFuncs<float>::Instance();//Create instances that will stay alive until the program exits.
auto vlf = VariationList<float>::Instance();
auto settings = FractoriumSettings::Instance();
#ifdef DO_DOUBLE
auto vd = VarFuncs<float>::Instance();
auto vld = VariationList<double>::Instance();//No further creations should occur after this.
@ -36,6 +30,13 @@ int main(int argc, char* argv[])
try
{
//Required for large allocs, else GPU memory usage will be severely limited to small sizes.
//This must be done in the application and not in the EmberCL DLL.
#ifdef _WIN32
_putenv_s("GPU_MAX_ALLOC_PERCENT", "100");
#else
putenv(const_cast<char*>("GPU_MAX_ALLOC_PERCENT=100"));
#endif
Fractorium w;
w.show();
a.installEventFilter(&w);
@ -43,11 +44,11 @@ int main(int argc, char* argv[])
}
catch (const std::exception& e)
{
QMessageBox::critical(0, "Fatal Error", QString::fromStdString(e.what()));
QMessageBox::critical(nullptr, "Fatal Error", QString::fromStdString(e.what()));
}
catch (const char* e)
{
QMessageBox::critical(0, "Fatal Error", e);
QMessageBox::critical(nullptr, "Fatal Error", e);
}
return rv;

View File

@ -0,0 +1,357 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>PaletteEditor</class>
<widget class="QDialog" name="PaletteEditor">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>942</width>
<height>542</height>
</rect>
</property>
<property name="windowTitle">
<string>Palette Editor</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QVBoxLayout" name="LeftSideVertialLayout">
<property name="spacing">
<number>4</number>
</property>
<item>
<layout class="QHBoxLayout" name="LeftTopHorizontalLayout">
<item>
<widget class="QPushButton" name="NewPaletteFileButton">
<property name="text">
<string>New Palette File...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="CopyPaletteFileButton">
<property name="text">
<string>Copy Palette File</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QComboBox" name="PaletteFilenameCombo"/>
</item>
<item>
<widget class="QTableWidget" name="PaletteListTable">
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Plain</enum>
</property>
<property name="autoScroll">
<bool>false</bool>
</property>
<property name="editTriggers">
<set>QAbstractItemView::AllEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectItems</enum>
</property>
<property name="horizontalScrollMode">
<enum>QAbstractItemView::ScrollPerPixel</enum>
</property>
<property name="cornerButtonEnabled">
<bool>false</bool>
</property>
<property name="columnCount">
<number>2</number>
</property>
<attribute name="horizontalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderDefaultSectionSize">
<number>16</number>
</attribute>
<attribute name="verticalHeaderHighlightSections">
<bool>false</bool>
</attribute>
<attribute name="verticalHeaderMinimumSectionSize">
<number>16</number>
</attribute>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Palette</string>
</property>
</column>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="LeftBottomHorizontalLayout">
<item>
<widget class="QPushButton" name="AppendPaletteButton">
<property name="text">
<string>Append Palette</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="OverwritePaletteButton">
<property name="text">
<string>Overwrite Palette</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="DeletePaletteButton">
<property name="text">
<string>Delete Palette</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="MainVerticalLayout">
<item>
<widget class="QGroupBox" name="ColorViewGroupBox">
<property name="title">
<string>Palette</string>
</property>
<property name="flat">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="ColorPickerGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Minimum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="title">
<string>Color Picker</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="ControlsGroupBox">
<property name="title">
<string>Adjustment Controls</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,4">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>6</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>6</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<item>
<layout class="QGridLayout" name="AdjustmentLeftGridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="SyncCheckBox">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Sync</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QSpinBox" name="ArrowsSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::NoButtons</enum>
</property>
<property name="suffix">
<string/>
</property>
<property name="prefix">
<string>Arrows: </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>256</number>
</property>
<property name="value">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="AutoDistributeCheckBox">
<property name="text">
<string>Auto Distribute</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="AdjustmentRightGridLayout">
<item row="1" column="2">
<widget class="QPushButton" name="CreatePaletteFromImageButton">
<property name="text">
<string>Create From Image</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="ResetColorsButton">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="QPushButton" name="RandomColorsButton">
<property name="text">
<string>Random Colors</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="RemoveColorButton">
<property name="text">
<string>Remove Color</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="DistributeColorsButton">
<property name="text">
<string>Distribute Colors</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QPushButton" name="AddColorButton">
<property name="text">
<string>Add Color</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="InvertColorsButton">
<property name="text">
<string>Invert Colors</string>
</property>
</widget>
</item>
<item row="1" column="3">
<widget class="QPushButton" name="CreatePaletteAgainFromImageButton">
<property name="text">
<string>Create Again From Image</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>PaletteEditor</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>PaletteEditor</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -0,0 +1,79 @@
/*
Copyright (C) 2009, Etienne Moutot <e.moutot@gmail.com>
This file is part of colorPickerWidget.
colorPickerWidget 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.
colorPickerWidget 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 Foobar. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FractoriumPch.h"
#include "ColorPanel.h"
/// <summary>
/// Constructor which passes parent widget to the base and initializes the minimum size.
/// </summary>
/// <param name="p">The parent widget</param>
ColorPanel::ColorPanel(QWidget* p)
: QPushButton(p)
{
setMinimumSize(10, 10);
}
/// <summary>
/// Derived paint event which paints the entire button surface with m_Color.
/// </summary>
void ColorPanel::paintEvent(QPaintEvent*)
{
QPainter p(this);
p.setPen(m_Pen);
p.setBrush(QBrush(m_Color));
p.drawRect(QRect(2, 2, width() - 6, height() - 6));
}
/// <summary>
/// Set the pen object used to paint the button.
/// </summary>
/// <param name="pen">The pen object used to paint the button</param>
void ColorPanel::Pen(const QPen& pen)
{
m_Pen = pen;
update();
}
/// <summary>
/// Get the pen object used to paint the button.
/// </summary>
/// <returns>QPen</returns>
QPen ColorPanel::Pen() const
{
return m_Pen;
}
/// <summary>
/// Set the color used to paint the button.
/// </summary>
/// <param name="color">The color used to paint the button</param>
void ColorPanel::Color(const QColor& color)
{
m_Color = color;
update();
}
/// <summary>
/// Get the color used to paint the button.
/// </summary>
/// <returns>QColor</returns>
QColor ColorPanel::Color() const
{
return m_Color;
}

View File

@ -0,0 +1,47 @@
/*
Copyright (C) 2009, Etienne Moutot <e.moutot@gmail.com>
This file is part of colorPickerWidget.
colorPickerWidget 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.
colorPickerWidget 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 Foobar. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// A derivation of a QPushButton which makes a large, clickable panel
/// which custom paints the button based on a user specified color.
/// </summary>
class ColorPanel : public QPushButton
{
Q_OBJECT
public:
ColorPanel(QWidget* p = nullptr);
void Pen(const QPen& pen);
QPen Pen() const;
void Color(const QColor& color);
QColor Color() const;
protected:
virtual void paintEvent(QPaintEvent* event) override;
private:
QPen m_Pen;
QColor m_Color;
};

View File

@ -0,0 +1,96 @@
/*
Copyright (C) 2009, Etienne Moutot <e.moutot@gmail.com>
This file is part of colorPickerWidget.
colorPickerWidget 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.
colorPickerWidget 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 Foobar. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FractoriumPch.h"
#include "ColorPickerWidget.h"
/// <summary>
/// Constructor which passes parent widget to the base and initializes the member controls
/// on a grid layout.
/// </summary>
/// <param name="p">The parent widget</param>
ColorPickerWidget::ColorPickerWidget(QWidget* p)
: QWidget(p)
{
m_ColorTriangle = new ColorTriangle(this);
m_ColorPanel = new ColorPanel(this);
m_ColorDialog = new QColorDialog(this);
m_ColorPanel->Color(m_ColorTriangle->Color());
connect(m_ColorTriangle, SIGNAL(ColorChanged(const QColor&)), this, SLOT(OnTriangleColorChanged(const QColor&)));
connect(m_ColorPanel, SIGNAL(clicked()), this, SLOT(OnColorViewerClicked()));
auto layout = new QGridLayout(this);
layout->setMargin(4);
layout->addWidget(m_ColorTriangle, 0, 0, 3, 1);
layout->addWidget(m_ColorPanel, 0, 1, 3, 1);
setLayout(layout);
}
/// <summary>
/// Get the color used to paint the color panel.
/// </summary>
/// <returns>QColor</returns>
QColor ColorPickerWidget::Color() const
{
return m_ColorPanel->Color();
}
/// <summary>
/// Set the current color for the triangle.
/// </summary>
/// <param name="col">The parent widget</param>
void ColorPickerWidget::SetColorPanelColor(const QColor& col)
{
if (col.isValid())
m_ColorTriangle->Color(col);//Internally emits ColorChanged() which will call OnTriangleColorChanged(), which will call m_ColorPanel->Color().
}
/// <summary>
/// Overridden resize event to set the color panel height slightly
/// smaller than the container it's in.
/// </summary>
/// <param name="col">The resize event</param>
void ColorPickerWidget::resizeEvent(QResizeEvent* event)
{
m_ColorPanel->setMinimumHeight(event->size().height() - 22);
m_ColorPanel->setMaximumHeight(event->size().height() - 22);
}
/// <summary>
/// Slot called when the color panel is clicked, which will show the color
/// picker dialog.
/// </summary>
void ColorPickerWidget::OnColorViewerClicked()
{
m_ColorDialog->setCurrentColor(m_ColorPanel->Color());
auto newColor = m_ColorDialog->getColor(m_ColorPanel->Color(), this);
SetColorPanelColor(newColor);
}
/// <summary>
/// Slot called when the color on the triangle changes for any reason,
/// either user initiated or programatically called.
/// </summary>
/// <param name="col">The new color on the triangle</param>
void ColorPickerWidget::OnTriangleColorChanged(const QColor& col)
{
if (col.isValid())
m_ColorPanel->Color(col);
emit ColorChanged(col);
}

View File

@ -0,0 +1,54 @@
/*
Copyright (C) 2009, Etienne Moutot <e.moutot@gmail.com>
This file is part of colorPickerWidget.
colorPickerWidget 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.
colorPickerWidget 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 Foobar. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "FractoriumPch.h"
#include "ColorTriangle.h"
#include "ColorPanel.h"
/// <summary>
/// Aggregator class to package a color triangle, color panel, and color dialog
/// all together on a layout.
/// </summary>
class ColorPickerWidget : public QWidget
{
Q_OBJECT
public:
ColorPickerWidget(QWidget* p = nullptr);
QColor Color() const;
void SetColorPanelColor(const QColor& col);
Q_SIGNALS:
void ColorChanged(const QColor& col);
protected:
void resizeEvent(QResizeEvent* event) override;
private slots:
void OnColorViewerClicked();
void OnTriangleColorChanged(const QColor& col);
private:
ColorTriangle* m_ColorTriangle;
ColorPanel* m_ColorPanel;
QColorDialog* m_ColorDialog;
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,138 @@
/****************************************************************************
**
** This file is part of a Qt Solutions component.
**
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Qt Software Information (qt-info@nokia.com)
**
** Commercial Usage
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Solutions Commercial License Agreement provided
** with the Software or, alternatively, in accordance with the terms
** contained in a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, Nokia gives you certain
** additional rights. These rights are described in the Nokia Qt LGPL
** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
** package.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3.0 as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL included in the
** packaging of this file. Please review the following information to
** ensure the GNU General Public License version 3.0 requirements will be
** met: http://www.gnu.org/copyleft/gpl.html.
**
** Please note Third Party Software included with Qt Solutions may impose
** additional restrictions and it is the user's responsibility to ensure
** that they have met the licensing requirements of the GPL, LGPL, or Qt
** Solutions Commercial license and the relevant license of the Third
** Party Software they are using.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at qt-sales@nokia.com.
**
****************************************************************************/
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// DoubleColor, Vertex and ColorTriangle classes.
/// </summary>
/// <summary>
/// Used to store color values in the range 0..255 as doubles.
/// </summary>
struct DoubleColor
{
double r, g, b;
DoubleColor() : r(0.0), g(0.0), b(0.0) {}
DoubleColor(double red, double green, double blue) : r(red), g(green), b(blue) {}
DoubleColor(const DoubleColor& c) : r(c.r), g(c.g), b(c.b) {}
};
/// <summary>
/// Used to store pairs of DoubleColor and DoublePoint in one structure.
/// </summary>
struct Vertex
{
DoubleColor color;
QPointF point;
Vertex(const DoubleColor& c, const QPointF& p) : color(c), point(p) {}
Vertex(const QColor& c, const QPointF& p)
: color(DoubleColor((double)c.red(), (double)c.green(),
(double)c.blue())), point(p) {}
};
/// <summary>
/// Widget for drawing a color triangle which allows users to select colors
/// in a manner more intuitive than the usual color picker dialog.
/// This class was taken from an open source project named Chaos Helper, which took
/// it from the Qt examples, so it mostly remains as-is.
/// </summary>
class ColorTriangle : public QWidget
{
Q_OBJECT
public:
ColorTriangle(QWidget* parent = nullptr);
void Polish();
QColor Color() const;
void Color(const QColor& col);
virtual int heightForWidth(int w) const override;
virtual QSize sizeHint() const override;
signals:
void ColorChanged(const QColor& col);
protected:
virtual void paintEvent(QPaintEvent*) override;
virtual void mouseMoveEvent(QMouseEvent*) override;
virtual void mousePressEvent(QMouseEvent*) override;
virtual void mouseReleaseEvent(QMouseEvent*) override;
virtual void keyPressEvent(QKeyEvent* e) override;
virtual void resizeEvent(QResizeEvent*) override;
private:
void GenBackground();
void DrawTrigon(QImage* p, const QPointF& a, const QPointF& b, const QPointF& c, const QColor& color);
double CalcOuterRadius() const;
double RadiusAt(const QPointF& pos, const QRect& rect) const;
double AngleAt(const QPointF& pos, const QRect& rect) const;
QColor ColorFromPoint(const QPointF& p) const;
QPointF PointFromColor(const QColor& col) const;
QPointF MovePointToTriangle(double x, double y, const Vertex& a, const Vertex& b, const Vertex& c) const;
bool mustGenerateBackground;
int curHue;
int penWidth;
int ellipseSize;
int outerRadius;
double a, b, c;
QImage bg;
QColor curColor;
QPointF pa, pb, pc, pd;
QPointF selectorPos;
enum SelectionMode
{
Idle,
SelectingHue,
SelectingSatValue
} selMode;
};

View File

@ -0,0 +1,78 @@
/****************************************************************************/
// This file is part of the gradLib library originally made by Stian Broen
//
// For more free libraries, please visit <http://broentech.no>
//
// gradLib 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 library. If not, see <http://www.gnu.org/licenses/>
/****************************************************************************/
#pragma once
#include "FractoriumPch.h"
/// <summary>
/// Class for drawing the small arrows below the gradient in the palette editor.
/// The drawing is accomplished via a QPolygon object.
/// </summary>
class GradientArrow
{
public:
/// <summary>
/// Default constructor which sets up the size of the arrow.
/// </summary>
explicit GradientArrow()
{
QPolygon area;
area << QPoint(5, 5) << QPoint(10, 0) << QPoint(15, 5) << QPoint(15, 15) << QPoint(5, 15) << QPoint(5, 5);
Area(area);
}
/// <summary>
/// Constructor which takes the color and focus state of the arrow.
/// </summary>
/// <param name="col">The color of the arrow</param>
/// <param name="focus">Whether the arrow is focused</param>
explicit GradientArrow(QColor col, bool focus)
: GradientArrow()
{
m_Color = col;
m_Focus = focus;
}
/// <summary>
/// Copy constructor to copy another GradientArrow.
/// </summary>
/// <param name="other">The GradientArrow object to copy</param>
GradientArrow(const GradientArrow& other)
: m_Focus(other.Focus()),
m_Area(other.Area()),
m_Color(other.Color())
{
}
/// <summary>
/// Getters and setters.
/// </summary>
inline bool Focus() const { return m_Focus; }
inline void Focus(bool val) { m_Focus = val; }
inline const QPolygon Area() const { return m_Area; }
inline void Area(const QPolygon& val) {m_Area = val; }
inline const QColor Color() const { return m_Color; }
inline void Color(const QColor& val) { m_Color = val; }
private:
bool m_Focus;
QPolygon m_Area;
QColor m_Color;
};

View File

@ -0,0 +1,630 @@
/****************************************************************************/
// This file is part of the gradLib library originally made by Stian Broen
//
// For more free libraries, please visit <http://broentech.no>
//
// gradLib 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 library. If not, see <http://www.gnu.org/licenses/>
/****************************************************************************/
#include "FractoriumPch.h"
#include "GradientColorsView.h"
/// <summary>
/// Constructor which passes parent widget to the base and sets various size constraints.
/// </summary>
/// <param name="p">The parent widget</param>
GradientColorsView::GradientColorsView(QWidget* p)
: QWidget(p)
{
m_ViewRect = QRect(QPoint(0, 0), QPoint(0, 0));
qRegisterMetaType<GradientArrow>("GradientArrow");
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
setFocusPolicy(Qt::StrongFocus);
setMinimumSize(p->width() - 10, p->height() - 10);
setMouseTracking(true);
ResetToDefault();
}
/// <summary>
/// Set the focus to the arrow at the given normalized position.
/// </summary>
/// <param name="position">The normalized position of the arrow to focus</param>
void GradientColorsView::SetFocus(float position)
{
bool focused = false;
for (auto& it : m_Arrows)
{
focused |= position == it.first;
it.second.Focus(position == it.first);
}
if (!focused)
m_Arrows.begin()->second.Focus(true);
update();
}
/// <summary>
/// Set the focus to the arrow at the given index.
/// </summary>
/// <param name="position">The index of the arrow to focus</param>
void GradientColorsView::SetFocus(size_t position)
{
bool focused = false;
size_t index = 0;
position = std::min(m_Arrows.size() - 1, position);
for (auto& it : m_Arrows)
{
bool b = position == index++;
focused |= b;
it.second.Focus(b);
}
if (!focused)
m_Arrows.begin()->second.Focus(true);
update();
}
/// <summary>
/// Set the color of the currently focused arrow to the passed in color.
/// </summary>
/// <param name="color">The color to set the focused arrow to</param>
void GradientColorsView::SetFocusColor(const QColor& color)
{
for (auto& it : m_Arrows)
{
auto& anArrow = it.second;
if (anArrow.Focus())
{
anArrow.Color(color);
update();
break;
}
}
}
/// <summary>
/// Add an arrow whose color will be assigned the passed in color.
/// </summary>
/// <param name="color">The color to assign to the new arrow</param>
void GradientColorsView::AddArrow(const QColor& color)
{
float position = 0.5f;
if (m_Arrows.size() >= 256)
return;
if (m_Arrows.empty())
{
position = 0;
}
else if (m_Arrows.size() == 1)
{
position = (m_Arrows.begin()->first < 1) ? 1 : 0;
}
else if (m_Arrows.size() == 2)
{
auto b = m_Arrows.begin();
auto rb = m_Arrows.rbegin();
position = std::abs((rb->first + b->first) / 2.0);
if (position == b->first)
position = b->first / 2.0;
else if (position == rb->first)
position = (1.0 + rb->first) / 2.0;
}
else
{
bool set = false;
auto it = m_Arrows.begin();
auto oneBeforeLast = Advance(m_Arrows.begin(), m_Arrows.size() - 1);
for (; it != oneBeforeLast; ++it)
{
if (it->second.Focus())
{
auto next = Advance(it, 1);
position = std::abs((next->first + it->first) / 2.0);
set = true;
break;
}
}
if (!set)
{
it = m_Arrows.begin();
position = std::abs((Advance(it, 1)->first + it->first) / 2.0);
}
}
AddArrow(position, color);
}
/// <summary>
/// Add an arrow whose position and color will be assigned the values passed in.
/// If an arrow exists at the specified position, it is overwritten.
/// </summary>
/// <param name="position">The position to place the new arrow in</param>
/// <param name="color">The color to assign to the new arrow</param>
void GradientColorsView::AddArrow(float position, const QColor& color)
{
GradientArrow arrow;
arrow.Focus(true);
arrow.Color(color);
m_Arrows[position] = arrow;
SetFocus(position);
update();
}
/// <summary>
/// Delete the currently focused arrow if there are more than 2 arrows.
/// Set the focus to the arrow whose index is one greater than the one deleted.
/// </summary>
void GradientColorsView::DeleteFocusedArrow()
{
if (m_Arrows.size() <= 2)
return;
size_t index = 0;
for (auto it = m_Arrows.begin(); it != m_Arrows.end(); ++it)
{
if (it->second.Focus())
{
m_Arrows.erase(it);
break;
}
index++;
}
SetFocus(index);
update();
}
/// <summary>
/// Invert the values of all colors by subtracting each component from 255.
/// </summary>
void GradientColorsView::InvertColors()
{
for (auto& it : m_Arrows)
{
auto& arrow = it.second;
auto col = arrow.Color();
arrow.Color(QColor(255 - col.red(), 255 - col.green(), 255 - col.blue()));
if (arrow.Focus())
emit ArrowDoubleClicked(arrow);
}
update();
}
/// <summary>
/// Set each component of each color to a random value between 0 and 255 inclusive.
/// </summary>
void GradientColorsView::RandomColors()
{
for (auto& it : m_Arrows)
it.second.Color(
{
int(QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(256)),
int(QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(256)),
int(QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand(256))
});
update();
}
/// <summary>
/// Set the distance between each arrow to be equal.
/// </summary>
void GradientColorsView::DistributeColors()
{
map<float, GradientArrow> arrows;
float index = 0, inc = 1.0f / std::max<size_t>(size_t(1), m_Arrows.size() - 1);
for (auto it : m_Arrows)
{
arrows[index] = it.second;
index = std::min(1.0f, index + inc);
}
m_Arrows = std::move(arrows);
update();
}
/// <summary>
/// Delete all arrows and add a white arrow at index 0, and a black
/// arrow at index 1.
/// </summary>
void GradientColorsView::ResetToDefault()
{
ClearArrows();
AddArrow(0.0, Qt::white);
AddArrow(1.0, Qt::black);
}
/// <summary>
/// Clear all arrows.
/// </summary>
void GradientColorsView::ClearArrows()
{
m_Arrows.clear();
}
/// <summary>
/// Set the arrow at the specified index to the specified color, and also
/// focus it.
/// </summary>
/// <param name="color">The color to assign to the arrow at the specified index</param>
/// <param name="index">The index of the arrow to assign the color to and focus</param>
void GradientColorsView::NewFocusColor(const QColor& color, int index)
{
int i = 0;
for (auto& kv : m_Arrows)
{
auto& arrow = kv.second;
if (i == index)
{
arrow.Color(color);
arrow.Focus(true);
update();
}
else
arrow.Focus(false);
kv.second = arrow;
i++;
}
}
/// <summary>
/// Set the arrow map to the passed in one.
/// </summary>
/// <param name="newArrows">The new arrows to assign to the internal m_Arrows member</param>
void GradientColorsView::SetArrows(map<float, GradientArrow>& newArrows)
{
m_Arrows = newArrows;
update();
}
/// <summary>
/// Get the number of arrows in the map.
/// </summary>
/// <returns>int</returns>
int GradientColorsView::ArrowCount()
{
return int(m_Arrows.size());
}
/// <summary>
/// Get the index of the focused arrow.
/// Return 0 if none are focused.
/// </summary>
/// <returns>The focused index if at least one arrow is focused, else 0.</returns>
int GradientColorsView::GetFocusedIndex()
{
int index = 0;
for (auto& kv : m_Arrows)
{
if (kv.second.Focus())
break;
index++;
}
return index;
}
/// <summary>
/// Return a pixmap to be used to draw the palette.
/// The pixmap is lazily instantiated on the first call, and all subsequent
/// calls return a pointer to the same pixmap.
/// </summary>
/// <returns>The pixmap</returns>
QPixmap* GradientColorsView::GetBackGround()
{
if (!m_Background.get())
CreateBackground(m_BackgroundVerSpace, m_BackgroundHorSpace);
return m_Background.get();
}
/// <summary>
/// Return a reference to the arrows map.
/// Be very careful what you do with this.
/// </summary>
/// <returns>A reference to the internal map containing the arrows</returns>
map<float, GradientArrow>& GradientColorsView::GetArrows()
{
return m_Arrows;
}
/// <summary>
/// Populate the palette member with the specified number of elements based on
/// interpolating the values in the arrows and return a reference to it.
/// </summary>
/// <param name="size">The number of elements the palette will have</param>
/// <returns>A reference to the internal map containing the arrows</returns>
Palette<float>& GradientColorsView::GetPalette(int size)
{
QSize imageSize(size, 1);
QImage image(imageSize, QImage::Format_ARGB32_Premultiplied);
QPainter p;
QLinearGradient grad(QPoint(0, 0), QPoint(imageSize.width(), imageSize.height()));
m_Palette.m_SourceColors.clear();
for (auto& it : m_Arrows)
{
auto pos = it.first;
auto col = it.second.Color();
m_Palette.m_SourceColors[pos] = v4F(col.red() / 255.0f, col.green() / 255.0f, col.blue() / 255.0f, 1.0f);
grad.setColorAt(pos, col);
}
p.begin(&image);
p.fillRect(image.rect(), grad);
p.end();
m_Palette.m_Entries.reserve(image.width());
for (int i = 0; i < image.width(); i++)
{
QColor col(image.pixel(i, 0));
m_Palette[i].r = col.red() / 255.0f;
m_Palette[i].g = col.green() / 255.0f;
m_Palette[i].b = col.blue() / 255.0f;
}
return m_Palette;
}
/// <summary>
/// Assign the values of the m_SourceColors member of the palette to the
/// internal map of arrows. Note this assignment will only take place if
/// the number of source colors is 2 or more.
/// This will only be the case if it was a user created palette made here.
/// All palettes gotten from elsewhere are not assignable.
/// </summary>
/// <param name="palette">The palette whose source colors will be assigned to the arrow map</param>
void GradientColorsView::SetPalette(const Palette<float>& palette)
{
if (palette.m_SourceColors.size() > 1)
{
m_Palette = palette;
m_Arrows.clear();
for (auto& col : m_Palette.m_SourceColors)
{
auto& rgb = col.second;
m_Arrows[col.first] = GradientArrow(QColor(rgb.r * 255, rgb.g * 255, rgb.b * 255), false);
}
SetFocus(size_t(0));
update();
}
}
/// <summary>
/// Custom paint event to draw the palette and arrows.
/// </summary>
void GradientColorsView::paintEvent(QPaintEvent*)
{
if (m_ViewRect.size().isNull() ||
m_ViewRect.size().isEmpty() ||
m_ViewRect.topLeft() == m_ViewRect.bottomRight())
{
m_ViewRect = QRect(QPoint(5, 0), QPoint(width() - 15, height() / 3 * 2 - 10));
m_ViewRect.translate(5, 5);
CreateBackground();
}
QPainter painter(this);
if (m_Background.get())
painter.drawPixmap(m_ViewRect, *m_Background.get(), m_ViewRect);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.setRenderHint(QPainter::Antialiasing);
QPoint gradStart = QPoint(m_ViewRect.topLeft().x(), m_ViewRect.bottomLeft().y() / 2);
QPoint gradStop = QPoint(m_ViewRect.topRight().x(), m_ViewRect.bottomRight().y() / 2);
QLinearGradient grad(gradStart, gradStop);
for (auto& it : m_Arrows)
{
GradientArrow& arrow = it.second;
grad.setColorAt(it.first, arrow.Color());
QPolygon arrowPolygon = arrow.Area();
int iPosX = it.first * (width() - 20),
iPosY = height() / 3 * 2;
arrowPolygon.translate(iPosX, iPosY);
QPainterPath paintPath;
paintPath.addPolygon(arrowPolygon);
painter.setBrush(QBrush(arrow.Color()));
if (arrow.Focus())
paintPath.addRect(iPosX + 5, iPosY + 20, 10, 5);
painter.drawPath(paintPath);
painter.setBrush(QBrush(Qt::NoBrush));
}
QBrush brush(grad);
painter.fillRect(m_ViewRect, brush);
painter.drawRect(m_ViewRect);
painter.end();
}
/// <summary>
/// Event for detecting when the mouse is pressed on an arrow to begin dragging.
/// </summary>
/// <param name="e">The mouse event</param>
void GradientColorsView::mousePressEvent(QMouseEvent* e)
{
m_DragStart = e->pos();
for (auto& it : m_Arrows)
{
auto& arrow = it.second;
QPolygon poly = arrow.Area();
poly.translate(it.first * (width() - 20), height() / 3 * 2);
if (poly.containsPoint(m_DragStart, Qt::OddEvenFill))
{
m_ArrowMoving = true;
arrow.Focus(true);
}
else
arrow.Focus(false);
}
update();
}
/// <summary>
/// Event for detecting when the mouse is pressed on an arrow to begin dragging.
/// </summary>
/// <param name="event">The mouse event</param>
void GradientColorsView::mouseDoubleClickEvent(QMouseEvent* e)
{
for (auto& it : m_Arrows)
{
auto& arrow = it.second;
QPolygon poly = arrow.Area();
poly.translate(it.first * (width() - 20), height() / 3 * 2);
if (poly.containsPoint(e->pos(), Qt::OddEvenFill))
{
arrow.Focus(true);
emit ArrowDoubleClicked(arrow);
}
else
arrow.Focus(false);
}
}
/// <summary>
/// Event for detecting when the mouse is moving during dragging.
/// </summary>
/// <param name="event">The mouse event</param>
void GradientColorsView::mouseMoveEvent(QMouseEvent* e)
{
if (!m_ArrowMoving) return;
size_t index = 0;
qreal maxMove = 11.5 / (width() - 20);
for (auto it = m_Arrows.begin(); it != m_Arrows.end(); ++it)
{
auto& arrow = it->second;
if (arrow.Focus())
{
qreal lastPos = it->first;
qreal start = m_DragStart.x();
qreal end = width() - 20;
qreal dPos = ((qreal) e->pos().x() - start) / end;
qreal newPos = lastPos + dPos;
if ( (it->first + dPos > 1) || (it->first + dPos < 0) )
return;
if (dPos < 0 && index > 0)
{
qreal posBefore = std::prev(it)->first;
if ( (lastPos - maxMove + dPos) <= posBefore )
return;
}
if ((dPos > 0) && (index < (m_Arrows.size() - 1)))
{
qreal posAfter = std::next(it)->first;
if ((lastPos + maxMove + dPos) >= posAfter)
return;
}
GradientArrow arrowCopy(it->second);
m_Arrows.erase(lastPos);
m_Arrows[newPos] = arrowCopy;
emit ArrowMove(lastPos, arrow);
break;
}
index++;
}
m_DragStart = e->pos();
update();
}
/// <summary>
/// Event for detecting when the mouse is released during dragging.
/// </summary>
void GradientColorsView::mouseReleaseEvent(QMouseEvent*)
{
m_ArrowMoving = false;
}
/// <summary>
/// Event for custom drawing the viewable area when its resized.
/// </summary>
void GradientColorsView::resizeEvent(QResizeEvent*)
{
m_ViewRect = QRect(QPoint(5, 0), QPoint(width() - 15, height() / 3 * 2 - 10));
m_ViewRect.translate(5, 5);
}
/// <summary>
/// Create the background to represent the palette.
/// </summary>
/// <param name="vertLineSpace">The space between vertical lines to use</param>
/// <param name="horLineSpace">The space between horizontal lines to use</param>
void GradientColorsView::CreateBackground(int vertLineSpace, int horLineSpace)
{
m_BackgroundVerSpace = vertLineSpace;
m_BackgroundHorSpace = horLineSpace;
m_Background = make_unique<QPixmap>(QSize(800, 800));
m_Background->fill(Qt::white);
QPainter painter(m_Background.get());
int x = 0;
while (x < m_Background->width())//Veritcal lines.
{
const QPoint lineStart(x, 0);
const QPoint lineStop(x, m_Background->height());
painter.drawLine(lineStart, lineStop);
x += vertLineSpace;
}
int y = 0;
while (y < m_Background->height())//Horizontal lines.
{
const QPoint lineStart(0, y);
const QPoint lineStop(m_Background->width(), y);
painter.drawLine(lineStart, lineStop);
y += horLineSpace;
}
painter.end();
update();
}

View File

@ -0,0 +1,80 @@
/****************************************************************************/
// This file is part of the gradLib library originally made by Stian Broen
//
// For more free libraries, please visit <http://broentech.no>
//
// gradLib 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 library. If not, see <http://www.gnu.org/licenses/>
/****************************************************************************/
#pragma once
#include "FractoriumPch.h"
#include "GradientArrow.h"
/// <summary>
/// Class for drawing the resulting palette created by interpolating the key colors
/// as well as the arrows underneath the palette.
/// The arrows are held in a sorted map whose key is the normalized index of the arrow,
/// between 0 and 1 inclusive. They value is the arrow itself.
/// The resulting palette is always stored in the m_Palette member.
/// </summary>
class GradientColorsView : public QWidget
{
Q_OBJECT
public:
explicit GradientColorsView(QWidget* p = nullptr);
void SetFocus(float position);
void SetFocus(size_t position);
void SetFocusColor(const QColor& col);
void AddArrow(const QColor& color);
void AddArrow(float position, const QColor& color);
void DeleteFocusedArrow();
void InvertColors();
void RandomColors();
void DistributeColors();
void ResetToDefault();
void ClearArrows();
void NewFocusColor(const QColor& col, int index);
void SetArrows(map<float, GradientArrow>& newArrows);
int ArrowCount();
int GetFocusedIndex();
QPixmap* GetBackGround();
map<float, GradientArrow>& GetArrows();
Palette<float>& GetPalette(int size);
void SetPalette(const Palette<float>& palette);
signals:
void ArrowMove(qreal lastPos, const GradientArrow& arrow);
void ArrowDoubleClicked(const GradientArrow& arrow);
protected:
virtual void paintEvent(QPaintEvent* e) override;
virtual void mousePressEvent(QMouseEvent* e) override;
virtual void mouseDoubleClickEvent(QMouseEvent* e) override;
virtual void mouseMoveEvent(QMouseEvent* e) override;
virtual void mouseReleaseEvent(QMouseEvent* e) override;
virtual void resizeEvent(QResizeEvent*) override;
private:
void CreateBackground(int vertLineSpace = 5, int horLineSpace = 5);
bool m_ArrowMoving = false;
int m_BackgroundVerSpace = 5;
int m_BackgroundHorSpace = 5;
QRect m_ViewRect;
QPoint m_DragStart;
unique_ptr<QPixmap> m_Background;
map<float, GradientArrow> m_Arrows;
Palette<float> m_Palette;
};

View File

@ -0,0 +1,498 @@
#include "FractoriumPch.h"
#include "PaletteEditor.h"
#include "ui_paletteeditor.h"
/// <summary>
/// Constructor which passes parent widget to the base and sets up slots and other ui
/// elements.
/// This takes a reference to a palette list, which it then searches for user created palettes
/// and only populates the combo box with those.
/// </summary>
/// <param name="paletteList">A reference to an existing palette list, gotten from the main window.</param>
/// <param name="p">The parent widget</param>
PaletteEditor::PaletteEditor(PaletteList<float>& paletteList, QWidget* p) :
QDialog(p),
ui(make_unique<Ui::PaletteEditor>()),
m_PaletteList(paletteList)
{
ui->setupUi(this);
m_ColorPicker = new ColorPickerWidget(this);
m_ColorPicker->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
m_ColorPicker->SetColorPanelColor(Qt::black);
QVBoxLayout* colorLayout = new QVBoxLayout();
colorLayout->setMargin(3);
colorLayout->addWidget(m_ColorPicker);
ui->ColorPickerGroupBox->setLayout(colorLayout);
ui->ColorPickerGroupBox->setContentsMargins(3, 8, 3, 3);
m_GradientColorView = new GradientColorsView(ui->ColorViewGroupBox);
connect(m_ColorPicker, SIGNAL(ColorChanged(const QColor&)), this, SLOT(OnColorPickerColorChanged(const QColor&)));
connect(m_GradientColorView, SIGNAL(ArrowMove(qreal, const GradientArrow&)), this, SLOT(OnArrowMoved(qreal, const GradientArrow&)));
connect(m_GradientColorView, SIGNAL(ArrowDoubleClicked(const GradientArrow&)), this, SLOT(OnArrowDoubleClicked(const GradientArrow&)));
connect(ui->CreatePaletteFromImageButton, SIGNAL(clicked()), this, SLOT(OnCreatePaletteFromImageButtonClicked()));
connect(ui->CreatePaletteAgainFromImageButton, SIGNAL(clicked()), this, SLOT(OnCreatePaletteAgainFromImageButton()));
connect(ui->AddColorButton, SIGNAL(clicked()), this, SLOT(OnAddColorButtonClicked()));
connect(ui->RemoveColorButton, SIGNAL(clicked()), this, SLOT(OnRemoveColorButtonClicked()));
connect(ui->InvertColorsButton, SIGNAL(clicked()), this, SLOT(OnInvertColorsButtonClicked()));
connect(ui->ResetColorsButton, SIGNAL(clicked()), this, SLOT(OnResetToDefaultButtonClicked()));
connect(ui->DistributeColorsButton, SIGNAL(clicked()), this, SLOT(OnDistributeColorsButtonClicked()));
connect(ui->RandomColorsButton, SIGNAL(clicked()), this, SLOT(OnRandomColorsButtonClicked()));
connect(ui->SyncCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSyncCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui->PaletteFilenameCombo, SIGNAL(currentIndexChanged(const QString&)), this, SLOT(OnPaletteFilenameComboChanged(const QString&)), Qt::QueuedConnection);
connect(ui->PaletteListTable, SIGNAL(cellClicked(int, int)), this, SLOT(OnPaletteCellClicked(int, int)), Qt::QueuedConnection);
connect(ui->PaletteListTable, SIGNAL(cellChanged(int, int)), this, SLOT(OnPaletteCellChanged(int, int)), Qt::QueuedConnection);
connect(ui->NewPaletteFileButton, SIGNAL(clicked()), this, SLOT(OnNewPaletteFileButtonClicked()));
connect(ui->CopyPaletteFileButton, SIGNAL(clicked()), this, SLOT(OnCopyPaletteFileButtonClicked()));
connect(ui->AppendPaletteButton, SIGNAL(clicked()), this, SLOT(OnAppendPaletteButtonClicked()));
connect(ui->OverwritePaletteButton, SIGNAL(clicked()), this, SLOT(OnOverwritePaletteButtonClicked()));
connect(ui->DeletePaletteButton, SIGNAL(clicked()), this, SLOT(OnDeletePaletteButtonClicked()));
ui->PaletteListTable->horizontalHeader()->setSectionsClickable(true);
auto layout = new QVBoxLayout();
layout->setMargin(0);
layout->setSpacing(0);
layout->addWidget(m_GradientColorView);
ui->ColorViewGroupBox->setLayout(layout);
auto& pals = m_PaletteList.Palettes();
for (auto& pal : pals)
{
if (m_PaletteList.IsModifiable(pal.first))//Only add user created palettes.
{
QFileInfo info(QString::fromStdString(pal.first));
ui->PaletteFilenameCombo->addItem(info.fileName());
}
}
if (ui->PaletteFilenameCombo->count() > 0)
m_CurrentPaletteFilePath = ui->PaletteFilenameCombo->itemText(0).toStdString();
}
/// <summary>
/// Get whether change events here are propagated back to the main window.
/// </summary>
/// <returns>bool</returns>
bool PaletteEditor::Sync()
{
return ui->SyncCheckBox->isChecked();
}
/// <summary>
/// Get a pointer to the palette pixmap from the underlying gradient color view.
/// </summary>
/// <returns>QPixmap*</returns>
QPixmap* PaletteEditor::GetBackGround()
{
return m_GradientColorView->GetBackGround();
}
/// <summary>
/// Populate and retrieve a reference to the palette from the underlying gradient color view
/// using the specified number of elements.
/// </summary>
/// <param name="size">The number of elements the palette will have</param>
/// <returns>A freference to the palette</returns>
Palette<float>& PaletteEditor::GetPalette(int size)
{
return m_GradientColorView->GetPalette(size);
}
/// <summary>
/// Set the palette of the underlying gradient color view.
/// Note this assignment will only take place if
/// the number of source colors is 2 or more.
/// This will only be the case if it was a user created palette made here.
/// All palettes gotten from elsewhere are not assignable.
/// </summary>
/// <param name="palette">The palette to assign</param>
void PaletteEditor::SetPalette(Palette<float>& palette)
{
if (palette.m_SourceColors.size() > 1)
{
m_PaletteIndex = std::numeric_limits<int>::max();
m_GradientColorView->SetPalette(palette);
auto& arrows = m_GradientColorView->GetArrows();
if (!arrows.empty())
m_ColorPicker->SetColorPanelColor(arrows.begin()->second.Color());
}
}
/// <summary>
/// Add a new arrow using the current color.
/// Called when the Add Color button is clicked.
/// </summary>
void PaletteEditor::OnAddColorButtonClicked()
{
AddArrow(m_ColorPicker->Color());
EmitPaletteChanged();
}
/// <summary>
/// Delete the focused arrow and optionally redistribute the arrows.
/// Called when the Remove Color button is clicked.
/// </summary>
void PaletteEditor::OnRemoveColorButtonClicked()
{
m_GradientColorView->DeleteFocusedArrow();
if (ui->AutoDistributeCheckBox->isChecked())
m_GradientColorView->DistributeColors();
EmitPaletteChanged();
}
/// <summary>
/// Invert the colors of the arrows.
/// Called when the Invert Colors button is clicked.
/// </summary>
void PaletteEditor::OnInvertColorsButtonClicked()
{
m_GradientColorView->InvertColors();
EmitPaletteChanged();
}
/// <summary>
/// Randomize the colors of the arrows.
/// Called when the Random Colors button is clicked.
/// </summary>
void PaletteEditor::OnRandomColorsButtonClicked()
{
m_GradientColorView->RandomColors();
EmitPaletteChanged();
}
/// <summary>
/// Set the distance between each arrow to be equal.
/// Called when the Distribute Colors button is clicked.
/// </summary>
void PaletteEditor::OnDistributeColorsButtonClicked()
{
m_GradientColorView->DistributeColors();
EmitPaletteChanged();
}
/// <summary>
/// Delete all arrows and add a white arrow at index 0, and a black
/// arrow at index 1.
/// Called when the Reset button is clicked.
/// </summary>
void PaletteEditor::OnResetToDefaultButtonClicked()
{
m_GradientColorView->ResetToDefault();
EmitPaletteChanged();
}
/// <summary>
/// Create a palette by opening an image and selecting the colors from
/// pixels at random locations.
/// Ensure arrows spin box has a value of at least two.
/// Called when the From Image button is clicked.
/// </summary>
void PaletteEditor::OnCreatePaletteFromImageButtonClicked()
{
auto filenames = SetupOpenImagesDialog();
if (!filenames.empty())
{
m_Filename = filenames[0];
if (ui->ArrowsSpinBox->value() < 2)
ui->ArrowsSpinBox->setValue(2);
auto colors = GetRandomColorsFromImage(m_Filename, ui->ArrowsSpinBox->value());
m_GradientColorView->SetArrows(colors);
EmitPaletteChanged();
}
}
/// <summary>
/// Create a palette by re-opening the previously selected image and selecting the colors from
/// pixels at random locations.
/// Ensure arrows spin box has a value of at least two.
/// Called when the From Image Again button is clicked.
/// </summary>
void PaletteEditor::OnCreatePaletteAgainFromImageButton()
{
if (QFile::exists(m_Filename))
{
if (ui->ArrowsSpinBox->value() < 2)
ui->ArrowsSpinBox->setValue(2);
auto colors = GetRandomColorsFromImage(m_Filename, ui->ArrowsSpinBox->value());
m_GradientColorView->SetArrows(colors);
EmitPaletteChanged();
}
}
/// <summary>
/// Set the focus color as a result of selecting a stock in the color picker.
/// Called when the color picker signals the ColorChanged event.
/// </summary>
void PaletteEditor::OnColorPickerColorChanged(const QColor& col)
{
m_GradientColorView->SetFocusColor(col);
EmitPaletteChanged();
}
/// <summary>
/// Set the color panel color as a result of double clicking an arrow.
/// Called when the color view signals the ArrowDoubleClicked event.
/// </summary>
/// <param name="arrow">The arrow which was double clicked on</param>
void PaletteEditor::OnArrowDoubleClicked(const GradientArrow& arrow)
{
m_ColorPicker->SetColorPanelColor(arrow.Color());
}
/// <summary>
/// Change whether palette changes are synced with the main window.
/// Called when the Sync checkbox is checked/unchecked.
/// </summary>
/// <param name="state">Ignored</param>
void PaletteEditor::OnSyncCheckBoxStateChanged(int state)
{
EmitPaletteChanged();
}
/// <summary>
/// Load the palette file based on the currently selected index in the combo box.
/// Called when the index of the palette filename combo box changes.
/// </summary>
/// <param name="text">The text of the combo box, which is just the palette filename without the path.</param>
void PaletteEditor::OnPaletteFilenameComboChanged(const QString& text)
{
if (!text.isEmpty())//This occasionally seems to get called with an empty string for reasons unknown.
{
auto paletteTable = ui->PaletteListTable;
m_CurrentPaletteFilePath = text.toStdString();
::FillPaletteTable(text.toStdString(), paletteTable, m_PaletteList);
auto fullname = m_PaletteList.GetFullPathFromFilename(m_CurrentPaletteFilePath);
ui->PaletteFilenameCombo->setToolTip(QString::fromStdString(fullname));
OnPaletteCellClicked(0, 1);
}
}
/// <summary>
/// Load the palette into the editor controls.
/// Called when the second column in a row in the palette list is clicked.
/// </summary>
/// <param name="row">The table row clicked</param>
/// <param name="col">The table column clicked</param>
void PaletteEditor::OnPaletteCellClicked(int row, int col)
{
if (col == 1)
{
if (auto palette = m_PaletteList.GetPaletteByFilename(m_CurrentPaletteFilePath, row))
{
SetPalette(*palette);
m_PaletteIndex = row;
}
}
}
/// <summary>
/// Update the name of the palette.
/// Called when the first column in a row in the palette list is clicked and edited.
/// </summary>
/// <param name="row">The table row clicked</param>
/// <param name="col">The table column clicked</param>
void PaletteEditor::OnPaletteCellChanged(int row, int col)
{
if (col == 0)
{
if (auto palette = m_PaletteList.GetPaletteByFilename(m_CurrentPaletteFilePath, row))
{
palette->m_Name = ui->PaletteListTable->item(row, col)->text().toStdString();
emit PaletteFileChanged();
}
}
}
/// <summary>
/// Create a new palette file.
/// The newly created file will have a unique name.
/// Called when the new palette file button is clicked.
/// </summary>
void PaletteEditor::OnNewPaletteFileButtonClicked()
{
auto filename = EmberFile<float>::UniqueFilename(GetDefaultUserPath() + "/user-palettes.xml");
if (m_PaletteList.AddEmptyPaletteFile(filename.toStdString()))
{
QFileInfo info(filename);
ui->PaletteFilenameCombo->addItem(info.fileName());
ui->PaletteFilenameCombo->setCurrentIndex(ui->PaletteFilenameCombo->count() - 1);
}
}
/// <summary>
/// Copy the current palette file, add it to the combo box and load the new palette file.
/// The newly created file will have a unique name.
/// Called when the copy palette file button is clicked.
/// </summary>
void PaletteEditor::OnCopyPaletteFileButtonClicked()
{
auto& paletteFiles = m_PaletteList.Palettes();
auto qscurr = QString::fromStdString(m_CurrentPaletteFilePath);
auto qfilename = EmberFile<float>::UniqueFilename(GetDefaultUserPath() + "/" + qscurr);
auto filename = qfilename.toStdString();
if (m_PaletteList.GetPaletteListByFullPath(filename) == nullptr)//Ensure the new filename does not exist in the map.
{
//Get the list of palettes for the current filename, this will be added as a copy below.
if (auto currentPaletteFile = m_PaletteList.GetPaletteListByFilename(m_CurrentPaletteFilePath))//Ensure the list of palettes was properly retrieved.
{
if (m_PaletteList.AddPaletteFile(filename, *currentPaletteFile))//Add the current vector of palettes to an entry with the new filename.
{
QFileInfo info(qfilename);
ui->PaletteFilenameCombo->addItem(info.fileName());
ui->PaletteFilenameCombo->setCurrentIndex(ui->PaletteFilenameCombo->count() - 1);
}
else
QMessageBox::critical(this, "Copy palette file error", "Failed copy palette to " + qfilename + ", because already exists in memory, but not on disk. Did you delete it while the program was running?");
}
else
QMessageBox::critical(this, "Copy palette file error", "The current file " + qscurr + " did not exist. Did you delete it while the program was running?");
}
else
QMessageBox::critical(this, "Copy palette file error", "Failed copy palette to " + qfilename + ", because it likely already exists");
}
/// <summary>
/// Copy the current palette to the end of the palette file.
/// Called when the append palette button is clicked.
/// </summary>
void PaletteEditor::OnAppendPaletteButtonClicked()
{
auto& pal = m_GradientColorView->GetPalette(256);
m_PaletteList.AddPaletteToFile(m_CurrentPaletteFilePath, pal);
::FillPaletteTable(m_CurrentPaletteFilePath, ui->PaletteListTable, m_PaletteList);
m_PaletteIndex = ui->PaletteListTable->rowCount() - 1;
emit PaletteFileChanged();
}
/// <summary>
/// Overwrite the current palette in the palette file.
/// Called when the overwrite palette button is clicked.
/// </summary>
void PaletteEditor::OnOverwritePaletteButtonClicked()
{
auto& pal = m_GradientColorView->GetPalette(256);
m_PaletteList.Replace(m_CurrentPaletteFilePath, pal, m_PaletteIndex);
::FillPaletteTable(m_CurrentPaletteFilePath, ui->PaletteListTable, m_PaletteList);
emit PaletteFileChanged();
}
/// <summary>
/// Delete the current palette from the palette file.
/// Called when the delete palette button is clicked.
/// Note that the palette will not be deleted if it's the only palette in the file.
/// </summary>
void PaletteEditor::OnDeletePaletteButtonClicked()
{
auto table = ui->PaletteListTable;
if (table->rowCount() > 1)
{
m_PaletteList.Delete(m_CurrentPaletteFilePath, m_PaletteIndex);
::FillPaletteTable(m_CurrentPaletteFilePath, table, m_PaletteList);
emit PaletteFileChanged();
OnPaletteCellClicked(0, table->rowCount() - 1);
}
}
/// <summary>
/// Emit a palette changed event.
/// Called when an arrow is moved.
/// </summary>
void PaletteEditor::OnArrowMoved(qreal, const GradientArrow&)
{
EmitPaletteChanged();
}
/// <summary>
/// Emit a palette changed event if the sync checkbox is checked.
/// </summary>
void PaletteEditor::EmitPaletteChanged()
{
if (ui->SyncCheckBox->isChecked())
emit PaletteChanged();
}
/// <summary>
/// Helper to lazily instantiate an open file dialog.
/// Once created, it will remain alive for the duration of the program run.
/// </summary>
QStringList PaletteEditor::SetupOpenImagesDialog()
{
if (!m_FileDialog)
{
m_FileDialog = new QFileDialog(this);
m_FileDialog->setViewMode(QFileDialog::List);
m_FileDialog->setFileMode(QFileDialog::ExistingFile);
m_FileDialog->setAcceptMode(QFileDialog::AcceptOpen);
m_FileDialog->setNameFilter("Image Files (*.png *.jpg *.bmp)");
m_FileDialog->setWindowTitle("Open Image");
m_FileDialog->setDirectory(QCoreApplication::applicationDirPath());
m_FileDialog->selectNameFilter("*.jpg");
}
QStringList filenames;
if (m_FileDialog->exec() == QDialog::Accepted)
{
filenames = m_FileDialog->selectedFiles();
if (!filenames.empty())
m_FileDialog->setDirectory(QFileInfo(filenames[0]).canonicalPath());
}
return filenames;
}
/// <summary>
/// Add an arrow whose color will be assigned the passed in color.
/// Optionally distribute colors and emit a palette changed event.
/// </summary>
/// <param name="color">The color to assign to the new arrow</param>
void PaletteEditor::AddArrow(const QColor& color)
{
auto count = std::min<int>(std::abs(256 - m_GradientColorView->ArrowCount()), ui->ArrowsSpinBox->value());
for (int i = 0; i < count; i++)
{
m_GradientColorView->AddArrow(color);
if (ui->AutoDistributeCheckBox->isChecked())
m_GradientColorView->DistributeColors();
}
}
/// <summary>
/// Helper to get the colors of random pixels within an image.
/// </summary>
/// <param name="filename">The full path to the image file to get random colors from</param>
/// <param name="numPoints">The number of colors to get</param>
map<float, GradientArrow> PaletteEditor::GetRandomColorsFromImage(QString filename, int numPoints)
{
map<float, GradientArrow> colors;
QTime time = QTime::currentTime();
qsrand((uint)time.msec());
QImage image(filename);
const qreal gSize = 512;
float off = 0.0f, inc = 1.0f / std::max(1, numPoints - 1);
QLinearGradient grad(QPoint(0, 0), QPoint(gSize, 1));
for (int i = 0; i < numPoints; i++)
{
int x = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand() % image.width();
int y = QTIsaac<ISAAC_SIZE, ISAAC_INT>::LockedRand() % image.height();
QRgb rgb = image.pixel(x, y);
GradientArrow arrow;
arrow.Color(QColor::fromRgb(rgb));
arrow.Focus(i == 0);
colors[off] = arrow;
off += inc;
}
return colors;
}

View File

@ -0,0 +1,78 @@
#pragma once
#include "FractoriumPch.h"
#include "ColorPickerWidget.h"
#include "GradientColorsView.h"
#include "EmberFile.h"
#include "ui_PaletteEditor.h"
namespace Ui
{
class PaletteEditor;
}
/// <summary>
/// Dialog for editing user created palettes.
/// This will load with all available user created palettes populated in the combo
/// box. As the user changes the selected index, the palettes for that file
/// are shown in the list box. The user can click on those and then edit them and either
/// save it back to the same position in the file, or append it to the end.
/// They can also click in the name column to set/rename the palette name.
/// Any changes on this dialog can be "synced". That is, when the Sync checkbox is checked
/// any changes result in a signal being sent back to the main window.
/// </summary>
class PaletteEditor : public QDialog
{
Q_OBJECT
public:
explicit PaletteEditor(PaletteList<float>& paletteList, QWidget* p = nullptr);
public:
bool Sync();
QPixmap* GetBackGround();
Palette<float>& GetPalette(int size);
void SetPalette(Palette<float>& palette);
signals:
void PaletteChanged();
void PaletteFileChanged();
private slots:
void OnAddColorButtonClicked();
void OnRemoveColorButtonClicked();
void OnInvertColorsButtonClicked();
void OnRandomColorsButtonClicked();
void OnDistributeColorsButtonClicked();
void OnResetToDefaultButtonClicked();
void OnCreatePaletteFromImageButtonClicked();
void OnCreatePaletteAgainFromImageButton();
void OnColorPickerColorChanged(const QColor& col);
void OnArrowDoubleClicked(const GradientArrow& arrow);
void OnSyncCheckBoxStateChanged(int state);
void OnArrowMoved(qreal lastPos, const GradientArrow& arrow);
void OnPaletteFilenameComboChanged(const QString& text);
void OnPaletteCellClicked(int row, int col);
void OnPaletteCellChanged(int row, int col);
void OnNewPaletteFileButtonClicked();
void OnCopyPaletteFileButtonClicked();
void OnAppendPaletteButtonClicked();
void OnOverwritePaletteButtonClicked();
void OnDeletePaletteButtonClicked();
private:
void EmitPaletteChanged();
QStringList SetupOpenImagesDialog();
void AddArrow(const QColor& color);
map<float, GradientArrow> GetRandomColorsFromImage(QString filename, int numPoints);
bool m_PaletteFileChanged = false;
int m_PaletteIndex = 0;
QString m_Filename;
string m_CurrentPaletteFilePath;
ColorPickerWidget* m_ColorPicker = nullptr;
GradientColorsView* m_GradientColorView = nullptr;
QFileDialog* m_FileDialog = nullptr;
PaletteList<float>& m_PaletteList;
std::unique_ptr<Ui::PaletteEditor> ui;
};

View File

@ -11,28 +11,17 @@
/// The lifetime of the palette object must be greater than or equal to
/// the lifetime of this object.
/// </summary>
class PaletteTableWidgetItemBase : public QTableWidgetItem
class PaletteTableWidgetItem : public QTableWidgetItem
{
public:
PaletteTableWidgetItemBase()
{
}
virtual size_t Index() const { return 0; }
};
template <typename T>
class PaletteTableWidgetItem : public PaletteTableWidgetItemBase
{
public:
PaletteTableWidgetItem(Palette<T>* palette)
PaletteTableWidgetItem(Palette<float>* palette)
: m_Palette(palette)
{
}
virtual size_t Index() const override { return m_Palette->m_Index; }
Palette<T>* GetPalette() const { return m_Palette; }
size_t Index() const { return m_Palette->m_Index; }
Palette<float>* GetPalette() const { return m_Palette; }
private:
Palette<T>* m_Palette;
Palette<float>* m_Palette;
};

View File

@ -17,10 +17,12 @@ SpinBox::SpinBox(QWidget* p, int h, int step)
: QSpinBox(p)
{
m_DoubleClick = false;
m_DoubleClickLowVal = 0;
m_DoubleClickNonZero = 0;
m_DoubleClickZero = 1;
m_Step = step;
m_SmallStep = 1;
m_Settings = FractoriumSettings::DefInstance();
setSingleStep(step);
setFrame(false);
setButtonSymbols(QAbstractSpinBox::NoButtons);
@ -54,6 +56,16 @@ void SpinBox::DoubleClick(bool b)
m_DoubleClick = b;
}
/// <summary>
/// Set the value to be used instead of zero to represent the lower value
/// used when responding to a double click.
/// </summary>
/// <param name="val">The value to be used for the lower value instead of zero</param>
void SpinBox::DoubleClickLowVal(int val)
{
m_DoubleClickLowVal = val;
}
/// <summary>
/// Set the value to be used when the user double clicks the spinner while
/// it contains zero.
@ -135,35 +147,34 @@ void SpinBox::OnTimeout()
bool SpinBox::eventFilter(QObject* o, QEvent* e)
{
QMouseEvent* me = dynamic_cast<QMouseEvent*>(e);
auto settings = FractoriumSettings::DefInstance();
if (isEnabled() && me)
{
if (!settings->ToggleType() &&//Ensure double click toggles, not right click.
if (!m_Settings->ToggleType() &&//Ensure double click toggles, not right click.
me->type() == QMouseEvent::MouseButtonPress &&
me->button() == Qt::RightButton)
{
m_MouseDownPoint = m_MouseMovePoint = me->pos();
StartTimer();
}
else if (!settings->ToggleType() &&
else if (!m_Settings->ToggleType() &&
me->type() == QMouseEvent::MouseButtonRelease &&
me->button() == Qt::RightButton)
{
StopTimer();
m_MouseDownPoint = m_MouseMovePoint = me->pos();
}
else if (!settings->ToggleType() &&
else if (!m_Settings->ToggleType() &&
me->type() == QMouseEvent::MouseMove &&
QGuiApplication::mouseButtons() & Qt::RightButton)
{
m_MouseMovePoint = me->pos();
}
else if (m_DoubleClick &&
((!settings->ToggleType() && e->type() == QMouseEvent::MouseButtonDblClick && me->button() == Qt::LeftButton) ||
(settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton)))
((!m_Settings->ToggleType() && e->type() == QMouseEvent::MouseButtonDblClick && me->button() == Qt::LeftButton) ||
(m_Settings->ToggleType() && me->type() == QMouseEvent::MouseButtonRelease && me->button() == Qt::RightButton)))
{
if (IsNearZero(value()))
if (IsClose(m_DoubleClickLowVal, value()))
setValue(m_DoubleClickZero);
else
setValue(m_DoubleClickNonZero);

View File

@ -22,6 +22,7 @@ public:
void SetValueStealth(int d);
void SetValueStealth(size_t d);
void DoubleClick(bool b);
void DoubleClickLowVal(int val);
void DoubleClickZero(int val);
void DoubleClickNonZero(int val);
void SmallStep(int step);
@ -43,11 +44,13 @@ private:
void StopTimer();
bool m_DoubleClick;
int m_DoubleClickLowVal;
int m_DoubleClickNonZero;
int m_DoubleClickZero;
int m_Step;
int m_SmallStep;
QPoint m_MouseDownPoint;
QPoint m_MouseMovePoint;
shared_ptr<FractoriumSettings> m_Settings;
static QTimer s_Timer;
};