--User changes

-Give tabs a height of 4px in the qss files. Looks a little large on 4k screens, but just right on HD screens which are much more common.
 -Allow for styling of zero and non-zero variation tree nodes via qss.
 -Allow for toggling whether to interpolate between colors in the palette editor, or to do hard cuts between colors.
 -Allow for adjusting spinner values with the + = or up arrow keys to increase, and - _ or down arrow keys to decrease.
 -Allow for responding to global presses of + = and - _ to cycle up or down to specify which xform is set as the current one.
 -Allow for adding "layers" via xaos which will add a user-specified number of xforms, and set certain xaos values to 0 or 1.
 -Add a new menu item under the Edit menu to copy the OpenCL iteration kernel source to the clipboard.
 -Show text on the status bar which indicates that an OpenCL kernel compilation is taking place.
 -Show xform name on xform combo box when expanded. Adjust size to fit all names.
 -Draw post affine circles using dashed lines.
 -Prevent QSS dialog from styling its editor, which makes it easier to see text when creating styles which have custom colors for text boxes.

--Bug fixes
 -Fix up some table layouts which seemed to have regressed/decayed over time for reasons unknown.
 -Using undo/redo would create a new flame in the library every time.
 -Solo was not being preserved when using undo/redo.

--Code changes
 -Make the solo flag be a part of the flame data now.
 -Fix some tabification in the OpenCL code for EllipticVariation.
 -Fix tabification in the varState code for OpenCL.
 -Add an event called m_CompileBegun to RendererCL that is called right before an OpenCL compile is begun.
 --This required making RendererCLBase not a pure virtual base class. Member functions just return defaults.
 -Filter key presses on main window to only process the third one. This is due to Qt triggering three events for every key press.
 -The palette preview table was installing an event filter for seemingly no reason. Remove it.
 -Mark certain virtual functions as override in SpinBox and DoubleSpinBox.
This commit is contained in:
Person
2018-07-30 21:39:41 -07:00
parent 0deabd45b8
commit 26c558a2f5
37 changed files with 708 additions and 201 deletions

View File

@ -230,6 +230,47 @@ bool DoubleSpinBox::eventFilter(QObject* o, QEvent* e)
return QDoubleSpinBox::eventFilter(o, e);
}
/// <summary>
/// Override which is for handling specific key presses while this control is focused.
/// In particular, + = and up arrow increase the value, equivalent to scrolling the mouse wheel up, while also observing shift/ctrl modifiers.
/// Values are decreased in the same way by pressing - _ or down arrow.
/// </summary>
/// <param name="ke">The key event</param>
void DoubleSpinBox::keyPressEvent(QKeyEvent* ke)
{
bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
bool ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
if (ke->key() == Qt::Key_Plus || ke->key() == Qt::Key_Equal || ke->key() == Qt::Key_Up)
{
if (shift)
{
setSingleStep(m_SmallStep);
setValue(value() + m_SmallStep);
}
else
{
setSingleStep(m_Step);
setValue(value() + (ctrl ? m_Step * 10 : m_Step));
}
}
else if (ke->key() == Qt::Key_Minus || ke->key() == Qt::Key_Underscore || ke->key() == Qt::Key_Down)
{
if (shift)
{
setSingleStep(m_SmallStep);
setValue(value() - m_SmallStep);
}
else
{
setSingleStep(m_Step);
setValue(value() - (ctrl ? m_Step * 10 : m_Step));
}
}
else
QDoubleSpinBox::keyPressEvent(ke);
}
/// <summary>
/// Called when focus enters the spinner.
/// </summary>

View File

@ -38,10 +38,11 @@ public slots:
protected:
virtual bool eventFilter(QObject* o, QEvent* e) override;
virtual void focusInEvent(QFocusEvent* e);
virtual void focusOutEvent(QFocusEvent* e);
virtual void enterEvent(QEvent* e);
virtual void leaveEvent(QEvent* e);
virtual void keyPressEvent(QKeyEvent* event) override;
virtual void focusInEvent(QFocusEvent* e) override;
virtual void focusOutEvent(QFocusEvent* e) override;
virtual void enterEvent(QEvent* e) override;
virtual void leaveEvent(QEvent* e) override;
bool m_DoubleClick;
shared_ptr<FractoriumSettings> m_Settings;

View File

@ -89,10 +89,6 @@ Fractorium::Fractorium(QWidget* p)
pixmap.fill(m_XformComboColors[i]);
m_XformComboIcons[i] = QIcon(pixmap);
}
//Set Default VariationTreeBgColor
m_VariationTreeBgColorNoneZero=QColor(200,200,200);
m_VariationTreeBgColorZero=QColor(255,255,255);
QPixmap pixmap(iconSize_, iconSize_);
pixmap.fill(m_FinalXformComboColor);
@ -348,6 +344,11 @@ void Fractorium::dockLocationChanged(Qt::DockWidgetArea area)
/// <returns>false</returns>
bool Fractorium::eventFilter(QObject* o, QEvent* e)
{
static int fcount = 0;//Qt seems to deliver three events for every key press. So a count must be kept to only respond to the third event.
static int libdelcount = 0;//Note that if anything ever changes under the hood with Qt, this will likely stop working, so adjust as accordingly.
static int xfupcount = 0;
static int xfdncount = 0;
if (o == ui.GLParentScrollArea && e->type() == QEvent::Resize)
{
m_WidthSpin->DoubleClickNonZero(ui.GLParentScrollArea->width() * ui.GLDisplay->devicePixelRatioF());
@ -355,24 +356,79 @@ bool Fractorium::eventFilter(QObject* o, QEvent* e)
}
else if (auto ke = dynamic_cast<QKeyEvent*>(e))
{
auto combo = ui.CurrentXformCombo;
bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
if (ke->key() >= Qt::Key_F1 && ke->key() <= Qt::Key_F32)
{
int val = ke->key() - (int)Qt::Key_F1;
fcount++;
if (val < ui.CurrentXformCombo->count())
ui.CurrentXformCombo->setCurrentIndex(val);
if (fcount >= 3)
{
int val = ke->key() - (int)Qt::Key_F1;
if (val < combo->count())
combo->setCurrentIndex(val);
fcount = 0;
qDebug() << "global function key press: " << ke->key() << " " << o->metaObject()->className() << " " << o->objectName();
}
return true;
}
else if (o == ui.LibraryTree)
{
//Require shift for deleting to prevent it from triggering when the user enters delete in the edit box.
if (ke->key() == Qt::Key_Delete && e->type() == QEvent::KeyRelease && shift)
{
auto v = GetCurrentEmberIndex();
libdelcount++;
if (ui.LibraryTree->topLevelItem(0)->childCount() > 1)
OnDelete(v);
if (libdelcount >= 3)
{
auto v = GetCurrentEmberIndex();
if (ui.LibraryTree->topLevelItem(0)->childCount() > 1)
OnDelete(v);
libdelcount = 0;
}
return true;
}
}
else if (o == this)
{
unsigned int index = combo->currentIndex();
if (ke->key() == Qt::Key_Plus || ke->key() == Qt::Key_Equal)
{
xfupcount++;
if (xfupcount >= 3)
{
xfupcount = 0;
combo->setCurrentIndex((index + 1) % combo->count());
qDebug() << "global arrow plus key press: " << ke->key() << " " << o->metaObject()->className() << " " << o->objectName();
}
return true;
}
else if (ke->key() == Qt::Key_Minus || ke->key() == Qt::Key_Underscore)
{
xfdncount++;
if (xfdncount >= 3)
{
xfdncount = 0;
if (index == 0)
index = combo->count();
combo->setCurrentIndex((index - 1) % combo->count());
qDebug() << "global arrow minus key press: " << ke->key() << " " << o->metaObject()->className() << " " << o->objectName();
}
return true;
}
}
}
@ -866,6 +922,10 @@ void Fractorium::SetTabOrders()
w = SetTabOrder(this, w, ui.WorldPivotRadio);
w = SetTabOrder(this, ui.VariationsFilterLineEdit, ui.VariationsFilterClearButton);//Xforms variation.
w = SetTabOrder(this, w, ui.VariationsTree);
w = SetTabOrder(this, w, ui.ClearXaosButton);
w = SetTabOrder(this, w, ui.RandomXaosButton);
w = SetTabOrder(this, w, ui.AddLayerButton);
w = SetTabOrder(this, w, ui.AddLayerSpinBox);
//Xforms xaos is done dynamically every time.
w = SetTabOrder(this, ui.PaletteFilenameCombo, m_PaletteHueSpin);//Palette.
w = SetTabOrder(this, w, m_PaletteContrastSpin);

View File

@ -63,9 +63,11 @@ template <typename T> class FinalRenderEmberController;
class Fractorium : public QMainWindow
{
Q_OBJECT
Q_PROPERTY(QColor VariationTreeBgColorNoneZero MEMBER m_VariationTreeBgColorNoneZero)
Q_PROPERTY(QColor VariationTreeBgColorZero MEMBER m_VariationTreeBgColorZero)
Q_PROPERTY(QColor VariationTreeColorNonZero MEMBER m_VariationTreeColorNonZero)
Q_PROPERTY(QColor VariationTreeColorZero MEMBER m_VariationTreeColorZero)
Q_PROPERTY(QColor VariationTreeBgColorNonZero MEMBER m_VariationTreeBgColorNonZero)
Q_PROPERTY(QColor VariationTreeBgColorZero MEMBER m_VariationTreeBgColorZero)
friend GLWidget;
friend QssDialog;
friend LibraryTreeWidget;
@ -140,6 +142,7 @@ public slots:
void OnActionPasteXmlOver(bool checked);
void OnActionCopySelectedXforms(bool checked);
void OnActionPasteSelectedXforms(bool checked);
void OnActionCopyKernel(bool checked);
void OnActionResetWorkspace(bool checked);//View
void OnActionAlternateEditorImage(bool checked);
@ -319,6 +322,7 @@ public slots:
void OnXaosChanged(double d);
void OnClearXaosButtonClicked(bool checked);
void OnRandomXaosButtonClicked(bool checked);
void OnAddLayerButtonClicked(bool checked);
void OnXaosRowDoubleClicked(int logicalIndex);
void OnXaosColDoubleClicked(int logicalIndex);
void OnXaosTableModelDataChanged(const QModelIndex& indexA, const QModelIndex& indexB);
@ -561,7 +565,8 @@ private:
char m_CoordinateString[128];
QColor m_XformComboColors[XFORM_COLOR_COUNT], m_FinalXformComboColor;
QIcon m_XformComboIcons[XFORM_COLOR_COUNT], m_FinalXformComboIcon;
QColor m_VariationTreeBgColorNoneZero, m_VariationTreeBgColorZero;
QColor m_VariationTreeColorNonZero, m_VariationTreeColorZero;
QColor m_VariationTreeBgColorNonZero, m_VariationTreeBgColorZero;
vector<QDockWidget*> m_Docks;
int m_FontSize;
@ -572,7 +577,7 @@ private:
shared_ptr<OpenCLInfo> m_Info;
unique_ptr<FractoriumEmberControllerBase> m_Controller;
Ui::FractoriumClass ui;
};

View File

@ -41,6 +41,9 @@
<height>16</height>
</size>
</property>
<property name="tabShape">
<enum>QTabWidget::Rounded</enum>
</property>
<property name="dockOptions">
<set>QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::AnimatedDocks|QMainWindow::GroupedDragging</set>
</property>
@ -432,7 +435,7 @@
<bool>false</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>15</number>
@ -513,7 +516,7 @@
<bool>false</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>15</number>
@ -575,6 +578,9 @@
<property name="focusPolicy">
<enum>Qt::NoFocus</enum>
</property>
<property name="autoFillBackground">
<bool>false</bool>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
@ -600,7 +606,7 @@
<bool>false</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>15</number>
@ -697,7 +703,7 @@
<bool>false</bool>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>15</number>
@ -1587,7 +1593,7 @@
<number>1</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>15</number>
@ -1891,6 +1897,48 @@
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_4" rowminimumheight="0" columnminimumwidth="0,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="AddLayerButton">
<property name="toolTip">
<string>Add N new xforms with all to/from xaos values to each other set to 1, and the remainder set to 0</string>
</property>
<property name="text">
<string>Add Layer</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="SpinBox" name="AddLayerSpinBox">
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="suffix">
<string> Xforms</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>9999999</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QTableView" name="XaosTableView">
<property name="sizePolicy">
@ -2805,6 +2853,9 @@
<property name="maxVisibleItems">
<number>12</number>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::AdjustToContents</enum>
</property>
<property name="iconSize">
<size>
<width>9</width>
@ -3096,7 +3147,7 @@
<number>2</number>
</property>
<attribute name="horizontalHeaderVisible">
<bool>false</bool>
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderDefaultSectionSize">
<number>110</number>
@ -3179,7 +3230,7 @@
<enum>QTabWidget::Triangular</enum>
</property>
<property name="currentIndex">
<number>2</number>
<number>3</number>
</property>
<widget class="QWidget" name="XformColorTab">
<property name="sizePolicy">
@ -6924,6 +6975,8 @@
<addaction name="separator"/>
<addaction name="ActionCopySelectedXforms"/>
<addaction name="ActionPasteSelectedXforms"/>
<addaction name="separator"/>
<addaction name="ActionCopyKernel"/>
</widget>
<widget class="QMenu" name="MenuView">
<property name="title">
@ -8286,6 +8339,17 @@
<string>Reset Scale</string>
</property>
</action>
<action name="ActionCopyKernel">
<property name="text">
<string>Copy &amp;Kernel</string>
</property>
<property name="statusTip">
<string>Copy the OpenCL program to the clipboard for debugging.</string>
</property>
<property name="shortcut">
<string>Ctrl+K</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
@ -8310,6 +8374,11 @@
<extends>QTreeWidget</extends>
<header>LibraryTreeWidget.h</header>
</customwidget>
<customwidget>
<class>SpinBox</class>
<extends>QSpinBox</extends>
<header>spinbox.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>LibraryDockWidget</tabstop>

View File

@ -495,12 +495,61 @@ static QString BaseStyle()
"It's not needed for other themes."
"You should keep this at the top of whatever custom style you make to ensure the tabs aren't unusually large.*/\n"
#ifndef _WIN32
"QTabBar::tab { height: 3ex; }\n\n"
"QTabBar::tab { height: 4ex; }\n\n"
#else
"QTabBar::tab { height: 3ex; }\n\n"
"QTabBar::tab { height: 4ex; }\n\n"
#endif
"/*This is needed to give the labels on the status bar some padding.*/\n"
"QStatusBar QLabel { padding-left: 2px; padding-right: 2px; }\n\n"
"QStatusBar QLabel { padding-left: 2px; padding-right: 2px; }\n"
"\n"
"Fractorium\n"
"{\n"
"\tqproperty-VariationTreeColorNonZero:black;\n"
"\tqproperty-VariationTreeColorZero:black;\n"
"\tqproperty-VariationTreeBgColorNonZero:lightgray;\n"
"\tqproperty-VariationTreeBgColorZero:white;\n"
"}\n"
"\n"
"/*For some reason, Qt does not draw table headers correctly, so the style must always be manually specified.*/\n"
"QHeaderView::section::vertical:enabled\n"
"{\n"
"\tcolor: black;\n"
"\tbackground-color: lightgray;\n"
"\tborder: none;\n"
"\tborder-bottom: 1px solid gray;\n"
"\tpadding: 4px;\n"
"\tfont: 8pt \"MS Shell Dlg 2\";/*For some reason the font changes if you set any style. Set this to whatever font is the default on your system*/\n"
"}\n"
"\n"
"QHeaderView::section::horizontal:enabled\n"
"{\n"
"\tcolor: black;\n"
"\tbackground-color: lightgray;\n"
"\tborder: 0px solid darkgray;\n"
"\tborder-right: 1px solid gray;\n"
"\tpadding: 4px;\n"
"\tfont: 8pt \"MS Shell Dlg 2\";/*For some reason the font changes if you set any style. Set this to whatever font is the default on your system*/\n"
"}\n"
"\n"
"QHeaderView::section::vertical:disabled\n"
"{\n"
"\tcolor: rgb(35, 35, 35);\n"
"\tbackground-color: rgb(53, 53, 53);\n"
"\tborder: 0px none darkgray;\n"
"\tborder-bottom: 1px solid rgb(53, 53, 53);\n"
"\tpadding: 4px;\n"
"\tfont: 8pt \"MS Shell Dlg 2\";/*For some reason the font changes if you set any style. Set this to whatever font is the default on your system*/\n"
"}\n"
"\n"
"QHeaderView::section::horizontal:disabled\n"
"{\n"
"\tcolor:rgb(35, 35, 35);\n"
"\tbackground-color: rgb(53, 53, 53);\n"
"\tborder: 0px none darkgray;\n"
"\tborder-right: 0px solid rgb(53, 53, 53);\n"
"\tpadding: 4px;\n"
"\tfont: 8pt \"MS Shell Dlg 2\";/*For some reason the font changes if you set any style. Set this to whatever font is the default on your system*/\n"
"}\n"
;
}

View File

@ -336,8 +336,6 @@ void FractoriumEmberController<T>::SetEmberPrivate(const Ember<U>& ember, bool v
if (updatePointer && (typeid(T) == typeid(U)))
m_EmberFilePointer = (Ember<T>*)&ember;
else
m_EmberFilePointer = nullptr;
if (!verbatim)
{

View File

@ -109,6 +109,7 @@ public:
virtual void PasteXmlOver() { }
virtual void CopySelectedXforms() { }
virtual void PasteSelectedXforms() { }
virtual void CopyKernel() { }
virtual void AddReflectiveSymmetry() { }//Tools.
virtual void AddRotationalSymmetry() { }
virtual void AddBothSymmetry() { }
@ -184,7 +185,7 @@ public:
virtual void XformNameChanged(int row, int col) { }
virtual void XformAnimateChanged(int state) { }
virtual void FillXforms(int index = 0) { }
virtual void UpdateXformName(int index) { }
virtual void UpdateXformName(int index) { }
//Xforms Affine.
virtual void AffineSetHelper(double d, int index, bool pre) { }
@ -210,6 +211,7 @@ public:
virtual void XformColorSpeedChanged(double d) { }
virtual void XformOpacityChanged(double d) { }
virtual void XformDirectColorChanged(double d) { }
virtual void SoloXformCheckBoxStateChanged(int state, int index) { }
virtual QColor ColorIndexToQColor(double d) { return QColor(); }
//Xforms Variations.
@ -218,6 +220,7 @@ public:
virtual void ClearVariationsTree() { }
virtual void VariationSpinBoxValueChanged(double d) { }
virtual void FilteredVariations() { }
virtual void FillVariationTreeWithCurrentXform() { }
//Xforms Selection.
@ -226,6 +229,7 @@ public:
virtual void XaosChanged(int x, int y, double val) { }
virtual void ClearXaos() { }
virtual void RandomXaos() { }
virtual void AddLayer(int xforms) { }
//Palette.
virtual size_t InitPaletteList(const QString& s) { return 0; }
@ -375,6 +379,7 @@ public:
virtual void PasteXmlOver() override;
virtual void CopySelectedXforms() override;
virtual void PasteSelectedXforms() override;
virtual void CopyKernel() override;
virtual void AddReflectiveSymmetry() override;
virtual void AddRotationalSymmetry() override;
virtual void AddBothSymmetry() override;
@ -451,7 +456,7 @@ public:
virtual void XformNameChanged(int row, int col) override;
virtual void XformAnimateChanged(int state) override;
virtual void FillXforms(int index = 0) override;
virtual void UpdateXformName(int index) override;
virtual void UpdateXformName(int index) override;
void FillWithXform(Xform<T>* xform);
Xform<T>* CurrentXform();
void UpdateXform(std::function<void(Xform<T>*, size_t, size_t)> func, eXformUpdate updateType = eXformUpdate::UPDATE_CURRENT, bool updateRender = true, eProcessAction action = eProcessAction::FULL_RENDER, size_t index = 0);
@ -480,6 +485,7 @@ public:
virtual void XformColorSpeedChanged(double d) override;
virtual void XformOpacityChanged(double d) override;
virtual void XformDirectColorChanged(double d) override;
virtual void SoloXformCheckBoxStateChanged(int state, int index) override;
virtual QColor ColorIndexToQColor(double d) override;
void FillColorWithXform(Xform<T>* xform);
@ -489,6 +495,7 @@ public:
virtual void ClearVariationsTree() override;
virtual void VariationSpinBoxValueChanged(double d) override;
virtual void FilteredVariations() override;
virtual void FillVariationTreeWithCurrentXform() override;
void FillVariationTreeWithXform(Xform<T>* xform);
//Xforms Xaos.
@ -496,6 +503,7 @@ public:
virtual void XaosChanged(int x, int y, double val) override;
virtual void ClearXaos() override;
virtual void RandomXaos() override;
virtual void AddLayer(int xforms) override;
//Xforms Selection.
bool XformCheckboxAt(int i, std::function<void(QCheckBox*)> func);

View File

@ -25,6 +25,7 @@ void Fractorium::InitMenusUI()
connect(ui.ActionPasteXmlOver, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteXmlOver(bool)), Qt::QueuedConnection);
connect(ui.ActionCopySelectedXforms, SIGNAL(triggered(bool)), this, SLOT(OnActionCopySelectedXforms(bool)), Qt::QueuedConnection);
connect(ui.ActionPasteSelectedXforms, SIGNAL(triggered(bool)), this, SLOT(OnActionPasteSelectedXforms(bool)), Qt::QueuedConnection);
connect(ui.ActionCopyKernel, SIGNAL(triggered(bool)), this, SLOT(OnActionCopyKernel(bool)), Qt::QueuedConnection);
ui.ActionPasteSelectedXforms->setEnabled(false);
//View menu.
connect(ui.ActionResetWorkspace, SIGNAL(triggered(bool)), this, SLOT(OnActionResetWorkspace(bool)), Qt::QueuedConnection);
@ -471,10 +472,10 @@ void FractoriumEmberController<T>::Undo()
int index = m_Ember.GetTotalXformIndex(current, forceFinal);
m_LastEditWasUndoRedo = true;
m_UndoIndex = std::max<size_t>(0u, m_UndoIndex - 1u);
SetEmber(m_UndoList[m_UndoIndex], true, false);//Don't update pointer because it's coming from the undo list...
SetEmber(m_UndoList[m_UndoIndex], true, false);//Don't update pointer because it's coming from the undo list.
m_EditState = eEditUndoState::UNDO_REDO;
if (index >= 0)
if (index >= 0 && index < m_Fractorium->ui.CurrentXformCombo->count())
m_Fractorium->CurrentXform(index);
m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0));
@ -497,10 +498,10 @@ void FractoriumEmberController<T>::Redo()
int index = m_Ember.GetTotalXformIndex(current, forceFinal);
m_LastEditWasUndoRedo = true;
m_UndoIndex = std::min<size_t>(m_UndoIndex + 1, m_UndoList.size() - 1);
SetEmber(m_UndoList[m_UndoIndex], true, false);
SetEmber(m_UndoList[m_UndoIndex], true, false);//Don't update pointer because it's coming from the undo list.
m_EditState = eEditUndoState::UNDO_REDO;
if (index >= 0)
if (index >= 0 && index < m_Fractorium->ui.CurrentXformCombo->count())
m_Fractorium->CurrentXform(index);
m_Fractorium->ui.ActionUndo->setEnabled(m_UndoList.size() > 1 && (m_UndoIndex > 0));
@ -713,6 +714,19 @@ void Fractorium::OnActionPasteSelectedXforms(bool checked)
m_Controller->PasteSelectedXforms();
}
/// <summary>
/// Copy the text of the OpenCL iteration kernel to the clipboard.
/// This performs no action if the renderer is not of type RendererCL.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::CopyKernel()
{
if (auto rendererCL = dynamic_cast<RendererCL<T, float>*>(m_Renderer.get()))
QApplication::clipboard()->setText(QString::fromStdString(rendererCL->IterKernel()));
}
void Fractorium::OnActionCopyKernel(bool checked) { m_Controller->CopyKernel(); }
/// <summary>
/// Reset dock widgets and tabs to their default position.
/// Note that there is a bug in Qt, where it will only move them all to the left side if at least
@ -837,7 +851,7 @@ void FractoriumEmberController<T>::Flatten()
{
ember.Flatten(XmlToEmber<T>::m_FlattenNames);
}, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll());
FillVariationTreeWithXform(CurrentXform());
FillVariationTreeWithCurrentXform();
}
void Fractorium::OnActionFlatten(bool checked) { m_Controller->Flatten(); }
@ -852,7 +866,7 @@ void FractoriumEmberController<T>::Unflatten()
{
ember.Unflatten();
}, true, eProcessAction::FULL_RENDER, m_Fractorium->ApplyAll());
FillVariationTreeWithXform(CurrentXform());
FillVariationTreeWithCurrentXform();
}
void Fractorium::OnActionUnflatten(bool checked) { m_Controller->Unflatten(); }

View File

@ -38,7 +38,6 @@ void Fractorium::InitPaletteUI()
palettePreviewTable->setItem(0, 0, previewNameCol);
auto previewPaletteItem = new QTableWidgetItem();
palettePreviewTable->setItem(0, 1, previewPaletteItem);
palettePreviewTable->installEventFilter(this);
connect(ui.PaletteFilterLineEdit, SIGNAL(textChanged(const QString&)), this, SLOT(OnPaletteFilterLineEditTextChanged(const QString&)));
connect(ui.PaletteFilterClearButton, SIGNAL(clicked(bool)), this, SLOT(OnPaletteFilterClearButtonClicked(bool)));
paletteTable->setColumnWidth(1, 260);//256 plus small margin on each side.
@ -392,6 +391,7 @@ void FractoriumEmberController<T>::PaletteEditorButtonClicked()
ed->SetColorIndices(colorIndices);
ed->SetPaletteFile(m_CurrentPaletteFilePath);
//ed->setpal
if (ed->exec() == QDialog::Accepted)
{
//Copy all just to be safe, because they may or may not have synced.

View File

@ -346,7 +346,7 @@ bool FractoriumEmberController<T>::Render()
if (action != eProcessAction::NOTHING)
{
size_t i = 0;
int solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt();
int solo = m_Ember.m_Solo;
bool forceFinal = m_Fractorium->HaveFinal();
if (solo != -1)
@ -667,9 +667,11 @@ bool Fractorium::CreateRendererFromOptions(bool updatePreviews)
bool ok = true;
bool useOpenCL = m_Info->Ok() && m_Settings->OpenCL();
auto v = Devices(m_Settings->Devices());
bool doOpenCL = useOpenCL && !v.empty();
ui.ActionCopyKernel->setEnabled(doOpenCL);
//The most important option to process is what kind of renderer is desired, so do it first.
if (!m_Controller->CreateRenderer((useOpenCL && !v.empty()) ? eRendererType::OPENCL_RENDERER : eRendererType::CPU_RENDERER, v, updatePreviews, useOpenCL && m_Settings->SharedTexture()))
if (!m_Controller->CreateRenderer(doOpenCL ? eRendererType::OPENCL_RENDERER : eRendererType::CPU_RENDERER, v, updatePreviews, doOpenCL && m_Settings->SharedTexture()))
{
//If using OpenCL, will only get here if creating RendererCL failed, but creating a backup CPU Renderer succeeded.
ShowCritical("Renderer Creation Error", "Error creating renderer, most likely a GPU problem. Using CPU instead.");
@ -677,12 +679,21 @@ bool Fractorium::CreateRendererFromOptions(bool updatePreviews)
m_Settings->SharedTexture(false);
ui.ActionCpu->setChecked(true);
ui.ActionCL->setChecked(false);
ui.ActionCopyKernel->setEnabled(false);
m_OptionsDialog->ui.OpenCLCheckBox->setChecked(false);
m_OptionsDialog->ui.SharedTextureCheckBox->setChecked(false);
m_FinalRenderDialog->ui.FinalRenderOpenCLCheckBox->setChecked(false);
ok = false;
}
if (auto rendererCL = dynamic_cast<RendererCLBase*>(m_Controller->Renderer()))
rendererCL->m_CompileBegun = [&]()
{
m_RenderStatusLabel->setText("Compiling OpenCL kernel...");
m_RenderStatusLabel->repaint();
QApplication::processEvents();
};
return ok;
}

View File

@ -22,6 +22,7 @@ void Fractorium::InitXaosUI()
connect(m_XaosSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnXaosChanged(double)), Qt::QueuedConnection);
connect(ui.ClearXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnClearXaosButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.RandomXaosButton, SIGNAL(clicked(bool)), this, SLOT(OnRandomXaosButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.AddLayerButton, SIGNAL(clicked(bool)), this, SLOT(OnAddLayerButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.XaosTableView->verticalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(OnXaosRowDoubleClicked(int)), Qt::QueuedConnection);
connect(ui.XaosTableView->horizontalHeader(), SIGNAL(sectionDoubleClicked(int)), this, SLOT(OnXaosColDoubleClicked(int)), Qt::QueuedConnection);
}
@ -165,6 +166,49 @@ void FractoriumEmberController<T>::RandomXaos()
void Fractorium::OnRandomXaosButtonClicked(bool checked) { m_Controller->RandomXaos(); }
/// <summary>
/// Add a layer using the specified number of xforms.
/// A layer is defined as a new set of xforms whose xaos values are the following:
/// From existing to existing: unchanged.
/// From existing to new: 0.
/// From new to existing: 0.
/// From new to new: 1.
/// </summary>
/// <param name="xforms">The number of new xforms to add to create the layer</param>
template <typename T>
void FractoriumEmberController<T>::AddLayer(int xforms)
{
Update([&]
{
auto origXformCount = m_Ember.XformCount();
m_Ember.AddXforms(xforms);
for (auto i = 0; i < m_Ember.XformCount(); i++)
{
auto xf = m_Ember.GetXform(i);
if (i < origXformCount)
{
for (auto j = 0; j < m_Ember.XformCount(); j++)
if (j >= origXformCount)
xf->SetXaos(j, 0);
}
else
{
for (auto j = 0; j < m_Ember.XformCount(); j++)
if (j < origXformCount)
xf->SetXaos(j, 0);
else
xf->SetXaos(j, 1);
}
}
});
FillXforms();
FillSummary();
}
void Fractorium::OnAddLayerButtonClicked(bool checked) { m_Controller->AddLayer(ui.AddLayerSpinBox->value()); }
/// <summary>
/// Toggle all xaos values in one row.
/// Resets the rendering process.

View File

@ -15,25 +15,28 @@ void Fractorium::InitXformsUI()
connect(ui.AddFinalXformButton, SIGNAL(clicked(bool)), this, SLOT(OnAddFinalXformButtonClicked(bool)), Qt::QueuedConnection);
connect(ui.CurrentXformCombo, SIGNAL(currentIndexChanged(int)), this, SLOT(OnCurrentXformComboChanged(int)), Qt::QueuedConnection);
connect(ui.AnimateXformCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnXformAnimateCheckBoxStateChanged(int)), Qt::QueuedConnection);
SetFixedTableHeader(ui.XformWeightNameTable->horizontalHeader(),QHeaderView::ResizeToContents);
SetFixedTableHeader(ui.XformWeightNameTable->horizontalHeader(), QHeaderView::ResizeToContents);
//Use SetupSpinner() just to create the spinner, but use col of -1 to prevent it from being added to the table.
SetupSpinner<DoubleSpinBox, double>(ui.XformWeightNameTable, this, row, -1, m_XformWeightSpin, spinHeight, 0, 1000, 0.05, SIGNAL(valueChanged(double)), SLOT(OnXformWeightChanged(double)), false, 0, 1, 0);
m_XformWeightSpin->setDecimals(3);
m_XformWeightSpin->SmallStep(0.001);
m_XformWeightSpin->setMinimumWidth(40);
m_XformWeightSpin->setMinimumWidth(40);
m_XformWeightSpinnerButtonWidget = new SpinnerLabelButtonWidget(m_XformWeightSpin, "=", 20, 19, ui.XformWeightNameTable);
m_XformWeightSpinnerButtonWidget->m_SpinBox->setSizePolicy(QSizePolicy::Expanding,QSizePolicy::Fixed);
m_XformWeightSpinnerButtonWidget->m_Label->setStyleSheet("border: 0px;");
m_XformWeightSpinnerButtonWidget->m_Label->setAlignment(Qt::AlignRight| Qt::AlignVCenter);
m_XformWeightSpinnerButtonWidget->m_Label->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Fixed);
m_XformWeightSpinnerButtonWidget->m_Button->setToolTip("Equalize weights");
m_XformWeightSpinnerButtonWidget->m_SpinBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
m_XformWeightSpinnerButtonWidget->m_Label->setStyleSheet("border: 0px;");
m_XformWeightSpinnerButtonWidget->m_Label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
m_XformWeightSpinnerButtonWidget->m_Label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
m_XformWeightSpinnerButtonWidget->m_Button->setToolTip("Equalize weights");
m_XformWeightSpinnerButtonWidget->m_Button->setStyleSheet("text-align: center center");
m_XformWeightSpinnerButtonWidget->setMaximumWidth(130);
m_XformWeightSpinnerButtonWidget->setMaximumWidth(130);
connect(m_XformWeightSpinnerButtonWidget->m_Button, SIGNAL(clicked(bool)), this, SLOT(OnEqualWeightButtonClicked(bool)), Qt::QueuedConnection);
ui.XformWeightNameTable->setCellWidget(0, 0, m_XformWeightSpinnerButtonWidget);
ui.XformWeightNameTable->setItem(0, 1, new QTableWidgetItem());
connect(ui.XformWeightNameTable, SIGNAL(cellChanged(int, int)), this, SLOT(OnXformNameChanged(int, int)), Qt::QueuedConnection);
ui.CurrentXformCombo->setProperty("soloxform", -1);
ui.CurrentXformCombo->view()->setMinimumWidth(100);
ui.CurrentXformCombo->view()->setMaximumWidth(500);
//ui.CurrentXformCombo->view()->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
ui.CurrentXformCombo->view()->setSizeAdjustPolicy(QAbstractScrollArea::SizeAdjustPolicy::AdjustToContentsOnFirstShow);
#ifndef _WIN32
//For some reason linux makes these 24x24, even though the designer explicitly says 16x16.
ui.AddXformButton->setIconSize(QSize(16, 16));
@ -63,7 +66,7 @@ Xform<T>* FractoriumEmberController<T>::CurrentXform()
void Fractorium::CurrentXform(uint i)
{
if (i < uint(ui.CurrentXformCombo->count()))
ui.CurrentXformCombo->setCurrentIndex(i);
ui.CurrentXformCombo->setCurrentIndex(i);
}
/// <summary>
/// Set the current xform and populate all GUI widgets.
@ -79,7 +82,7 @@ void FractoriumEmberController<T>::CurrentXformComboChanged(int index)
{
FillWithXform(xform);
m_GLController->SetSelectedXform(xform);
int solo = m_Fractorium->ui.CurrentXformCombo->property("soloxform").toInt();
int solo = m_Ember.m_Solo;
m_Fractorium->ui.SoloXformCheckBox->blockSignals(true);
m_Fractorium->ui.SoloXformCheckBox->setChecked(solo == index);
m_Fractorium->ui.SoloXformCheckBox->blockSignals(false);
@ -244,7 +247,7 @@ void FractoriumEmberController<T>::ClearXform()
{
xform->ClearAndDeleteVariations();//Note xaos is left alone.
}, eXformUpdate::UPDATE_SELECTED);
FillVariationTreeWithXform(CurrentXform());
FillVariationTreeWithCurrentXform();
}
void Fractorium::OnClearXformButtonClicked(bool checked) { m_Controller->ClearXform(); }
@ -393,11 +396,13 @@ void FractoriumEmberController<T>::XformNameChanged(int row, int col)
}, eXformUpdate::UPDATE_CURRENT, false);
FillSummary();//Manually update because this does not trigger a render, which is where this would normally be called.
}
void Fractorium::OnXformNameChanged(int row, int col)
{
m_Controller->XformNameChanged(row, col);
m_Controller->UpdateXformName(ui.CurrentXformCombo->currentIndex());
m_Controller->XformNameChanged(row, col);
m_Controller->UpdateXformName(ui.CurrentXformCombo->currentIndex());
}
/// <summary>
/// Set the animate field of the selected xforms, this allows excluding current if it's not checked, but applies only to it if none are checked.
/// This has no effect on interactive rendering, it only sets a value
@ -480,9 +485,8 @@ void FractoriumEmberController<T>::SetNormalizedWeightText(Xform<T>* xform)
m_Ember.CalcNormalizedWeights(m_NormalizedWeights);
if (index != -1 && index < m_NormalizedWeights.size())
//m_Fractorium->m_XformWeightSpin->setSuffix(QString(" (") + QLocale::system().toString(double(m_NormalizedWeights[index]), 'g', 3) + ")");
m_Fractorium->m_XformWeightSpinnerButtonWidget->m_Label->setText(QString(" (") + QLocale::system().toString(double(m_NormalizedWeights[index]), 'g', 3) + ")");
}
m_Fractorium->m_XformWeightSpinnerButtonWidget->m_Label->setText(QString(" (") + QLocale::system().toString(double(m_NormalizedWeights[index]), 'g', 3) + ")");
}
}
/// <summary>
/// Determine whether the specified xform is the final xform in the ember.
@ -517,7 +521,7 @@ void FractoriumEmberController<T>::FillXforms(int index)
{
combo->addItem(ToString(i + 1));
combo->setItemIcon(i, m_Fractorium->m_XformComboIcons[i % XFORM_COLOR_COUNT]);
UpdateXformName(i);
UpdateXformName(i);
}
i = 0;
@ -553,7 +557,7 @@ void FractoriumEmberController<T>::FillXforms(int index)
m_Fractorium->m_XformsSelectionLayout->addRow(cb, new QWidget(m_Fractorium));
combo->addItem("Final");
combo->setItemIcon(i, m_Fractorium->m_FinalXformComboIcon);
UpdateXformName(i);
UpdateXformName(i);
}
m_Fractorium->m_XformsSelectionLayout->blockSignals(false);
@ -562,31 +566,50 @@ void FractoriumEmberController<T>::FillXforms(int index)
if (index < combo->count())
combo->setCurrentIndex(index);
m_Fractorium->ui.SoloXformCheckBox->blockSignals(true);
if (m_Ember.m_Solo == combo->currentIndex())
m_Fractorium->ui.SoloXformCheckBox->setChecked(true);
else
m_Fractorium->ui.SoloXformCheckBox->setChecked(false);
SoloXformCheckBoxStateChanged(m_Ember.m_Solo > -1 ? Qt::Checked : Qt::Unchecked, m_Ember.m_Solo);
m_Fractorium->ui.SoloXformCheckBox->blockSignals(false);
m_Fractorium->FillXaosTable();
m_Fractorium->OnSoloXformCheckBoxStateChanged(Qt::Unchecked);
m_Fractorium->OnCurrentXformComboChanged(index);//Make sure the event gets called, because it won't if the zero index is already selected.
m_Fractorium->OnCurrentXformComboChanged(index);//Make sure the event gets called, because it won't if the zero index is already selected.
}
/// <summary>
/// Update the text in xforms combo box to show the name of Xform.
/// </summary>
/// <param name="index">The index of the Xform to update.</param>
///
///
template<typename T>
void FractoriumEmberController<T>::UpdateXformName(int index)
{
bool forceFinal = m_Fractorium->HaveFinal();
bool isFinal = m_Ember.FinalXform() == m_Ember.GetTotalXform(index, forceFinal);
QString name = isFinal ? "Final" : QString::number(index + 1);
if (auto xform = m_Ember.GetTotalXform(index, forceFinal))
{
if (!xform->m_Name.empty())
{
name += " " + QString::fromStdString(xform->m_Name);
}
m_Fractorium->ui.CurrentXformCombo->setItemText(index,name);
}
bool forceFinal = m_Fractorium->HaveFinal();
bool isFinal = m_Ember.FinalXform() == m_Ember.GetTotalXform(index, forceFinal);
QString name = isFinal ? "Final" : QString::number(index + 1);
if (auto xform = m_Ember.GetTotalXform(index, forceFinal))
{
if (!xform->m_Name.empty())
name += " " + QString::fromStdString(xform->m_Name);
m_Fractorium->ui.CurrentXformCombo->setItemText(index, name);
auto view = m_Fractorium->ui.CurrentXformCombo->view();
auto fontMetrics1 = view->fontMetrics();
auto textWidth = m_Fractorium->ui.CurrentXformCombo->width();
auto ww = fontMetrics1.width("WW");
for (int i = 0; i < m_Fractorium->ui.CurrentXformCombo->count(); ++i)
textWidth = std::max(fontMetrics1.width(m_Fractorium->ui.CurrentXformCombo->itemText(i)) + ww, textWidth);
view->setMinimumWidth(textWidth);
view->setMaximumWidth(textWidth);
}
}
template class FractoriumEmberController<float>;
#ifdef DO_DOUBLE
template class FractoriumEmberController<double>;

View File

@ -181,28 +181,32 @@ void Fractorium::OnXformDirectColorChanged(double d) { m_Controller->XformDirect
/// If checked, current is solo, if unchecked, none are solo.
/// Solo means that all other xforms will have their opacity temporarily
/// set to zero while rendering so that only the effect of current xform is visible.
/// This will not permanently alter the ember, as the temporary opacity values will be applied
/// This will not permanently alter the opacities of ember, as the temporary opacity values will be applied
/// right before rendering and reset right after.
/// Called when solo xform check box is checked.
/// Resets the rendering process.
/// </summary>
/// <param name="state">The state of the checkbox</param>
void Fractorium::OnSoloXformCheckBoxStateChanged(int state)
/// <param name="index">The index which has been specified as the solo xform, -1 to specify none.</param>
template <typename T>
void FractoriumEmberController<T>::SoloXformCheckBoxStateChanged(int state, int index)
{
if (state == Qt::Checked)
{
ui.CurrentXformCombo->setProperty("soloxform", ui.CurrentXformCombo->currentIndex());
ui.SoloXformCheckBox->setText("Solo (" + ToString(ui.CurrentXformCombo->currentIndex() + 1) + ")");
m_Ember.m_Solo = index;
m_Fractorium->ui.SoloXformCheckBox->setText("Solo (" + ToString(index + 1) + ")");
}
else if (state == Qt::Unchecked)
{
ui.CurrentXformCombo->setProperty("soloxform", -1);
ui.SoloXformCheckBox->setText("Solo");
m_Ember.m_Solo = -1;
m_Fractorium->ui.SoloXformCheckBox->setText("Solo");
}
m_Controller->UpdateRender();
UpdateRender();
}
void Fractorium::OnSoloXformCheckBoxStateChanged(int state) { m_Controller->SoloXformCheckBoxStateChanged(state, ui.CurrentXformCombo->currentIndex()); }
/// <summary>
/// Redraw the palette ref table.
/// Called on resize.

View File

@ -16,6 +16,11 @@ void Fractorium::InitXformsVariationsUI()
//Setting dimensions in the designer with a layout is futile, so must hard code here.
tree->setColumnWidth(0, 160);
tree->setColumnWidth(1, 23);
//Set Default variation tree text and background colors for zero and non zero cases.
m_VariationTreeColorNonZero = Qt::black;
m_VariationTreeColorZero = Qt::black;
m_VariationTreeBgColorNonZero = Qt::lightGray;
m_VariationTreeBgColorZero = Qt::white;
}
/// <summary>
@ -226,8 +231,9 @@ void FractoriumEmberController<T>::VariationSpinBoxValueChanged(double d)//Would
if (xformVar)
xform->DeleteVariationById(var->VariationId());
// widgetItem->setBackgroundColor(0, QColor(255, 255, 255));//Ensure background is always white if weight goes to zero.
widgetItem->setBackgroundColor(0, m_Fractorium->m_VariationTreeBgColorZero); }
widgetItem->setTextColor(0, m_Fractorium->m_VariationTreeColorZero);
widgetItem->setBackgroundColor(0, m_Fractorium->m_VariationTreeBgColorZero);
}
else
{
if (xformVar)//The xform already contained this variation, which means they just went from a non-zero weight to another non-zero weight (the simple case).
@ -241,8 +247,9 @@ void FractoriumEmberController<T>::VariationSpinBoxValueChanged(double d)//Would
auto newVar = var->Copy();//Create a new one with default values.
newVar->m_Weight = d;
xform->AddVariation(newVar);
// widgetItem->setBackgroundColor(0, QColor(200, 200, 200));//Set background to gray when a variation has non-zero weight in this xform.
widgetItem->setBackgroundColor(0, m_Fractorium->m_VariationTreeBgColorNoneZero);
widgetItem->setTextColor(0, m_Fractorium->m_VariationTreeColorNonZero);
widgetItem->setBackgroundColor(0, m_Fractorium->m_VariationTreeBgColorNonZero);
//If they've added a new parametric variation, then grab the values currently in the spinners
//for the child parameters and assign them to the newly added variation.
if (parVar)
@ -275,6 +282,15 @@ void FractoriumEmberController<T>::VariationSpinBoxValueChanged(double d)//Would
void Fractorium::OnVariationSpinBoxValueChanged(double d) { m_Controller->VariationSpinBoxValueChanged(d); }
/// <summary>
/// Fill in the variations tree with the values from the current xform.
/// </summary>
template <typename T>
void FractoriumEmberController<T>::FillVariationTreeWithCurrentXform()
{
FillVariationTreeWithXform(CurrentXform());
}
/// <summary>
/// Fill the variation tree values from passed in xform and apply the current sorting mode.
/// Called when the currently selected xform changes.
@ -300,9 +316,9 @@ void FractoriumEmberController<T>::FillVariationTreeWithXform(Xform<T>* xform)
item->setHidden(false);
spinBox->SetValueStealth(var ? var->m_Weight : 0);//If the variation was present, set the spin box to its weight, else zero.
// item->setBackgroundColor(0, var ? Qt::darkGray : Qt::lightGray);//Ensure background is always white if the value goes to zero, else gray if var present.
// item->setBackgroundColor(0, var ? QColor(200, 200, 200) : QColor(255, 255, 255));//Ensure background is always white if the value goes to zero, else gray if var present.
item->setBackgroundColor(0, var ? m_Fractorium->m_VariationTreeBgColorNoneZero : m_Fractorium->m_VariationTreeBgColorZero);
item->setTextColor(0, var ? m_Fractorium->m_VariationTreeColorNonZero : m_Fractorium->m_VariationTreeColorZero);
item->setBackgroundColor(0, var ? m_Fractorium->m_VariationTreeBgColorNonZero : m_Fractorium->m_VariationTreeBgColorZero);
for (int j = 0; j < item->childCount(); j++)//Iterate through all of the children, which will be the params if it was a parametric variation.
{
T* param = nullptr;

View File

@ -685,7 +685,7 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
GLfloat(m_DragHandlePos.x), GLfloat(m_DragHandlePos.y)
};
QVector4D col(1.0f, 1.0f, 0.5f, 1.0f);
m_GL->DrawPointOrLine(col, vertices, 1, GL_POINTS, 6.0f);
m_GL->DrawPointOrLine(col, vertices, 1, GL_POINTS, false, 6.0f);
#endif
}
else if (m_DragState == eDragState::DragSelect)
@ -733,7 +733,7 @@ void GLEmberController<T>::DrawAffines(bool pre, bool post)
GLfloat(m_HoverHandlePos.x), GLfloat(m_HoverHandlePos.y)
};
QVector4D col(0.5f, 1.0f, 1.0f, 1.0f);
m_GL->DrawPointOrLine(col, vertices, 1, GL_POINTS, 6.0f);
m_GL->DrawPointOrLine(col, vertices, 1, GL_POINTS, false, 6.0f);
#endif
}
}
@ -1091,10 +1091,11 @@ void GLWidget::wheelEvent(QWheelEvent* e)
/// <param name="col">The color to draw with</param>
/// <param name="vertices">The vertices to use</param>
/// <param name="drawType">The type of primitive to draw, such as GL_POINT or GL_LINES</param>
/// <param name="dashed">True to draw dashed lines, else solid</param>
/// <param name="pointSize">The size in pixels of points, which is internally scaled by the device pixel ratio.</param>
void GLWidget::DrawPointOrLine(const QVector4D& col, const std::vector<float>& vertices, int drawType, GLfloat pointSize)
void GLWidget::DrawPointOrLine(const QVector4D& col, const std::vector<float>& vertices, int drawType, bool dashed, GLfloat pointSize)
{
DrawPointOrLine(col, vertices.data(), int(vertices.size() / 2), drawType, pointSize);
DrawPointOrLine(col, vertices.data(), int(vertices.size() / 2), drawType, dashed, pointSize);
}
/// <summary>
@ -1104,10 +1105,18 @@ void GLWidget::DrawPointOrLine(const QVector4D& col, const std::vector<float>& v
/// <param name="vertices">The vertices to use</param>
/// <param name="size">The number of verticies. This is usually the size of vertices / 2.</param>
/// <param name="drawType">The type of primitive to draw, such as GL_POINT or GL_LINES</param>
/// <param name="dashed">True to draw dashed lines, else solid</param>
/// <param name="pointSize">The size in pixels of points, which is internally scaled by the device pixel ratio.</param>
void GLWidget::DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, int size, int drawType, GLfloat pointSize)
void GLWidget::DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, int size, int drawType, bool dashed, GLfloat pointSize)
{
#ifdef USE_GLSL
if (dashed && drawType == GL_LINES)
{
glLineStipple(1, 0XFF00);
glEnable(GL_LINE_STIPPLE);
}
m_ModelViewProjectionMatrix = m_ProjMatrix * m_ModelViewMatrix;
m_Program->setUniformValue(m_ColAttr, col);
m_Program->setUniformValue(m_PointSizeUniform, pointSize * GLfloat(devicePixelRatioF()));
@ -1116,6 +1125,10 @@ void GLWidget::DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, in
this->glEnableVertexAttribArray(0);
this->glDrawArrays(drawType, 0, size);
this->glDisableVertexAttribArray(0);
if (dashed && drawType == GL_LINES)
glDisable(GL_LINE_STIPPLE);
#endif
}
@ -1509,7 +1522,7 @@ void GLEmberController<T>::DrawAffine(Xform<T>* xform, bool pre, bool selected)
m_Verts.push_back(0.0f);
m_Verts.push_back(0.0f);
m_Verts.push_back(1.0f);
m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, 5.0f);//Three black points, one in the center and two on the circle. Drawn big 5px first to give a black outline.
m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, !pre, 5.0f);//Three black points, one in the center and two on the circle. Drawn big 5px first to give a black outline.
//
m_GL->glLineWidth(2.0f * m_GL->devicePixelRatioF());//Draw lines again for y axis only, without drawing the circle, using the color of the selected xform.
m_Verts.clear();
@ -1518,7 +1531,7 @@ void GLEmberController<T>::DrawAffine(Xform<T>* xform, bool pre, bool selected)
m_Verts.push_back(0.0f);
m_Verts.push_back(1.0f);
col = QVector4D(color.r, color.g, color.b, 1.0f);
m_GL->DrawPointOrLine(col, m_Verts, GL_LINES);
m_GL->DrawPointOrLine(col, m_Verts, GL_LINES, !pre);
//
m_Verts.clear();
m_Verts.push_back(0.0f);
@ -1528,7 +1541,7 @@ void GLEmberController<T>::DrawAffine(Xform<T>* xform, bool pre, bool selected)
m_Verts.push_back(0.0f);
m_Verts.push_back(1.0f);
col = QVector4D(1.0f, 1.0f, 1.0f, selected ? 1.0f : 0.5f);
m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, 3.0f);//Draw smaller white points, to give a black outline effect.
m_GL->DrawPointOrLine(col, m_Verts, GL_POINTS, false, 3.0f);//Draw smaller white points, to give a black outline effect.
m_GL->m_ModelViewMatrix.setToIdentity();
#endif
}
@ -1628,7 +1641,7 @@ void GLWidget::DrawAffineHelper(int index, bool selected, bool pre, bool final,
}
}
DrawPointOrLine(color, m_Verts, GL_LINES);
DrawPointOrLine(color, m_Verts, GL_LINES, !pre);
//Lines from center to circle.
if (!background)
@ -1649,7 +1662,7 @@ void GLWidget::DrawAffineHelper(int index, bool selected, bool pre, bool final,
m_Verts.push_back(0);
m_Verts.push_back(1);
m_Verts.push_back(0);
DrawPointOrLine(color, m_Verts, GL_LINES);
DrawPointOrLine(color, m_Verts, GL_LINES, !pre);
if (background)
color = QVector4D(0.0f, 0.0f, 0.0f, 1.0f);
@ -1659,7 +1672,7 @@ void GLWidget::DrawAffineHelper(int index, bool selected, bool pre, bool final,
m_Verts.push_back(0);
m_Verts.push_back(0);
m_Verts.push_back(1);
DrawPointOrLine(color, m_Verts, GL_LINES);
DrawPointOrLine(color, m_Verts, GL_LINES, !pre);
#endif
}

View File

@ -66,8 +66,8 @@ protected:
virtual void mouseMoveEvent(QMouseEvent* e) override;
virtual void wheelEvent(QWheelEvent* e) override;
void DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, int size, int drawType, GLfloat pointSize = 1.0f);
void DrawPointOrLine(const QVector4D& col, const std::vector<float>& vertices, int drawType, GLfloat pointSize = 1.0f);
void DrawPointOrLine(const QVector4D& col, const GLfloat* vertices, int size, int drawType, bool dashed = false, GLfloat pointSize = 1.0f);
void DrawPointOrLine(const QVector4D& col, const std::vector<float>& vertices, int drawType, bool dashed = false, GLfloat pointSize = 1.0f);
private:
void SetDimensions(int w, int h);

View File

@ -70,11 +70,11 @@
<height>16777215</height>
</size>
</property>
<property name="font">
<font>
<kerning>true</kerning>
</font>
</property>
<property name="font">
<font>
<kerning>true</kerning>
</font>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
@ -213,17 +213,17 @@
</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>
<item row="1" column="0">
<widget class="QCheckBox" name="AutoDistributeCheckBox">
<property name="text">
<string>Sync</string>
<string>Auto Distribute</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0">
<item row="3" column="0">
<widget class="QSpinBox" name="ArrowsSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
@ -251,10 +251,20 @@
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QCheckBox" name="AutoDistributeCheckBox">
<item row="0" column="0">
<widget class="QCheckBox" name="SyncCheckBox">
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="text">
<string>Auto Distribute</string>
<string>Sync</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="BlendCheckBox">
<property name="text">
<string>Blend</string>
</property>
<property name="checked">
<bool>true</bool>

View File

@ -36,6 +36,24 @@ GradientColorsView::GradientColorsView(QWidget* p)
ResetToDefault();
}
/// <summary>
/// Get whether to interpolate color keys.
/// </summary>
/// <returns>True to interpolate (blend), false to do hard cuts.</returns>
bool GradientColorsView::Blend()
{
return m_Blend;
}
/// <summary>
/// Set whether to interpolate color keys.
/// </summary>
/// <param name="blend">rue to interpolate (blend), false to do hard cuts.</param>
void GradientColorsView::Blend(bool blend)
{
m_Blend = blend;
}
/// <summary>
/// Set the focus to the arrow at the given normalized position.
/// </summary>
@ -374,23 +392,37 @@ Palette<float>& GradientColorsView::GetPalette(int size)
{
if (!m_Arrows.empty())
{
QPainter p;
QSize imageSize(size, 1);
QImage image(imageSize, QImage::Format_ARGB32_Premultiplied);
QLinearGradient grad(QPoint(0, 0), QPoint(imageSize.width(), imageSize.height()));
m_Palette.m_SourceColors.clear();
QPainter painter(&image);
float start = 0;
for (auto& it : m_Arrows)
if (Blend())
{
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);
QLinearGradient grad(QPoint(0, 0), QPoint(imageSize.width(), imageSize.height()));
for (auto& it : m_Arrows)
{
auto col = it.second.Color();
m_Palette.m_SourceColors[it.first] = v4F(col.red() / 255.0f, col.green() / 255.0f, col.blue() / 255.0f, 1.0f);
grad.setColorAt(it.first, col);
}
painter.fillRect(image.rect(), grad);
}
else
{
for (auto& it : m_Arrows)
{
auto col = it.second.Color();
m_Palette.m_SourceColors[it.first] = v4F(col.red() / 255.0f, col.green() / 255.0f, col.blue() / 255.0f, 1.0f);
painter.setBrush(col);
painter.fillRect(start, 0, imageSize.width(), imageSize.height(), col);
start = std::ceil(it.first * imageSize.width());
}
}
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++)
@ -493,13 +525,25 @@ void GradientColorsView::paintEvent(QPaintEvent*)
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);
float start = m_ViewRect.x();
for (auto& it : m_Arrows)
{
GradientArrow& arrow = it.second;
grad.setColorAt(it.first, arrow.Color());
auto offset = std::ceil(it.first * RectWidth());
if (Blend())
{
grad.setColorAt(it.first, arrow.Color());
}
else
{
painter.fillRect(start, m_ViewRect.y(), m_ViewRect.right() - start, m_ViewRect.height(), arrow.Color());
start = m_ViewRect.x() + offset;
}
QPolygon arrowPolygon = arrow.Area();
int iPosX = it.first * RectWidth(),
int iPosX = offset,
iPosY = m_ViewRect.height() + m_ViewRect.top() + 3;
arrowPolygon.translate(iPosX, iPosY);
QPainterPath paintPath;
@ -513,8 +557,11 @@ void GradientColorsView::paintEvent(QPaintEvent*)
painter.setBrush(QBrush(Qt::NoBrush));
}
QBrush brush(grad);
painter.fillRect(m_ViewRect, brush);
if (Blend())
{
painter.fillRect(m_ViewRect, grad);
}
painter.drawRect(m_ViewRect);
}
else
@ -534,8 +581,6 @@ void GradientColorsView::paintEvent(QPaintEvent*)
//Draw text inside of the arrow.
painter.drawText(topArrowRect.x() + (topArrowRect.width() - (topArrow.Width() - 5)) / 2.0, topArrowRect.y() + (topArrowRect.height() - 5), topArrow.Text());
}
painter.end();
}
/// <summary>

View File

@ -35,6 +35,8 @@ class GradientColorsView : public QWidget
public:
explicit GradientColorsView(QWidget* p = nullptr);
bool Blend();
void Blend(bool blend);
void SetFocus(float position);
void SetFocus(size_t position);
void SetFocusColor(const QColor& col);
@ -74,6 +76,7 @@ private:
int RectHeight();
bool m_ArrowMoving = false;
bool m_ColorIndexArrowMoving = false;
bool m_Blend = true;
QPoint m_ViewRectSize;
QPoint m_ViewRectOffset = QPoint(5, 15);
QPoint m_ViewRectTranslate = QPoint(5, 5);

View File

@ -34,9 +34,10 @@ PaletteEditor::PaletteEditor(QWidget* p) :
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->DistributeColorsButton, SIGNAL(clicked()), this, SLOT(OnDistributeColorsButtonClicked()));
connect(ui->SyncCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnSyncCheckBoxStateChanged(int)), Qt::QueuedConnection);
connect(ui->BlendCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnBlendCheckBoxStateChanged(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);
@ -290,6 +291,18 @@ void PaletteEditor::OnSyncCheckBoxStateChanged(int state)
EmitColorIndexChanged(std::numeric_limits<size_t>::max(), 0);//Pass special value to update all.
}
/// <summary>
/// Change whether palette colors are blended between points, or instead do hard cuts.
/// Called when the Blend checkbox is checked/unchecked.
/// </summary>
/// <param name="state">Ignored</param>
void PaletteEditor::OnBlendCheckBoxStateChanged(int state)
{
m_GradientColorView->Blend((bool)state);
m_GradientColorView->update();
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.
@ -607,6 +620,7 @@ void PaletteEditor::EnablePaletteControls()
ui->AddColorButton->setEnabled(b);
ui->DistributeColorsButton->setEnabled(b);
ui->AutoDistributeCheckBox->setEnabled(b);
ui->BlendCheckBox->setEnabled(b);
ui->RandomColorsButton->setEnabled(b);
ui->RemoveColorButton->setEnabled(b);
ui->ResetColorsButton->setEnabled(b);

View File

@ -54,6 +54,7 @@ private Q_SLOTS:
void OnColorPickerColorChanged(const QColor& col);
void OnArrowDoubleClicked(const GradientArrow& arrow);
void OnSyncCheckBoxStateChanged(int state);
void OnBlendCheckBoxStateChanged(int state);
void OnArrowMoved(qreal lastPos, const GradientArrow& arrow);
void OnColorIndexMove(size_t index, float value);
void OnPaletteFilenameComboChanged(const QString& text);

View File

@ -315,6 +315,7 @@ void QssDialog::reject()
m_Parent->m_Settings->Theme(m_LastTheme->objectName());
}
m_Parent->m_Controller->FillVariationTreeWithCurrentXform();
QDialog::reject();
}
@ -466,6 +467,7 @@ void QssDialog::SlotApplyCss()
label->setText(tr("Valid Style Sheet"));
label->setStyleSheet(QStringLiteral("color: green"));
m_Parent->setStyleSheet(style);
m_Parent->m_Controller->FillVariationTreeWithCurrentXform();
}
else
{

View File

@ -161,9 +161,12 @@ void SpinBox::OnTimeout()
/// <returns>false</returns>
bool SpinBox::eventFilter(QObject* o, QEvent* e)
{
if (!isEnabled())
return QSpinBox::eventFilter(o, e);
auto me = dynamic_cast<QMouseEvent*>(e);
if (isEnabled() && me)
if (me)
{
if (!m_Settings->ToggleType() &&//Ensure double click toggles, not right click.
me->type() == QMouseEvent::MouseButtonPress &&
@ -222,6 +225,47 @@ bool SpinBox::eventFilter(QObject* o, QEvent* e)
return QSpinBox::eventFilter(o, e);
}
/// <summary>
/// Override which is for handling specific key presses while this control is focused.
/// In particular, + = and up arrow increase the value, equivalent to scrolling the mouse wheel up, while also observing shift/ctrl modifiers.
/// Values are decreased in the same way by pressing - _ or down arrow.
/// </summary>
/// <param name="ke">The key event</param>
void SpinBox::keyPressEvent(QKeyEvent* ke)
{
bool shift = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
bool ctrl = QGuiApplication::keyboardModifiers().testFlag(Qt::ControlModifier);
if (ke->key() == Qt::Key_Plus || ke->key() == Qt::Key_Equal || ke->key() == Qt::Key_Up)
{
if (shift)
{
setSingleStep(m_SmallStep);
setValue(value() + m_SmallStep);
}
else
{
setSingleStep(m_Step);
setValue(value() + (ctrl ? m_Step * 10 : m_Step));
}
}
else if (ke->key() == Qt::Key_Minus || ke->key() == Qt::Key_Underscore || ke->key() == Qt::Key_Down)
{
if (shift)
{
setSingleStep(m_SmallStep);
setValue(value() - m_SmallStep);
}
else
{
setSingleStep(m_Step);
setValue(value() - (ctrl ? m_Step * 10 : m_Step));
}
}
else
QSpinBox::keyPressEvent(ke);
}
/// <summary>
/// Called when focus enters the spinner.
/// </summary>

View File

@ -38,11 +38,12 @@ public slots:
void OnTimeout();
protected:
bool eventFilter(QObject* o, QEvent* e);
virtual void focusInEvent(QFocusEvent* e);
virtual void focusOutEvent(QFocusEvent* e);
virtual void enterEvent(QEvent* e);
virtual void leaveEvent(QEvent* e);
virtual bool eventFilter(QObject* o, QEvent* e) override;
virtual void keyPressEvent(QKeyEvent* event) override;
virtual void focusInEvent(QFocusEvent* e) override;
virtual void focusOutEvent(QFocusEvent* e) override;
virtual void enterEvent(QEvent* e) override;
virtual void leaveEvent(QEvent* e) override;
private:
void StartTimer();

View File

@ -34,7 +34,6 @@ public:
m_Button1 = new QPushButton(caption1, p);
m_Button2 = new QPushButton(caption2, p);
m_Combo = new QComboBox(p);
m_Combo->addItems(comboStrings);
if (w1 != -1)
@ -55,14 +54,12 @@ public:
m_Button2->setMaximumHeight(h);
m_Combo->setMinimumHeight(h - 3);
m_Combo->setMaximumHeight(h - 3);
l->addWidget(m_Combo);
l->addWidget(m_Button1);
l->addWidget(m_Button2);
l->setAlignment(Qt::AlignLeft);
l->setMargin(0);
l->setSpacing(2);
setLayout(l);
}
@ -105,13 +102,11 @@ public:
m_Button->setMinimumHeight(h);
m_Button->setMaximumHeight(h);
l->addWidget(spinBox);
l->addWidget(m_Button);
l->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
l->setMargin(0);
l->setSpacing(0);
setLayout(l);
}
@ -125,8 +120,8 @@ class SpinnerLabelButtonWidget : public QWidget
public:
/// <summary>
/// Constructor that passes the parent to the base, then creates a QLabel,
/// then creates a QPushButton and sets up its caption and dimensions, then
/// Constructor that passes the parent to the base, then creates a QLabel,
/// then creates a QPushButton and sets up its caption and dimensions, then
/// assigns the DoubleSpinBox.
/// </summary>
/// <param name="spinBox">The pre-created DoubleSpinBox</param>
@ -139,12 +134,12 @@ public:
{
QHBoxLayout* l = new QHBoxLayout(this);
m_Button = new QPushButton(buttonCaption, p);
m_SpinBox = spinBox;
m_SpinBox = spinBox;
m_Label = new QLabel(p);
m_Label->setMinimumHeight(h);
m_Label->setMaximumHeight(h);
if (w != -1)
m_Label->setMinimumHeight(h);
m_Label->setMaximumHeight(h);
if (w != -1)
{
m_Button->setMinimumWidth(w);
m_Button->setMaximumWidth(w);
@ -152,9 +147,8 @@ public:
m_Button->setMinimumHeight(h);
m_Button->setMaximumHeight(h);
l->addWidget(spinBox);
l->addWidget(m_Label);
l->addWidget(m_Label);
l->addWidget(m_Button);
l->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
l->setMargin(0);
@ -164,5 +158,5 @@ public:
DoubleSpinBox* m_SpinBox;
QPushButton* m_Button;
QLabel* m_Label;
QLabel* m_Label;
};

View File

@ -194,10 +194,8 @@ void FractoriumVariationsDialog::OnSelectionCheckBoxStateChanged(int i)
static vector<string> dc{ "m_ColorX" };
static vector<string> assign{ "outPoint->m_X =", "outPoint->m_Y =", "outPoint->m_Z =",
"outPoint->m_X=", "outPoint->m_Y=", "outPoint->m_Z=" };
//static vector<const Variation<T>*> excluded;
static std::set<eVariationId> excluded;
excluded.clear();
//excluded.reserve(size_t(eVariationId::LAST_VAR));
if (auto s = dynamic_cast<QCheckBox*>(sender()))
{